ab_contracts_tooling/
build.rs

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