1use crate::child;
4use crate::command::build::BuildProfile;
5use crate::emoji;
6use crate::manifest::Crate;
7use crate::PBAR;
8use anyhow::{anyhow, bail, Context, Result};
9use cargo_metadata::Message;
10use std::io::BufReader;
11use std::path::Path;
12use std::process::{Command, Stdio};
13use std::str;
14
15pub mod wasm_target;
16
17pub struct WasmPackVersion {
20 pub local: String,
22 pub latest: String,
25}
26
27pub fn check_rustc_version() -> Result<String> {
29 let local_minor_version = rustc_minor_version();
30 match local_minor_version {
31 Some(mv) => {
32 if mv < 30 {
33 bail!(
34 "Your version of Rust, '1.{}', is not supported. Please install Rust version 1.30.0 or higher.",
35 mv.to_string()
36 )
37 } else {
38 Ok(mv.to_string())
39 }
40 }
41 None => bail!("We can't figure out what your Rust version is- which means you might not have Rust installed. Please install Rust version 1.30.0 or higher."),
42 }
43}
44
45fn rustc_minor_version() -> Option<u32> {
47 macro_rules! otry {
48 ($e:expr) => {
49 match $e {
50 Some(e) => e,
51 None => return None,
52 }
53 };
54 }
55 let output = otry!(Command::new("rustc").arg("--version").output().ok());
56 let version = otry!(str::from_utf8(&output.stdout).ok());
57 let mut pieces = version.split('.');
58 if pieces.next() != Some("rustc 1") {
59 return None;
60 }
61 otry!(pieces.next()).parse().ok()
62}
63
64pub fn check_wasm_pack_versions() -> Result<WasmPackVersion> {
66 match wasm_pack_local_version() {
67 Some(local) => Ok(WasmPackVersion {local, latest: Crate::return_wasm_pack_latest_version()?.unwrap_or_else(|| "".to_string())}),
68 None => bail!("We can't figure out what your wasm-pack version is, make sure the installation path is correct.")
69 }
70}
71
72fn wasm_pack_local_version() -> Option<String> {
73 let output = env!("CARGO_PKG_VERSION");
74 Some(output.to_string())
75}
76
77pub fn is_tier3_wasm(target_triple: &str) -> bool {
81 target_triple.starts_with("wasm64")
82}
83
84pub fn cargo_build_wasm(
86 path: &Path,
87 profile: BuildProfile,
88 extra_options: &[String],
89 target_triple: &str,
90 panic_unwind: bool,
91) -> Result<String> {
92 let msg = if panic_unwind {
93 format!("{}Compiling to Wasm (with panic=unwind)...", emoji::CYCLONE)
94 } else {
95 format!("{}Compiling to Wasm...", emoji::CYCLONE)
96 };
97 PBAR.info(&msg);
98
99 let mut cmd = Command::new("cargo");
100 cmd.current_dir(path);
101 if panic_unwind {
103 cmd.arg("+nightly");
104 }
105 cmd.arg("build").arg("--lib");
106
107 if PBAR.quiet() {
108 cmd.arg("--quiet");
109 }
110
111 match profile {
112 BuildProfile::Profiling => {
113 cmd.arg("--release");
119 }
120 BuildProfile::Release => {
121 cmd.arg("--release");
122 }
123 BuildProfile::Dev => {
124 }
127 BuildProfile::Custom(arg) => {
128 cmd.arg("--profile").arg(arg);
129 }
130 }
131
132 cmd.env("CARGO_BUILD_TARGET", target_triple);
133
134 if panic_unwind {
135 cmd.arg("-Z").arg("build-std=std,panic_unwind");
136
137 let existing = std::env::var("RUSTFLAGS").unwrap_or_default();
139 let combined = if existing.is_empty() {
140 "-Cpanic=unwind".to_string()
141 } else {
142 format!("{existing} -Cpanic=unwind")
143 };
144 cmd.env("RUSTFLAGS", combined);
145 }
146
147 let mut handle_path = false;
150 let extra_options_with_absolute_paths = extra_options
151 .iter()
152 .map(|option| -> Result<String> {
153 let value = if handle_path && Path::new(option).is_relative() {
154 std::env::current_dir()?
155 .join(option)
156 .to_str()
157 .ok_or_else(|| anyhow!("path contains non-UTF-8 characters"))?
158 .to_string()
159 } else {
160 option.to_string()
161 };
162 handle_path = matches!(&**option, "--target-dir" | "--out-dir" | "--manifest-path");
163 Ok(value)
164 })
165 .collect::<Result<Vec<_>>>()?;
166 cmd.args(extra_options_with_absolute_paths);
167
168 cmd.arg("--message-format=json");
169
170 let mut cargo_process = cmd.stdout(Stdio::piped()).spawn()?;
171
172 let final_artifact =
173 Message::parse_stream(BufReader::new(cargo_process.stdout.as_mut().unwrap()))
174 .filter_map(|msg| {
175 match msg {
176 Ok(Message::CompilerArtifact(artifact)) => return Some(artifact),
177 Ok(Message::CompilerMessage(msg)) => eprintln!("{msg}"),
178 Ok(Message::TextLine(text)) => eprintln!("{text}"),
179 Err(err) => log::error!("Couldn't parse cargo message: {err}"),
180 _ => {} }
182 None
183 })
184 .last();
185
186 if !cargo_process
187 .wait()
188 .context("Failed to wait for cargo build process")?
189 .success()
190 {
191 bail!("`cargo build` failed, see the output above for details");
192 }
193
194 let wasm_files: Vec<_> = final_artifact
195 .context("Expected at least one compiler artifact in the output of `cargo build`")?
196 .filenames
197 .into_iter()
198 .filter(|path| path.extension() == Some("wasm"))
199 .collect();
200
201 match <[_; 1]>::try_from(wasm_files) {
202 Ok([filename]) => Ok(filename.into_string()),
203 Err(filenames) => {
204 bail!(
205 "Expected exactly one .wasm file in the compiler artifact, but found {filenames:?}"
206 )
207 }
208 }
209}
210
211pub fn cargo_build_wasm_tests(
229 path: &Path,
230 debug: bool,
231 extra_options: &[String],
232 target_triple: &str,
233 panic_unwind: bool,
234) -> Result<()> {
235 let mut cmd = Command::new("cargo");
236 cmd.current_dir(path);
237 if panic_unwind {
239 cmd.arg("+nightly");
240 }
241 cmd.arg("build").arg("--tests");
242
243 if PBAR.quiet() {
244 cmd.arg("--quiet");
245 }
246
247 if !debug {
248 cmd.arg("--release");
249 }
250
251 cmd.env("CARGO_BUILD_TARGET", target_triple);
252
253 if panic_unwind {
254 cmd.arg("-Z").arg("build-std=std,panic_unwind");
255
256 let existing = std::env::var("RUSTFLAGS").unwrap_or_default();
257 let combined = if existing.is_empty() {
258 "-Cpanic=unwind".to_string()
259 } else {
260 format!("{existing} -Cpanic=unwind")
261 };
262 cmd.env("RUSTFLAGS", combined);
263 }
264
265 cmd.args(extra_options);
266
267 child::run(cmd, "cargo build").context("Compilation of your program failed")?;
268 Ok(())
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn tier3_wasm_detection() {
277 assert!(is_tier3_wasm("wasm64-unknown-unknown"));
278 assert!(is_tier3_wasm("wasm64-wasi"));
279 assert!(!is_tier3_wasm("wasm32-unknown-unknown"));
280 assert!(!is_tier3_wasm("wasm32-wasi"));
281 assert!(!is_tier3_wasm("wasm32-unknown-emscripten"));
282 assert!(!is_tier3_wasm("x86_64-unknown-linux-gnu"));
283 }
284}