1use super::*;
18
19use leo_ast::{NetworkName, NodeBuilder, Program, Stub, TypeInterner};
20use leo_compiler::{Compiled, Compiler, CompilerOptions};
21use leo_package::{ABI_FILENAME, Package};
22use leo_span::Symbol;
23
24use snarkvm::prelude::{CanaryV0, MainnetV0, Process as SvmProcess, Program as SvmProgram, TestnetV0};
25
26use indexmap::IndexMap;
27use itertools::Itertools;
28use std::{
29 collections::HashSet,
30 path::{Path, PathBuf},
31 rc::Rc,
32};
33
34enum DisassembleProcess {
40 Mainnet(SvmProcess<MainnetV0>),
41 Testnet(SvmProcess<TestnetV0>),
42 Canary(SvmProcess<CanaryV0>),
43}
44
45struct ProgramForValidation {
47 bytecode: String,
49 path: PathBuf,
51 is_leo_compiled: bool,
53}
54
55impl From<BuildOptions> for CompilerOptions {
56 fn from(options: BuildOptions) -> Self {
57 Self { no_std: options.no_std }
58 }
59}
60
61#[derive(Parser, Debug)]
63pub struct LeoBuild {
64 #[clap(flatten)]
65 pub(crate) options: BuildOptions,
66 #[clap(flatten)]
67 pub(crate) env_override: EnvOptions,
68 #[clap(skip)]
71 pub(crate) rename: Option<String>,
72}
73
74impl Command for LeoBuild {
75 type Input = ();
76 type Output = Package;
77
78 fn log_span(&self) -> Span {
79 tracing::span!(tracing::Level::INFO, "Leo")
80 }
81
82 fn prelude(&self, _: Context) -> Result<Self::Input> {
83 Ok(())
84 }
85
86 fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
87 match context.resolve_targets()? {
88 Some((_, targets)) => {
89 let mut last_package = None;
90 for target in &targets {
91 let member_name = target.file_name().and_then(|n| n.to_str()).unwrap_or("?");
92 if targets.len() > 1 {
93 println!("\n--- workspace member '{member_name}' ---");
94 }
95 let member_ctx = context.with_path(target.clone());
96 last_package = Some(handle_build(&self, member_ctx)?);
97 }
98 last_package.ok_or_else(|| crate::errors::custom("No workspace members found.").into())
99 }
100 None => handle_build(&self, context),
101 }
102 }
103}
104
105fn handle_build(command: &LeoBuild, context: Context) -> Result<<LeoBuild as Command>::Output> {
107 let package_path = context.dir()?;
109 let home_path = context.home()?;
110
111 let network = match get_network(&command.env_override.network) {
113 Ok(network) => network,
114 Err(_) => {
115 println!("⚠️ No network specified, defaulting to 'testnet'.");
116 NetworkName::TestnetV0
117 }
118 };
119
120 let endpoint = match get_endpoint(&command.env_override.endpoint) {
122 Ok(endpoint) => endpoint,
123 Err(_) => {
124 println!("⚠️ No endpoint specified, defaulting to '{}'.", DEFAULT_ENDPOINT);
125 DEFAULT_ENDPOINT.to_string()
126 }
127 };
128
129 let mut package = if command.options.build_tests {
130 Package::from_directory_with_tests(
131 &package_path,
132 &home_path,
133 command.options.no_cache,
134 command.options.no_local,
135 command.options.offline,
136 Some(network),
137 Some(&endpoint),
138 command.env_override.network_retries,
139 )?
140 } else {
141 Package::from_directory(
142 &package_path,
143 &home_path,
144 command.options.no_cache,
145 command.options.no_local,
146 command.options.offline,
147 Some(network),
148 Some(&endpoint),
149 command.env_override.network_retries,
150 )?
151 };
152
153 if package.manifest.leo != env!("CARGO_PKG_VERSION") {
156 tracing::warn!(
157 "The Leo compiler version in the manifest ({}) does not match the current version ({}).",
158 package.manifest.leo,
159 env!("CARGO_PKG_VERSION")
160 );
161 }
162
163 let build_directory = package.build_directory();
164 let source_directory = package.source_directory();
165 let main_source_path = source_directory.join("main.leo");
166
167 let primary_name = package.primary_unit().map(|p| p.name);
169
170 let rename_target = apply_rename(command, &mut package, primary_name)?;
172
173 std::fs::create_dir_all(&build_directory).map_err(|err| {
174 crate::errors::util_file_io_error(format_args!("Couldn't create directory {}", build_directory.display()), err)
175 })?;
176 remove_legacy_build_artifacts(&build_directory);
179
180 let handler = Handler::default();
182 let node_builder = Rc::new(NodeBuilder::default());
183
184 let mut build_options = command.options.clone();
187 build_options.no_std = package.manifest.no_std;
188
189 let mut stubs: IndexMap<Symbol, Stub> = IndexMap::new();
190
191 if !build_options.no_std {
195 let std_stub = Compiler::build_std_stub(
196 handler.clone(),
197 Rc::clone(&node_builder),
198 network,
199 Rc::new(TypeInterner::default()),
200 )?;
201 stubs.insert(Symbol::intern(leo_std::library_name()), std_stub);
202 }
203
204 let mut compiled_programs: IndexMap<String, ProgramForValidation> = IndexMap::new();
207
208 let mut written: HashSet<String> = HashSet::new();
213
214 let mut disassemble_process = match network {
221 NetworkName::MainnetV0 => DisassembleProcess::Mainnet(SvmProcess::<MainnetV0>::load().map_err(|e| {
222 crate::errors::custom(format!("Failed to initialize snarkVM process for disassembler validation: {e}"))
223 })?),
224 NetworkName::TestnetV0 => DisassembleProcess::Testnet(SvmProcess::<TestnetV0>::load().map_err(|e| {
225 crate::errors::custom(format!("Failed to initialize snarkVM process for disassembler validation: {e}"))
226 })?),
227 NetworkName::CanaryV0 => DisassembleProcess::Canary(SvmProcess::<CanaryV0>::load().map_err(|e| {
228 crate::errors::custom(format!("Failed to initialize snarkVM process for disassembler validation: {e}"))
229 })?),
230 };
231
232 for unit in &package.compilation_units {
233 let unit_name = unit.name.to_string();
234 let unit_key = leo_package::bare_unit_name(&unit_name).to_string();
236 match &unit.data {
237 leo_package::ProgramData::Bytecode(bytecode) => {
238 let build_path = package.unit_bytecode_path(&unit_name);
240
241 if written.insert(unit_key.clone()) {
243 ensure_parent_dir(&build_path)?;
244 std::fs::write(&build_path, bytecode).map_err(crate::errors::failed_to_load_instructions)?;
245 }
246
247 let stub = match &mut disassemble_process {
250 DisassembleProcess::Mainnet(p) => {
251 leo_disassembler::disassemble_from_str::<MainnetV0>(unit.name, bytecode, p)
252 }
253 DisassembleProcess::Testnet(p) => {
254 leo_disassembler::disassemble_from_str::<TestnetV0>(unit.name, bytecode, p)
255 }
256 DisassembleProcess::Canary(p) => {
257 leo_disassembler::disassemble_from_str::<CanaryV0>(unit.name, bytecode, p)
258 }
259 }?;
260
261 stubs.insert(unit.name, stub.into());
262
263 compiled_programs.entry(unit_key.clone()).or_insert(ProgramForValidation {
264 bytecode: bytecode.clone(),
265 path: build_path,
266 is_leo_compiled: false,
267 });
268 }
269
270 leo_package::ProgramData::SourcePath { directory, source } => {
271 let source_dir = if unit.kind.is_test() {
273 source
274 .parent()
275 .ok_or_else(|| {
276 crate::errors::failed_to_open_file(format_args!(
277 "Failed to find directory for test {}",
278 source.display()
279 ))
280 })?
281 .to_path_buf()
282 } else {
283 directory.join("src")
284 };
285
286 let is_main = source == &main_source_path;
287 if is_main || unit.kind.is_test() {
288 let compiled = compile_leo_source_directory(
290 source, &source_dir,
292 unit.name,
293 unit.kind.is_test(),
294 &handler,
295 &node_builder,
296 build_options.clone(),
297 stubs.clone(),
298 network,
299 if is_main { rename_target.clone() } else { None },
300 )?;
301
302 let primary_path = package.unit_bytecode_path(&unit_name);
305 if written.insert(unit_key.clone()) {
306 ensure_parent_dir(&primary_path)?;
307 std::fs::write(&primary_path, &compiled.primary.bytecode)
308 .map_err(crate::errors::failed_to_load_instructions)?;
309 if is_main {
310 let abi_path = package.unit_abi_path(&unit_name);
311 let abi_json = serde_json::to_string_pretty(&compiled.primary.abi)
312 .map_err(|e| crate::errors::failed_to_serialize_abi(e.to_string()))?;
313 std::fs::write(&abi_path, abi_json).map_err(crate::errors::failed_to_write_abi)?;
314 tracing::info!("✅ Generated ABI for program '{unit_name}'.");
315 let interfaces_directory = package.unit_interfaces_directory(&unit_name);
316 write_interface_abis(&interfaces_directory, &compiled.interfaces)?;
317 }
318 }
319
320 for import in &compiled.imports {
322 let import_path = package.unit_bytecode_path(&import.name);
323 let import_key = leo_package::bare_unit_name(&import.name).to_string();
324 if written.insert(import_key.clone()) {
325 ensure_parent_dir(&import_path)?;
326 std::fs::write(&import_path, &import.bytecode)
327 .map_err(crate::errors::failed_to_load_instructions)?;
328
329 let import_abi_path = package.unit_abi_path(&import.name);
330 let import_abi_json = serde_json::to_string_pretty(&import.abi)
331 .map_err(|e| crate::errors::failed_to_serialize_abi(e.to_string()))?;
332 std::fs::write(&import_abi_path, import_abi_json)
333 .map_err(crate::errors::failed_to_write_abi)?;
334 }
335
336 compiled_programs.entry(import_key).or_insert(ProgramForValidation {
338 bytecode: import.bytecode.clone(),
339 path: import_path,
340 is_leo_compiled: true,
341 });
342 }
343 compiled_programs.entry(unit_key.clone()).or_insert(ProgramForValidation {
345 bytecode: compiled.primary.bytecode.clone(),
346 path: primary_path,
347 is_leo_compiled: true,
348 });
349 }
350
351 if unit.kind.is_library() {
352 let library = if primary_name == Some(unit.name) {
358 let (lib, interfaces) = build_leo_source_directory_library(
359 source,
360 &source_dir,
361 unit.name,
362 &handler,
363 &node_builder,
364 build_options.clone(),
365 stubs.clone(),
366 network,
367 )?;
368
369 let interfaces_directory = package.unit_interfaces_directory(&unit_name);
371 write_interface_abis(&interfaces_directory, &interfaces)?;
372
373 lib
374 } else {
375 parse_leo_source_directory_library(
376 source,
377 &source_dir,
378 unit.name,
379 &handler,
380 &node_builder,
381 build_options.clone(),
382 network,
383 )?
384 };
385 handler.last_err()?;
388 let mut library_stub: Stub = library.into();
390 for node in package.dep_graph.nodes() {
391 if package.dep_graph.neighbors(node).any(|dep| dep == &unit.name) {
392 library_stub.add_parent(*node);
393 }
394 }
395 stubs.insert(unit.name, library_stub);
396 } else if !unit.kind.is_test() {
397 let leo_program = parse_leo_source_directory(
402 source,
403 &source_dir,
404 unit.name,
405 &handler,
406 &node_builder,
407 build_options.clone(),
408 network,
409 if is_main { rename_target.clone() } else { None },
410 )?;
411
412 stubs.insert(unit.name, leo_program.into());
413 }
414 }
415 }
416 }
417
418 for unit in &package.compilation_units {
421 if !unit.kind.is_program() || unit.kind.is_test() {
422 continue;
423 }
424 let leo_package::ProgramData::SourcePath { directory, source } = &unit.data else { continue };
425 let unit_name = unit.name.to_string();
426 let unit_key = leo_package::bare_unit_name(&unit_name).to_string();
427 if !written.insert(unit_key.clone()) {
428 continue;
429 }
430 let source_dir = directory.join("src");
431 let compiled = compile_leo_source_directory(
432 source,
433 &source_dir,
434 unit.name,
435 false,
436 &handler,
437 &node_builder,
438 build_options.clone(),
439 stubs.clone(),
440 network,
441 None,
443 )?;
444 let primary_path = package.unit_bytecode_path(&unit_name);
445 ensure_parent_dir(&primary_path)?;
446 std::fs::write(&primary_path, &compiled.primary.bytecode)
447 .map_err(crate::errors::failed_to_load_instructions)?;
448 let abi_path = package.unit_abi_path(&unit_name);
449 let abi_json = serde_json::to_string_pretty(&compiled.primary.abi)
450 .map_err(|e| crate::errors::failed_to_serialize_abi(e.to_string()))?;
451 std::fs::write(&abi_path, abi_json).map_err(crate::errors::failed_to_write_abi)?;
452 let interfaces_directory = package.unit_interfaces_directory(&unit_name);
453 write_interface_abis(&interfaces_directory, &compiled.interfaces)?;
454 compiled_programs.entry(unit_key).or_insert(ProgramForValidation {
455 bytecode: compiled.primary.bytecode.clone(),
456 path: primary_path,
457 is_leo_compiled: true,
458 });
459 }
460
461 validate_compiled_programs(&compiled_programs, network)?;
463
464 Ok(package)
465}
466
467fn apply_rename(command: &LeoBuild, package: &mut Package, primary_name: Option<Symbol>) -> Result<Option<String>> {
476 let Some(requested) = &command.rename else {
477 return Ok(None);
478 };
479
480 if command.options.build_tests {
483 return Err(crate::errors::custom("`--rename` cannot be combined with `--build-tests`.").into());
484 }
485
486 let renamed = leo_package::canonicalize_program_name(requested);
487 if !leo_package::is_valid_program_name(&renamed) {
488 return Err(crate::errors::custom(format!(
489 "Invalid program name '{requested}' for `--rename`; expected a valid Aleo program name."
490 ))
491 .into());
492 }
493
494 let Some(original) = primary_name else {
495 return Err(crate::errors::custom("`--rename` requires a primary program to rename.").into());
496 };
497
498 let renamed_bare = leo_package::bare_unit_name(&renamed);
501 if renamed_bare == leo_package::bare_unit_name(&original.to_string()) {
502 return Err(crate::errors::custom(format!(
503 "`--rename` target '{renamed}' is identical to the program's current name."
504 ))
505 .into());
506 }
507
508 if package
513 .compilation_units
514 .iter()
515 .any(|unit| unit.name != original && leo_package::bare_unit_name(&unit.name.to_string()) == renamed_bare)
516 {
517 return Err(crate::errors::custom(format!(
518 "`--rename` target '{renamed}' conflicts with an existing program or dependency in this package; choose a different name."
519 ))
520 .into());
521 }
522
523 let renamed_symbol = Symbol::intern(&renamed);
524 for unit in package.compilation_units.iter_mut() {
525 if !unit.kind.is_test() && unit.name == original {
526 unit.name = renamed_symbol;
527 }
528 }
529
530 Ok(Some(renamed))
531}
532
533fn collect_checksums<N: snarkvm::prelude::Network>(program: &SvmProgram<N>) -> BuildOutput {
535 let mut function_checksums = IndexMap::with_capacity(program.functions().len() + program.views().len());
536 for (name, function) in program.functions() {
537 function_checksums.insert(name.to_string(), function.to_checksum().iter().map(|b| **b).collect());
538 }
539 for (name, view) in program.views() {
540 function_checksums.insert(name.to_string(), view.to_checksum().iter().map(|b| **b).collect());
541 }
542 BuildOutput {
543 program: program.id().to_string(),
544 program_checksum: program.to_checksum().iter().map(|b| **b).collect(),
545 function_checksums,
546 }
547}
548
549fn program_checksums(network: NetworkName, bytecode: &str) -> Result<BuildOutput> {
552 Ok(match network {
553 NetworkName::MainnetV0 => collect_checksums(&SvmProgram::<MainnetV0>::from_str(bytecode)?),
554 NetworkName::TestnetV0 => collect_checksums(&SvmProgram::<TestnetV0>::from_str(bytecode)?),
555 NetworkName::CanaryV0 => collect_checksums(&SvmProgram::<CanaryV0>::from_str(bytecode)?),
556 })
557}
558
559pub fn build_output(package: &Package, network: Option<NetworkName>) -> Result<BuildOutput> {
561 let network = get_network(&network).unwrap_or(NetworkName::TestnetV0);
562 let unit =
563 package.primary_unit().ok_or_else(|| crate::errors::custom("No primary program found in the package."))?;
564 let name = unit.name.to_string();
565 let bytecode = std::fs::read_to_string(package.unit_bytecode_path(&name)).map_err(|err| {
566 crate::errors::util_file_io_error(format_args!("Trying to read compiled bytecode for `{name}`"), err)
567 })?;
568 program_checksums(network, &bytecode)
569}
570
571#[allow(clippy::too_many_arguments)]
573fn compile_leo_source_directory(
574 entry_file_path: &Path,
575 source_directory: &Path,
576 program_name: Symbol,
577 is_test: bool,
578 handler: &Handler,
579 node_builder: &Rc<NodeBuilder>,
580 options: BuildOptions,
581 stubs: IndexMap<Symbol, Stub>,
582 network: NetworkName,
583 rename: Option<String>,
584) -> Result<Compiled> {
585 if !is_test {
586 println!();
587 }
588 tracing::info!("🔨 Compiling '{program_name}'");
589 let print_checksums = options.checksums;
591 let mut compiler = Compiler::new(
593 Some(program_name.to_string()),
594 is_test,
595 handler.clone(),
596 Rc::clone(node_builder),
597 Some(options.into()),
598 stubs,
599 network,
600 );
601 compiler.rename = rename;
603
604 let compiled = if is_test {
607 compiler.compile_from_file(entry_file_path)?
608 } else {
609 compiler.compile_from_directory(entry_file_path, source_directory)?
610 };
611 let primary_bytecode = &compiled.primary.bytecode;
612
613 use leo_package::MAX_PROGRAM_SIZE;
615 let program_size = primary_bytecode.len();
616
617 if program_size > MAX_PROGRAM_SIZE {
618 return Err(crate::errors::program_size_limit_exceeded(program_name, program_size, MAX_PROGRAM_SIZE).into());
619 }
620
621 if print_checksums {
622 let checksums = program_checksums(network, primary_bytecode)?;
623 let format = |bytes: &[u8]| bytes.iter().map(|b| format!("{b}u8")).join(", ");
624 tracing::info!(" The program checksum is: '[{}]'.", format(&checksums.program_checksum));
625 for (name, function_checksum) in &checksums.function_checksums {
626 tracing::info!(" `{name}` function checksum is: '[{}]'.", format(function_checksum));
627 }
628 }
629
630 let (size_kb, max_kb, warning) = format_program_size(program_size, MAX_PROGRAM_SIZE);
631 if let Some(msg) = warning {
632 tracing::warn!("⚠️ Program '{program_name}' is {msg}.");
633 }
634
635 if !is_test {
636 tracing::info!(" Program size: {size_kb:.2} KB / {max_kb:.2} KB");
637 tracing::info!("✅ Compiled '{program_name}' into Aleo instructions.");
638 }
639
640 if print_checksums {
642 for import in &compiled.imports {
643 let dep_checksum: String = match network {
645 NetworkName::MainnetV0 => {
646 SvmProgram::<MainnetV0>::from_str(&import.bytecode)?.to_checksum().iter().join(", ")
647 }
648 NetworkName::TestnetV0 => {
649 SvmProgram::<TestnetV0>::from_str(&import.bytecode)?.to_checksum().iter().join(", ")
650 }
651 NetworkName::CanaryV0 => {
652 SvmProgram::<CanaryV0>::from_str(&import.bytecode)?.to_checksum().iter().join(", ")
653 }
654 };
655
656 tracing::info!(" Import '{}': checksum = '[{dep_checksum}]'", import.name);
657 }
658 }
659
660 if !is_test {
663 for import in &compiled.imports {
664 let import_size = import.bytecode.len();
665 let (size_kb, max_kb, _warning) = format_program_size(import_size, MAX_PROGRAM_SIZE);
666 tracing::info!(" Import '{}': program size: {size_kb:.2} KB / {max_kb:.2} KB", import.name);
667 }
668 }
669
670 Ok(compiled)
671}
672
673#[allow(clippy::too_many_arguments)]
675fn parse_leo_source_directory(
676 entry_file_path: &Path,
677 source_directory: &Path,
678 program_name: Symbol,
679 handler: &Handler,
680 node_builder: &Rc<NodeBuilder>,
681 options: BuildOptions,
682 network: NetworkName,
683 rename: Option<String>,
684) -> Result<Program> {
685 let mut compiler = Compiler::new(
687 Some(program_name.to_string()),
688 false,
689 handler.clone(),
690 Rc::clone(node_builder),
691 Some(options.into()),
692 IndexMap::new(),
693 network,
694 );
695 compiler.rename = rename;
698
699 compiler.parse_program_from_directory(entry_file_path, source_directory)
701}
702
703fn validate_compiled_programs(programs: &IndexMap<String, ProgramForValidation>, network: NetworkName) -> Result<()> {
706 match network {
707 NetworkName::MainnetV0 => validate_compiled_programs_inner::<MainnetV0>(programs),
708 NetworkName::TestnetV0 => validate_compiled_programs_inner::<TestnetV0>(programs),
709 NetworkName::CanaryV0 => validate_compiled_programs_inner::<CanaryV0>(programs),
710 }
711}
712
713fn validate_compiled_programs_inner<N: snarkvm::prelude::Network>(
715 programs: &IndexMap<String, ProgramForValidation>,
716) -> Result<()> {
717 let process = SvmProcess::<N>::load().map_err(|e| {
718 crate::errors::custom(format!("Failed to initialize snarkVM process for bytecode validation: {e}"))
719 })?;
720
721 for (name, ProgramForValidation { bytecode, path, is_leo_compiled }) in programs {
722 let program =
723 SvmProgram::<N>::from_str(bytecode).map_err(|e| crate::errors::failed_to_parse_aleo_file(name, e))?;
724
725 let checksum = program.to_checksum().iter().join(", ");
726
727 process.lock().add_program_with_edition(&program, LOCAL_PROGRAM_DEFAULT_EDITION).map_err(|e| {
728 if *is_leo_compiled {
729 crate::errors::generated_invalid_bytecode(name, path.display(), &checksum, e)
730 } else {
731 crate::errors::custom(format!(
732 "snarkVM rejected external program '{name}' during build validation: {e}"
733 ))
734 }
735 })?;
736 }
737
738 Ok(())
739}
740
741fn parse_leo_source_directory_library(
743 entry_file_path: &Path,
744 source_directory: &Path,
745 library_name: Symbol,
746 handler: &Handler,
747 node_builder: &Rc<NodeBuilder>,
748 options: BuildOptions,
749 network: NetworkName,
750) -> Result<leo_ast::Library> {
751 let mut compiler = Compiler::new(
753 Some(library_name.to_string()),
754 false,
755 handler.clone(),
756 Rc::clone(node_builder),
757 Some(options.into()),
758 IndexMap::new(),
759 network,
760 );
761
762 compiler.parse_library_from_directory(library_name, entry_file_path, source_directory)
764}
765
766#[allow(clippy::too_many_arguments)]
769fn build_leo_source_directory_library(
770 entry_file_path: &Path,
771 source_directory: &Path,
772 library_name: Symbol,
773 handler: &Handler,
774 node_builder: &Rc<NodeBuilder>,
775 options: BuildOptions,
776 stubs: IndexMap<Symbol, Stub>,
777 network: NetworkName,
778) -> Result<(leo_ast::Library, Vec<leo_abi::interfaces::CompiledInterface>)> {
779 println!();
781 tracing::info!("🔨 Building library '{library_name}'");
782
783 let mut compiler = Compiler::new(
784 Some(library_name.to_string()),
785 false,
786 handler.clone(),
787 Rc::clone(node_builder),
788 Some(options.into()),
789 stubs,
790 network,
791 );
792
793 let library = compiler.build_library_from_directory(library_name, entry_file_path, source_directory)?;
794 let interfaces = compiler.generate_library_interface_abis();
795
796 tracing::info!("✅ Validated '{library_name}'.");
797
798 Ok((library, interfaces))
799}
800
801fn write_interface_abis(interfaces_dir: &Path, interfaces: &[leo_abi::interfaces::CompiledInterface]) -> Result<()> {
803 if interfaces_dir.exists() {
805 std::fs::remove_dir_all(interfaces_dir).map_err(crate::errors::failed_to_write_abi)?;
806 }
807 if interfaces.is_empty() {
808 return Ok(());
809 }
810 for ci in interfaces {
811 let mut file_path = match &ci.owner {
812 leo_abi::interfaces::InterfaceOwner::Local => interfaces_dir.to_path_buf(),
813 leo_abi::interfaces::InterfaceOwner::External { owner_program } => interfaces_dir.join(owner_program),
814 };
815 for seg in &ci.abi.path[..ci.abi.path.len().saturating_sub(1)] {
817 file_path.push(seg);
818 }
819 std::fs::create_dir_all(&file_path).map_err(crate::errors::failed_to_write_abi)?;
820 file_path.push(format!("{}.json", ci.abi.name));
821 let json =
822 serde_json::to_string_pretty(&ci.abi).map_err(|e| crate::errors::failed_to_serialize_abi(e.to_string()))?;
823 std::fs::write(&file_path, json).map_err(crate::errors::failed_to_write_abi)?;
824 }
825 tracing::info!("✅ Generated {} interface ABI(s).", interfaces.len());
826 Ok(())
827}
828
829fn ensure_parent_dir(path: &Path) -> Result<()> {
831 if let Some(parent) = path.parent() {
832 std::fs::create_dir_all(parent).map_err(|err| {
833 crate::errors::util_file_io_error(format_args!("Couldn't create directory {}", parent.display()), err)
834 })?;
835 }
836 Ok(())
837}
838
839fn remove_legacy_build_artifacts(build_directory: &Path) {
846 let is_legacy =
847 build_directory.join("main.aleo").exists() || build_directory.join(leo_package::MANIFEST_FILENAME).exists();
848 if !is_legacy {
849 return;
850 }
851 for file in ["main.aleo", ABI_FILENAME, leo_package::MANIFEST_FILENAME] {
852 let _ = std::fs::remove_file(build_directory.join(file));
853 }
854 for dir in ["imports", "interfaces"] {
855 let _ = std::fs::remove_dir_all(build_directory.join(dir));
856 }
857}