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        .env("RUSTFLAGS", "--cfg sha2_backend=\"riscv-zknh-compact\"")
45        .args([
46            "rustc",
47            "-Z",
48            "build-std=core",
49            "--crate-type",
50            "cdylib",
51            "--target",
52            target_specification_path
53                .to_str()
54                .context("Path to target specification file is not valid UTF-8")?,
55        ]);
56
57    if env::var("MIRI_SYSROOT").is_ok() {
58        command_builder
59            .env_remove("RUSTC")
60            .env_remove("RUSTC_WRAPPER");
61    }
62
63    if let Some(package) = package {
64        command_builder.args([
65            "--package",
66            package,
67            "--features",
68            &format!("{package}/guest"),
69        ]);
70    } else {
71        command_builder.args(["--features", "guest"]);
72    }
73    if let Some(features) = features {
74        command_builder.args(["--features", features]);
75    }
76
77    command_builder.args(["--profile", profile]);
78
79    let metadata = MetadataCommand::new()
80        .exec()
81        .context("Failed to fetch cargo metadata")?;
82
83    let target_directory = if let Some(target_dir) = target_dir {
84        command_builder.args([
85            "--target-dir",
86            target_dir
87                .to_str()
88                .context("Path to target directory is not valid UTF-8")?,
89        ]);
90        target_dir
91    } else {
92        metadata.target_directory.as_std_path()
93    };
94
95    let cdylib_path = target_directory
96        .join(TARGET_SPECIFICATION_NAME)
97        .join(if profile == "dev" { "debug" } else { "release" })
98        .join({
99            let package_name = if let Some(package) = package {
100                package
101            } else {
102                let current_dir = env::current_dir().context("Failed to get current directory")?;
103                let current_manifest = current_dir.join("Cargo.toml");
104                metadata
105                    .packages
106                    .iter()
107                    .find_map(|package| {
108                        if package.manifest_path == current_manifest {
109                            Some(&package.name)
110                        } else {
111                            None
112                        }
113                    })
114                    .context("Failed to find package name")?
115            };
116
117            format!("{}.contract.so", package_name.replace('-', "_"))
118        });
119
120    debug!(
121        ?package,
122        ?features,
123        ?profile,
124        ?target_specification_path,
125        cdylib_path = ?cdylib_path,
126        command = ?command_builder,
127        "Building ELF `cdylib` contract"
128    );
129
130    let status = command_builder
131        .status()
132        .context("Failed to build a contract")?;
133
134    if !status.success() {
135        return Err(anyhow::anyhow!("Failed to build a contract"));
136    }
137
138    Ok(cdylib_path)
139}