stylus_tools/core/build/
mod.rs1pub mod reproducible;
5
6use std::{path::PathBuf, process::Stdio};
7
8use cargo_metadata::MetadataCommand;
9use escargot::Cargo;
10
11use crate::core::project::contract::Contract;
12
13const WASM_TARGET: &str = "wasm32-unknown-unknown";
14const OPT_LEVEL_Z_CONFIG: &str = "profile.release.opt-level='z'";
15const UNSTABLE_FLAGS: &[&str] = &[
16 "build-std=std,panic_abort",
17 "build-std-features=panic_immediate_abort",
18];
19
20#[derive(Clone, Debug, Default)]
21pub struct BuildConfig {
22 pub opt_level: OptLevel,
23 pub features: Vec<String>,
24}
25
26#[derive(Clone, Debug, Default)]
27pub enum OptLevel {
28 #[default]
29 S,
30 Z,
31}
32
33#[derive(Debug, thiserror::Error)]
34pub enum BuildError {
35 #[error("io error: {0}")]
36 Io(#[from] std::io::Error),
37
38 #[error("cargo error: {0}")]
39 Cargo(#[from] escargot::error::CargoError),
40 #[error("cargo metadata error: {0}")]
41 CargoMetadata(#[from] cargo_metadata::Error),
42
43 #[error("{0}")]
44 Toolchain(#[from] crate::utils::toolchain::ToolchainError),
45
46 #[error("build did not generate wasm file")]
47 NoWasmFound,
48 #[error("failed to execute cargo build")]
49 FailedToExecute,
50 #[error("cargo build command failed")]
51 CargoBuildFailed,
52}
53
54pub fn build_contract(contract: &Contract, config: &BuildConfig) -> Result<PathBuf, BuildError> {
55 info!(@grey, "Building project with Cargo.toml version: {}", contract.version());
56
57 let mut cmd = Cargo::new()
58 .args(["build", "--lib", "--locked", "--release"])
59 .args(["--target", WASM_TARGET]);
60 if !config.features.is_empty() {
61 cmd = cmd.args(["--features", &config.features.join(" ")]);
62 }
63 if !contract.stable() {
64 cmd = cmd.args(UNSTABLE_FLAGS.iter().flat_map(|flag| ["-Z", flag]));
65 }
66 if matches!(config.opt_level, OptLevel::Z) {
67 cmd = cmd.args(["--config", OPT_LEVEL_Z_CONFIG]);
68 }
69
70 let status = cmd
71 .into_command()
72 .stdout(Stdio::inherit())
73 .stderr(Stdio::inherit())
74 .status()
75 .map_err(|_| BuildError::FailedToExecute)?;
76 if !status.success() {
77 return Err(BuildError::CargoBuildFailed);
78 }
79
80 let metadata = MetadataCommand::new().exec()?;
81 let wasm_path = metadata
82 .target_directory
83 .join(WASM_TARGET)
84 .join("release")
85 .join("deps")
86 .join(format!("{}.wasm", contract.name()));
87 if !wasm_path.exists() {
88 return Err(BuildError::NoWasmFound);
89 }
90
91 Ok(wasm_path.into())
92}