use anyhow::{Context, Result};
use std::path::PathBuf;
use std::process::Command;
pub fn build(manifest: &str, script: &str) -> Result<Vec<u8>> {
let opts = BuildOpts {
manifest: manifest.to_string(),
script: script.to_string(),
..Default::default()
};
build_with(&opts)
}
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct BuildOpts {
pub manifest: String,
pub script: String,
pub offline: bool,
pub toolchain: Option<String>,
pub arrow_udf_version: Option<String>,
pub tempdir: Option<PathBuf>,
}
pub fn build_with(opts: &BuildOpts) -> Result<Vec<u8>> {
if !opts.offline {
let mut command = Command::new("rustup");
if let Some(toolchain) = &opts.toolchain {
command.arg(format!("+{toolchain}"));
}
let output = command
.arg("target")
.arg("add")
.arg("wasm32-wasip1")
.output()
.context("failed to run `rustup target add wasm32-wasip1`")?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"failed to install wasm32-wasip1 target. ({})\n--- stdout\n{}\n--- stderr\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}
}
let manifest = format!(
r#"
[package]
name = "udf"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies.arrow-udf]
version = "{}"
{}"#,
opts.arrow_udf_version.as_deref().unwrap_or("0.2"),
opts.manifest
);
let tempdir = if opts.tempdir.is_some() {
None
} else {
Some(tempfile::tempdir().context("failed to create tempdir")?)
};
let dir = match &opts.tempdir {
Some(dir) => dir,
None => tempdir.as_ref().unwrap().path(),
};
std::fs::create_dir_all(dir.join("src"))?;
std::fs::write(dir.join("src/lib.rs"), &opts.script)?;
std::fs::write(dir.join("Cargo.toml"), manifest)?;
let mut command = Command::new("cargo");
if let Some(toolchain) = &opts.toolchain {
command.arg(format!("+{toolchain}"));
}
command
.arg("build")
.arg("--release")
.arg("--target")
.arg("wasm32-wasip1")
.current_dir(dir);
if opts.offline {
command.arg("--offline");
}
let output = command.output().context("failed to run cargo build")?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"failed to build wasm ({})\n--- stdout\n{}\n--- stderr\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}
let binary_path = dir.join("target/wasm32-wasip1/release/udf.wasm");
if Command::new("wasm-strip").arg("--version").output().is_ok() {
let output = Command::new("wasm-strip")
.arg(&binary_path)
.output()
.context("failed to strip wasm")?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"failed to strip wasm. ({})\n--- stdout\n{}\n--- stderr\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}
}
let binary = std::fs::read(binary_path).context("failed to read wasm binary")?;
Ok(binary)
}