stylus_tools/core/build/
mod.rs

1// Copyright 2025, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4pub 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}