1use super::*;
18
19use leo_ast::{NetworkName, Stub};
20use leo_compiler::{AstSnapshots, Compiler, CompilerOptions};
21use leo_errors::{CliError, UtilError};
22use leo_package::{Manifest, Package};
23use leo_span::Symbol;
24
25use snarkvm::prelude::{CanaryV0, Itertools, MainnetV0, Program, TestnetV0};
26
27use indexmap::IndexMap;
28use std::path::Path;
29
30impl From<BuildOptions> for CompilerOptions {
31 fn from(options: BuildOptions) -> Self {
32 Self {
33 ast_spans_enabled: options.enable_ast_spans,
34 ast_snapshots: if options.enable_all_ast_snapshots {
35 AstSnapshots::All
36 } else {
37 AstSnapshots::Some(options.ast_snapshots.into_iter().collect())
38 },
39 initial_ast: options.enable_all_ast_snapshots | options.enable_initial_ast_snapshot,
40 }
41 }
42}
43
44#[derive(Parser, Debug)]
46pub struct LeoBuild {
47 #[clap(flatten)]
48 pub(crate) options: BuildOptions,
49 #[clap(flatten)]
50 pub(crate) env_override: EnvOptions,
51}
52
53impl Command for LeoBuild {
54 type Input = ();
55 type Output = Package;
56
57 fn log_span(&self) -> Span {
58 tracing::span!(tracing::Level::INFO, "Leo")
59 }
60
61 fn prelude(&self, _: Context) -> Result<Self::Input> {
62 Ok(())
63 }
64
65 fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
66 handle_build(&self, context)
68 }
69}
70
71fn handle_build(command: &LeoBuild, context: Context) -> Result<<LeoBuild as Command>::Output> {
73 let package_path = context.dir()?;
75 let home_path = context.home()?;
76
77 let network = match get_network(&command.env_override.network) {
79 Ok(network) => network,
80 Err(_) => {
81 println!("⚠️ No network specified, defaulting to 'testnet'.");
82 NetworkName::TestnetV0
83 }
84 };
85
86 let endpoint = get_endpoint(&command.env_override.endpoint).ok();
88
89 let package = if command.options.build_tests {
90 Package::from_directory_with_tests(
91 &package_path,
92 &home_path,
93 command.options.no_cache,
94 command.options.no_local,
95 Some(network),
96 endpoint.as_deref(),
97 )?
98 } else {
99 Package::from_directory(
100 &package_path,
101 &home_path,
102 command.options.no_cache,
103 command.options.no_local,
104 Some(network),
105 endpoint.as_deref(),
106 )?
107 };
108
109 if package.manifest.leo != env!("CARGO_PKG_VERSION") {
112 tracing::warn!(
113 "The Leo compiler version in the manifest ({}) does not match the current version ({}).",
114 package.manifest.leo,
115 env!("CARGO_PKG_VERSION")
116 );
117 }
118
119 let outputs_directory = package.outputs_directory();
120 let build_directory = package.build_directory();
121 let imports_directory = package.imports_directory();
122 let source_directory = package.source_directory();
123 let main_source_path = source_directory.join("main.leo");
124
125 for dir in [&outputs_directory, &build_directory, &imports_directory] {
126 std::fs::create_dir_all(dir).map_err(|err| {
127 UtilError::util_file_io_error(format_args!("Couldn't create directory {}", dir.display()), err)
128 })?;
129 }
130
131 let handler = Handler::default();
133
134 let mut stubs: IndexMap<Symbol, Stub> = IndexMap::new();
135
136 for program in package.programs.iter() {
137 let (bytecode, build_path) = match &program.data {
138 leo_package::ProgramData::Bytecode(bytecode) => {
139 (bytecode.clone(), imports_directory.join(format!("{}.aleo", program.name)))
141 }
142 leo_package::ProgramData::SourcePath { directory, source } => {
143 let build_path = if source == &main_source_path {
145 build_directory.join("main.aleo")
146 } else {
147 imports_directory.join(format!("{}.aleo", program.name))
148 };
149 let source_dir = directory.join("src");
151 let bytecode = compile_leo_source_directory(
152 source, &source_dir,
154 program.name,
155 program.is_test,
156 &outputs_directory,
157 &handler,
158 command.options.clone(),
159 stubs.clone(),
160 network,
161 )?;
162 (bytecode, build_path)
163 }
164 };
165
166 std::fs::write(build_path, &bytecode).map_err(CliError::failed_to_load_instructions)?;
168
169 let stub = match network {
171 NetworkName::MainnetV0 => leo_disassembler::disassemble_from_str::<MainnetV0>(program.name, &bytecode),
172 NetworkName::TestnetV0 => leo_disassembler::disassemble_from_str::<TestnetV0>(program.name, &bytecode),
173 NetworkName::CanaryV0 => leo_disassembler::disassemble_from_str::<CanaryV0>(program.name, &bytecode),
174 }?;
175 stubs.insert(program.name, stub);
176 }
177
178 let build_manifest_path = build_directory.join(leo_package::MANIFEST_FILENAME);
181 let fake_manifest = Manifest {
182 program: package.manifest.program.clone(),
183 version: "0.1.0".to_string(),
184 description: String::new(),
185 license: String::new(),
186 leo: env!("CARGO_PKG_VERSION").to_string(),
187 dependencies: None,
188 dev_dependencies: None,
189 };
190 fake_manifest.write_to_file(build_manifest_path)?;
191
192 Ok(package)
193}
194
195#[allow(clippy::too_many_arguments)]
197fn compile_leo_source_directory(
198 entry_file_path: &Path,
199 source_directory: &Path,
200 program_name: Symbol,
201 is_test: bool,
202 output_path: &Path,
203 handler: &Handler,
204 options: BuildOptions,
205 stubs: IndexMap<Symbol, Stub>,
206 network: NetworkName,
207) -> Result<String> {
208 let mut compiler = Compiler::new(
210 Some(program_name.to_string()),
211 is_test,
212 handler.clone(),
213 output_path.to_path_buf(),
214 Some(options.into()),
215 stubs,
216 network,
217 );
218
219 let bytecode = compiler.compile_from_directory(entry_file_path, source_directory)?;
221
222 use leo_package::MAX_PROGRAM_SIZE;
224 let program_size = bytecode.len();
225
226 if program_size > MAX_PROGRAM_SIZE {
227 return Err(leo_errors::LeoError::UtilError(UtilError::program_size_limit_exceeded(
228 program_name,
229 program_size,
230 MAX_PROGRAM_SIZE,
231 )));
232 }
233
234 let checksum: String = match network {
236 NetworkName::MainnetV0 => Program::<MainnetV0>::from_str(&bytecode)?.to_checksum().iter().join(", "),
237 NetworkName::TestnetV0 => Program::<TestnetV0>::from_str(&bytecode)?.to_checksum().iter().join(", "),
238 NetworkName::CanaryV0 => Program::<CanaryV0>::from_str(&bytecode)?.to_checksum().iter().join(", "),
239 };
240
241 tracing::info!(" {} statements before dead code elimination.", compiler.statements_before_dce);
242 tracing::info!(" {} statements after dead code elimination.", compiler.statements_after_dce);
243 tracing::info!(" The program checksum is: '[{checksum}]'.");
244
245 tracing::info!("✅ Compiled '{program_name}.aleo' into Aleo instructions.");
246 Ok(bytecode)
247}