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 = match get_endpoint(&command.env_override.endpoint) {
88 Ok(endpoint) => endpoint,
89 Err(_) => {
90 println!("⚠️ No endpoint specified, defaulting to '{}'.", DEFAULT_ENDPOINT);
91 DEFAULT_ENDPOINT.to_string()
92 }
93 };
94
95 let package = if command.options.build_tests {
96 Package::from_directory_with_tests(
97 &package_path,
98 &home_path,
99 command.options.no_cache,
100 command.options.no_local,
101 Some(network),
102 Some(&endpoint),
103 )?
104 } else {
105 Package::from_directory(
106 &package_path,
107 &home_path,
108 command.options.no_cache,
109 command.options.no_local,
110 Some(network),
111 Some(&endpoint),
112 )?
113 };
114
115 if package.manifest.leo != env!("CARGO_PKG_VERSION") {
118 tracing::warn!(
119 "The Leo compiler version in the manifest ({}) does not match the current version ({}).",
120 package.manifest.leo,
121 env!("CARGO_PKG_VERSION")
122 );
123 }
124
125 let outputs_directory = package.outputs_directory();
126 let build_directory = package.build_directory();
127 let imports_directory = package.imports_directory();
128 let source_directory = package.source_directory();
129 let main_source_path = source_directory.join("main.leo");
130
131 for dir in [&outputs_directory, &build_directory, &imports_directory] {
132 std::fs::create_dir_all(dir).map_err(|err| {
133 UtilError::util_file_io_error(format_args!("Couldn't create directory {}", dir.display()), err)
134 })?;
135 }
136
137 let handler = Handler::default();
139
140 let mut stubs: IndexMap<Symbol, Stub> = IndexMap::new();
141
142 for program in package.programs.iter() {
143 let (bytecode, build_path) = match &program.data {
144 leo_package::ProgramData::Bytecode(bytecode) => {
145 (bytecode.clone(), imports_directory.join(format!("{}.aleo", program.name)))
147 }
148 leo_package::ProgramData::SourcePath { directory, source } => {
149 let build_path = if source == &main_source_path {
151 build_directory.join("main.aleo")
152 } else {
153 imports_directory.join(format!("{}.aleo", program.name))
154 };
155 let source_dir = directory.join("src");
157 let bytecode = compile_leo_source_directory(
158 source, &source_dir,
160 program.name,
161 program.is_test,
162 &outputs_directory,
163 &handler,
164 command.options.clone(),
165 stubs.clone(),
166 network,
167 )?;
168 (bytecode, build_path)
169 }
170 };
171
172 std::fs::write(build_path, &bytecode).map_err(CliError::failed_to_load_instructions)?;
174
175 let stub = match network {
177 NetworkName::MainnetV0 => leo_disassembler::disassemble_from_str::<MainnetV0>(program.name, &bytecode),
178 NetworkName::TestnetV0 => leo_disassembler::disassemble_from_str::<TestnetV0>(program.name, &bytecode),
179 NetworkName::CanaryV0 => leo_disassembler::disassemble_from_str::<CanaryV0>(program.name, &bytecode),
180 }?;
181 stubs.insert(program.name, stub);
182 }
183
184 let build_manifest_path = build_directory.join(leo_package::MANIFEST_FILENAME);
187 let fake_manifest = Manifest {
188 program: package.manifest.program.clone(),
189 version: "0.1.0".to_string(),
190 description: String::new(),
191 license: String::new(),
192 leo: env!("CARGO_PKG_VERSION").to_string(),
193 dependencies: None,
194 dev_dependencies: None,
195 };
196 fake_manifest.write_to_file(build_manifest_path)?;
197
198 Ok(package)
199}
200
201#[allow(clippy::too_many_arguments)]
203fn compile_leo_source_directory(
204 entry_file_path: &Path,
205 source_directory: &Path,
206 program_name: Symbol,
207 is_test: bool,
208 output_path: &Path,
209 handler: &Handler,
210 options: BuildOptions,
211 stubs: IndexMap<Symbol, Stub>,
212 network: NetworkName,
213) -> Result<String> {
214 let mut compiler = Compiler::new(
216 Some(program_name.to_string()),
217 is_test,
218 handler.clone(),
219 output_path.to_path_buf(),
220 Some(options.into()),
221 stubs,
222 network,
223 );
224
225 let bytecode = compiler.compile_from_directory(entry_file_path, source_directory)?;
227
228 use leo_package::MAX_PROGRAM_SIZE;
230 let program_size = bytecode.len();
231
232 if program_size > MAX_PROGRAM_SIZE {
233 return Err(leo_errors::LeoError::UtilError(UtilError::program_size_limit_exceeded(
234 program_name,
235 program_size,
236 MAX_PROGRAM_SIZE,
237 )));
238 }
239
240 let checksum: String = match network {
242 NetworkName::MainnetV0 => Program::<MainnetV0>::from_str(&bytecode)?.to_checksum().iter().join(", "),
243 NetworkName::TestnetV0 => Program::<TestnetV0>::from_str(&bytecode)?.to_checksum().iter().join(", "),
244 NetworkName::CanaryV0 => Program::<CanaryV0>::from_str(&bytecode)?.to_checksum().iter().join(", "),
245 };
246
247 tracing::info!(" {} statements before dead code elimination.", compiler.statements_before_dce);
248 tracing::info!(" {} statements after dead code elimination.", compiler.statements_after_dce);
249 tracing::info!(" The program checksum is: '[{checksum}]'.");
250
251 tracing::info!("✅ Compiled '{program_name}.aleo' into Aleo instructions.");
252 Ok(bytecode)
253}