Skip to main content

ab_contracts_tooling/
build.rs

1//! Build an ELF `cdylib` with the contract
2
3use crate::target_specification::TARGET_SPECIFICATION_NAME;
4use anyhow::Context;
5use cargo_metadata::MetadataCommand;
6use std::env;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use tracing::debug;
10
11/// Options for building a contract
12#[derive(Debug)]
13pub struct BuildOptions<'a> {
14    /// Package to build.
15    ///
16    /// A package in the current directory is built if not specified explicitly.
17    pub package: Option<&'a str>,
18    /// Comma separated list of features to activate
19    pub features: Option<&'a str>,
20    /// Build artifacts with the specified profile
21    pub profile: &'a str,
22    /// Path to the target specification JSON file
23    pub target_specification_path: &'a Path,
24    /// Custom target directory to use instead of the default one
25    pub target_dir: Option<&'a Path>,
26}
27
28/// Build a `cdylib` with the contract and return the path to the resulting ELF file
29pub fn build_cdylib(options: BuildOptions<'_>) -> anyhow::Result<PathBuf> {
30    let BuildOptions {
31        package,
32        features,
33        profile,
34        target_specification_path,
35        target_dir,
36    } = options;
37
38    let mut command_builder = Command::new("cargo");
39    command_builder
40        .env_remove("RUSTFLAGS")
41        .env_remove("CARGO_ENCODED_RUSTFLAGS")
42        // Hack for enabling RISC-V Zknh backend in `sha2` crate since it is a nightly-only feature,
43        // and they really don't like using normal features for it.
44        // `-Zthreads=4` takes advantage of the parallel frontend
45        .env(
46            "RUSTFLAGS",
47            r#"--cfg sha2_backend="riscv-zknh" --cfg sha2_backend_riscv_zknh="compact"
48            -Zthreads=4"#,
49        )
50        .args([
51            "rustc",
52            "-Zbuild-std=core",
53            "--crate-type",
54            "cdylib",
55            "-Zjson-target-spec",
56            "--target",
57            target_specification_path
58                .to_str()
59                .context("Path to target specification file is not valid UTF-8")?,
60        ]);
61
62    if env::var("MIRI_SYSROOT").is_ok() {
63        command_builder
64            .env_remove("RUSTC")
65            .env_remove("RUSTC_WRAPPER");
66    }
67
68    if let Some(package) = package {
69        command_builder.args([
70            "--package",
71            package,
72            "--features",
73            &format!("{package}/guest"),
74        ]);
75    } else {
76        command_builder.args(["--features", "guest"]);
77    }
78    if let Some(features) = features {
79        command_builder.args(["--features", features]);
80    }
81
82    command_builder.args(["--profile", profile]);
83
84    let metadata = MetadataCommand::new()
85        .exec()
86        .context("Failed to fetch cargo metadata")?;
87
88    let target_directory = if let Some(target_dir) = target_dir {
89        command_builder.args([
90            "--target-dir",
91            target_dir
92                .to_str()
93                .context("Path to target directory is not valid UTF-8")?,
94        ]);
95        target_dir
96    } else {
97        metadata.target_directory.as_std_path()
98    };
99
100    let cdylib_path = target_directory
101        .join(TARGET_SPECIFICATION_NAME)
102        .join(if profile == "dev" { "debug" } else { profile })
103        .join({
104            let package_name = if let Some(package) = package {
105                package
106            } else {
107                let current_dir = env::current_dir().context("Failed to get current directory")?;
108                let current_manifest = current_dir.join("Cargo.toml");
109                metadata
110                    .packages
111                    .iter()
112                    .find_map(|package| {
113                        if package.manifest_path == current_manifest {
114                            Some(&package.name)
115                        } else {
116                            None
117                        }
118                    })
119                    .context("Failed to find package name")?
120            };
121
122            format!("{}.contract.so", package_name.replace('-', "_"))
123        });
124
125    debug!(
126        ?package,
127        ?features,
128        ?profile,
129        ?target_specification_path,
130        cdylib_path = ?cdylib_path,
131        command = ?command_builder,
132        "Building ELF `cdylib` contract"
133    );
134
135    let status = command_builder
136        .status()
137        .context("Failed to build a contract")?;
138
139    if !status.success() {
140        return Err(anyhow::anyhow!("Failed to build a contract"));
141    }
142
143    Ok(cdylib_path)
144}