cargo_openvm/commands/
build.rs

1use std::{
2    fs::{read, write},
3    path::PathBuf,
4};
5
6use clap::Parser;
7use eyre::Result;
8use openvm_build::{
9    build_guest_package, find_unique_executable, get_package, GuestOptions, TargetFilter,
10};
11use openvm_sdk::{
12    commit::{commit_app_exe, committed_exe_as_bn254},
13    fs::write_exe_to_file,
14    Sdk,
15};
16use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE};
17
18use crate::{
19    default::{
20        DEFAULT_APP_CONFIG_PATH, DEFAULT_APP_EXE_PATH, DEFAULT_COMMITTED_APP_EXE_PATH,
21        DEFAULT_MANIFEST_DIR,
22    },
23    util::read_config_toml_or_default,
24};
25
26#[derive(Parser)]
27#[command(name = "build", about = "Compile an OpenVM program")]
28pub struct BuildCmd {
29    #[clap(flatten)]
30    build_args: BuildArgs,
31}
32
33impl BuildCmd {
34    pub fn run(&self) -> Result<()> {
35        build(&self.build_args)?;
36        Ok(())
37    }
38}
39
40#[derive(Clone, Parser)]
41pub struct BuildArgs {
42    #[arg(
43        long,
44        help = "Path to the directory containing the Cargo.toml file for the guest code (relative to the current directory)",
45        default_value = DEFAULT_MANIFEST_DIR
46    )]
47    pub manifest_dir: PathBuf,
48
49    #[arg(long, help = "Path to the target directory")]
50    pub target_dir: Option<PathBuf>,
51
52    #[arg(long, value_delimiter = ',', help = "Feature flags passed to cargo")]
53    pub features: Vec<String>,
54
55    #[clap(flatten, help = "Filter the target to build")]
56    pub bin_type_filter: BinTypeFilter,
57
58    #[arg(
59        long,
60        default_value = "false",
61        help = "Skips transpilation into exe when set"
62    )]
63    pub no_transpile: bool,
64
65    #[arg(
66        long,
67        default_value = DEFAULT_APP_CONFIG_PATH,
68        help = "Path to the SDK config .toml file that specifies the transpiler extensions"
69    )]
70    pub config: PathBuf,
71
72    #[arg(
73        long,
74        default_value = DEFAULT_APP_EXE_PATH,
75        help = "Output path for the transpiled program"
76    )]
77    pub exe_output: PathBuf,
78
79    #[arg(
80        long,
81        default_value = DEFAULT_COMMITTED_APP_EXE_PATH,
82        help = "Output path for the committed program"
83    )]
84    pub committed_exe_output: PathBuf,
85
86    #[arg(long, default_value = "release", help = "Build profile")]
87    pub profile: String,
88}
89
90#[derive(Clone, Default, clap::Args)]
91#[group(required = false, multiple = false)]
92pub struct BinTypeFilter {
93    #[arg(long, help = "Specifies the bin target to build")]
94    pub bin: Option<String>,
95
96    #[arg(long, help = "Specifies the example target to build")]
97    pub example: Option<String>,
98}
99
100// Returns the path to the ELF file if it is unique.
101pub(crate) fn build(build_args: &BuildArgs) -> Result<Option<PathBuf>> {
102    println!("[openvm] Building the package...");
103    let target_filter = if let Some(bin) = &build_args.bin_type_filter.bin {
104        Some(TargetFilter {
105            name: bin.clone(),
106            kind: "bin".to_string(),
107        })
108    } else {
109        build_args
110            .bin_type_filter
111            .example
112            .as_ref()
113            .map(|example| TargetFilter {
114                name: example.clone(),
115                kind: "example".to_string(),
116            })
117    };
118    let mut guest_options = GuestOptions::default()
119        .with_features(build_args.features.clone())
120        .with_profile(build_args.profile.clone());
121    guest_options.target_dir = build_args.target_dir.clone();
122
123    let pkg = get_package(&build_args.manifest_dir);
124    // We support builds of libraries with 0 or >1 executables.
125    let elf_path = match build_guest_package(&pkg, &guest_options, None, &target_filter) {
126        Ok(target_dir) => {
127            find_unique_executable(&build_args.manifest_dir, &target_dir, &target_filter)
128        }
129        Err(None) => {
130            return Err(eyre::eyre!("Failed to build guest"));
131        }
132        Err(Some(code)) => {
133            return Err(eyre::eyre!("Failed to build guest: code = {}", code));
134        }
135    };
136
137    if !build_args.no_transpile {
138        let elf_path = elf_path?;
139        println!("[openvm] Transpiling the package...");
140        let output_path = &build_args.exe_output;
141        let app_config = read_config_toml_or_default(&build_args.config)?;
142        let transpiler = app_config.app_vm_config.transpiler();
143
144        let data = read(elf_path.clone())?;
145        let elf = Elf::decode(&data, MEM_SIZE as u32)?;
146        let exe = Sdk::new().transpile(elf, transpiler)?;
147        let committed_exe = commit_app_exe(app_config.app_fri_params.fri_params, exe.clone());
148        write_exe_to_file(exe, output_path)?;
149        write(
150            &build_args.committed_exe_output,
151            committed_exe_as_bn254(&committed_exe).value.to_bytes(),
152        )?;
153
154        println!(
155            "[openvm] Successfully transpiled to {}",
156            output_path.display()
157        );
158        Ok(Some(elf_path))
159    } else if let Ok(elf_path) = elf_path {
160        println!(
161            "[openvm] Successfully built the package: {}",
162            elf_path.display()
163        );
164        Ok(Some(elf_path))
165    } else {
166        println!("[openvm] Successfully built the package");
167        Ok(None)
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use std::path::PathBuf;
174
175    use eyre::Result;
176    use openvm_build::RUSTC_TARGET;
177
178    use super::*;
179
180    #[test]
181    fn test_build_with_profile() -> Result<()> {
182        let temp_dir = tempfile::tempdir()?;
183        let target_dir = temp_dir.path();
184        let build_args = BuildArgs {
185            manifest_dir: PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("example"),
186            features: vec![],
187            bin_type_filter: Default::default(),
188            no_transpile: true,
189            config: PathBuf::from(DEFAULT_APP_CONFIG_PATH),
190            exe_output: PathBuf::from(DEFAULT_APP_EXE_PATH),
191            committed_exe_output: PathBuf::from(DEFAULT_COMMITTED_APP_EXE_PATH),
192            profile: "dev".to_string(),
193            target_dir: Some(target_dir.to_path_buf()),
194        };
195        build(&build_args)?;
196        assert!(
197            target_dir.join(RUSTC_TARGET).join("debug").exists(),
198            "did not build with dev profile"
199        );
200        temp_dir.close()?;
201        Ok(())
202    }
203}