arrow_udf_runtime/wasm/
build.rs1use anyhow::{Context, Result};
4use std::path::PathBuf;
5use std::process::Command;
6
7pub fn build(manifest: &str, script: &str) -> Result<Vec<u8>> {
37 let opts = BuildOpts {
38 manifest: manifest.to_string(),
39 script: script.to_string(),
40 ..Default::default()
41 };
42 build_with(&opts)
43}
44
45#[derive(Debug, Default)]
47#[non_exhaustive]
48pub struct BuildOpts {
49 pub manifest: String,
51 pub script: String,
53 pub offline: bool,
55 pub toolchain: Option<String>,
57 pub arrow_udf_version: Option<String>,
60 pub tempdir: Option<PathBuf>,
63}
64
65pub fn build_with(opts: &BuildOpts) -> Result<Vec<u8>> {
67 if !opts.offline {
69 let mut command = Command::new("rustup");
70 if let Some(toolchain) = &opts.toolchain {
71 command.arg(format!("+{}", toolchain));
72 }
73 let output = command
74 .arg("target")
75 .arg("add")
76 .arg("wasm32-wasip1")
77 .output()
78 .context("failed to run `rustup target add wasm32-wasip1`")?;
79 if !output.status.success() {
80 return Err(anyhow::anyhow!(
81 "failed to install wasm32-wasip1 target. ({})\n--- stdout\n{}\n--- stderr\n{}",
82 output.status,
83 String::from_utf8_lossy(&output.stdout),
84 String::from_utf8_lossy(&output.stderr)
85 ));
86 }
87 }
88
89 let manifest = format!(
90 r#"
91[package]
92name = "udf"
93version = "0.1.0"
94edition = "2021"
95
96[lib]
97crate-type = ["cdylib"]
98
99[dependencies.arrow-udf]
100version = "{}"
101
102{}"#,
103 opts.arrow_udf_version.as_deref().unwrap_or("0.2"),
104 opts.manifest
105 );
106
107 let tempdir = if opts.tempdir.is_some() {
109 None
110 } else {
111 Some(tempfile::tempdir().context("failed to create tempdir")?)
112 };
113 let dir = match &opts.tempdir {
114 Some(dir) => dir,
115 None => tempdir.as_ref().unwrap().path(),
116 };
117 std::fs::create_dir_all(dir.join("src"))?;
118 std::fs::write(dir.join("src/lib.rs"), &opts.script)?;
119 std::fs::write(dir.join("Cargo.toml"), manifest)?;
120
121 let mut command = Command::new("cargo");
122 if let Some(toolchain) = &opts.toolchain {
123 command.arg(format!("+{}", toolchain));
124 }
125 command
126 .arg("build")
127 .arg("--release")
128 .arg("--target")
129 .arg("wasm32-wasip1")
130 .current_dir(dir);
131 if opts.offline {
132 command.arg("--offline");
133 }
134 let output = command.output().context("failed to run cargo build")?;
135 if !output.status.success() {
136 return Err(anyhow::anyhow!(
137 "failed to build wasm ({})\n--- stdout\n{}\n--- stderr\n{}",
138 output.status,
139 String::from_utf8_lossy(&output.stdout),
140 String::from_utf8_lossy(&output.stderr)
141 ));
142 }
143 let binary_path = dir.join("target/wasm32-wasip1/release/udf.wasm");
144 if Command::new("wasm-strip").arg("--version").output().is_ok() {
146 let output = Command::new("wasm-strip")
147 .arg(&binary_path)
148 .output()
149 .context("failed to strip wasm")?;
150 if !output.status.success() {
151 return Err(anyhow::anyhow!(
152 "failed to strip wasm. ({})\n--- stdout\n{}\n--- stderr\n{}",
153 output.status,
154 String::from_utf8_lossy(&output.stdout),
155 String::from_utf8_lossy(&output.stderr)
156 ));
157 }
158 }
159 let binary = std::fs::read(binary_path).context("failed to read wasm binary")?;
160 Ok(binary)
161}