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
100pub(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 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}