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