leo_lang/cli/commands/
build.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18
19use leo_ast::Stub;
20use leo_compiler::{AstSnapshots, Compiler, CompilerOptions};
21use leo_errors::{CliError, UtilError};
22use leo_package::{Manifest, NetworkName, Package};
23use leo_span::Symbol;
24
25use snarkvm::prelude::{MainnetV0, Network, TestnetV0};
26
27use indexmap::IndexMap;
28use snarkvm::prelude::CanaryV0;
29use std::path::Path;
30
31impl From<BuildOptions> for CompilerOptions {
32    fn from(options: BuildOptions) -> Self {
33        Self {
34            ast_spans_enabled: options.enable_ast_spans,
35            ast_snapshots: if options.enable_all_ast_snapshots {
36                AstSnapshots::All
37            } else {
38                AstSnapshots::Some(options.ast_snapshots.into_iter().collect())
39            },
40            initial_ast: options.enable_all_ast_snapshots | options.enable_initial_ast_snapshot,
41        }
42    }
43}
44
45/// Compile and build program command.
46#[derive(Parser, Debug)]
47pub struct LeoBuild {
48    #[clap(flatten)]
49    pub(crate) options: BuildOptions,
50    #[clap(flatten)]
51    pub(crate) env_override: EnvOptions,
52}
53
54impl Command for LeoBuild {
55    type Input = ();
56    type Output = Package;
57
58    fn log_span(&self) -> Span {
59        tracing::span!(tracing::Level::INFO, "Leo")
60    }
61
62    fn prelude(&self, _: Context) -> Result<Self::Input> {
63        Ok(())
64    }
65
66    fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
67        // Parse the network.
68        let network: NetworkName = context.get_network(&self.env_override.network)?.parse()?;
69        match network {
70            NetworkName::MainnetV0 => handle_build::<MainnetV0>(&self, context),
71            NetworkName::TestnetV0 => handle_build::<TestnetV0>(&self, context),
72            NetworkName::CanaryV0 => handle_build::<CanaryV0>(&self, context),
73        }
74    }
75}
76
77// A helper function to handle the build command.
78fn handle_build<N: Network>(command: &LeoBuild, context: Context) -> Result<<LeoBuild as Command>::Output> {
79    let package_path = context.dir()?;
80    let home_path = context.home()?;
81
82    let package = if command.options.build_tests {
83        leo_package::Package::from_directory_with_tests(&package_path, &home_path, command.options.no_cache)?
84    } else {
85        leo_package::Package::from_directory(&package_path, &home_path, command.options.no_cache)?
86    };
87
88    let outputs_directory = package.outputs_directory();
89    let build_directory = package.build_directory();
90    let imports_directory = package.imports_directory();
91    let source_directory = package.source_directory();
92    let main_source_path = source_directory.join("main.leo");
93
94    for dir in [&outputs_directory, &build_directory, &imports_directory] {
95        std::fs::create_dir_all(dir).map_err(|err| {
96            UtilError::util_file_io_error(format_args!("Couldn't create directory {}", dir.display()), err)
97        })?;
98    }
99
100    // Initialize error handler.
101    let handler = Handler::default();
102
103    let mut stubs: IndexMap<Symbol, Stub> = IndexMap::new();
104
105    for program in package.programs.iter() {
106        let (bytecode, build_path) = match &program.data {
107            leo_package::ProgramData::Bytecode(bytecode) => {
108                // This was a network dependency, and we've downloaded its bytecode.
109                (bytecode.clone(), imports_directory.join(format!("{}.aleo", program.name)))
110            }
111            leo_package::ProgramData::SourcePath(path) => {
112                // This is a local dependency, so we must compile it.
113                let build_path = if path == &main_source_path {
114                    build_directory.join("main.aleo")
115                } else {
116                    imports_directory.join(format!("{}.aleo", program.name))
117                };
118                let bytecode = compile_leo_file::<N>(
119                    path,
120                    program.name,
121                    program.is_test,
122                    &outputs_directory,
123                    &handler,
124                    command.options.clone(),
125                    stubs.clone(),
126                )?;
127                (bytecode, build_path)
128            }
129        };
130
131        // Write the .aleo file.
132        std::fs::write(build_path, &bytecode).map_err(CliError::failed_to_load_instructions)?;
133
134        // Track the Stub.
135        let stub = leo_disassembler::disassemble_from_str::<N>(program.name, &bytecode)?;
136        stubs.insert(program.name, stub);
137    }
138
139    // SnarkVM expects to find a `program.json` file in the build directory, so make
140    // a bogus one.
141    let build_manifest_path = build_directory.join(leo_package::MANIFEST_FILENAME);
142    let fake_manifest = Manifest {
143        program: package.manifest.program.clone(),
144        version: "0.1.0".to_string(),
145        description: String::new(),
146        license: String::new(),
147        dependencies: None,
148        dev_dependencies: None,
149    };
150    fake_manifest.write_to_file(build_manifest_path)?;
151
152    Ok(package)
153}
154
155/// Compiles a Leo file. Writes and returns the compiled bytecode.
156#[allow(clippy::too_many_arguments)]
157fn compile_leo_file<N: Network>(
158    source_file_path: &Path,
159    program_name: Symbol,
160    is_test: bool,
161    output_path: &Path,
162    handler: &Handler,
163    options: BuildOptions,
164    stubs: IndexMap<Symbol, Stub>,
165) -> Result<String> {
166    // Create a new instance of the Leo compiler.
167    let mut compiler = Compiler::<N>::new(
168        Some(program_name.to_string()),
169        is_test,
170        handler.clone(),
171        output_path.to_path_buf(),
172        Some(options.into()),
173        stubs,
174    );
175
176    // Compile the Leo program into Aleo instructions.
177    let bytecode = compiler.compile_from_file(source_file_path)?;
178
179    tracing::info!("    {} statements before dead code elimination.", compiler.statements_before_dce);
180    tracing::info!("    {} statements after dead code elimination.", compiler.statements_after_dce);
181
182    tracing::info!("✅ Compiled '{program_name}.aleo' into Aleo instructions");
183    Ok(bytecode)
184}