From a6b93cf00de682f95a4a0db6c058ecec82aa9d27 Mon Sep 17 00:00:00 2001 From: collin <16715212+collinc97@users.noreply.github.com> Date: Thu, 14 Jul 2022 18:08:06 -0700 Subject: [PATCH] impl leo build for multiple aleo files --- Cargo.lock | 1 + Cargo.toml | 5 + errors/src/errors/package/package_errors.rs | 7 + examples/helloworld/main.aleo | 7 + examples/helloworld/other.aleo | 4 + examples/transfer/main.aleo | 6 +- leo/commands/build.rs | 188 +++++++++++--------- leo/commands/clean.rs | 2 +- leo/commands/mod.rs | 2 - leo/context.rs | 4 +- leo/package/src/outputs/directory.rs | 2 +- leo/package/src/source/directory.rs | 38 ++-- 12 files changed, 159 insertions(+), 107 deletions(-) create mode 100644 examples/helloworld/main.aleo create mode 100644 examples/helloworld/other.aleo diff --git a/Cargo.lock b/Cargo.lock index 80b5da6892..f8552d9e16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1222,6 +1222,7 @@ dependencies = [ "leo-compiler", "leo-errors", "leo-package", + "leo-parser", "leo-span", "rand", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 33c9eabc2b..f09673053c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ path = "leo/main.rs" [workspace] members = [ "compiler/compiler", + "compiler/parser", "docs/grammar", "errors", "leo/package", @@ -46,6 +47,10 @@ version = "1.5.3" path = "./leo/package" version = "1.5.3" +[dependencies.leo-parser] +path = "./compiler/parser" +version = "1.5.3" + [dependencies.leo-span] path = "./compiler/span" version = "1.5.3" diff --git a/errors/src/errors/package/package_errors.rs b/errors/src/errors/package/package_errors.rs index 0006a0b486..3114d573a6 100644 --- a/errors/src/errors/package/package_errors.rs +++ b/errors/src/errors/package/package_errors.rs @@ -580,4 +580,11 @@ create_messages!( msg: format!("i/o operation failed, file: {}, error: {}", file, error), help: None, } + + @backtraced + failed_to_get_file_name { + args: (), + msg: "Failed to get names of Leo files in the `src/` directory.".to_string(), + help: Some("Check your `src/` directory for invalid file names.".to_string()), + } ); diff --git a/examples/helloworld/main.aleo b/examples/helloworld/main.aleo new file mode 100644 index 0000000000..88fd8a3f5b --- /dev/null +++ b/examples/helloworld/main.aleo @@ -0,0 +1,7 @@ +program helloworld.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; diff --git a/examples/helloworld/other.aleo b/examples/helloworld/other.aleo new file mode 100644 index 0000000000..0fff2d9127 --- /dev/null +++ b/examples/helloworld/other.aleo @@ -0,0 +1,4 @@ +program other.aleo; +interface other: + a as u64; + diff --git a/examples/transfer/main.aleo b/examples/transfer/main.aleo index 5714ae58db..bd0a1c1a97 100644 --- a/examples/transfer/main.aleo +++ b/examples/transfer/main.aleo @@ -7,7 +7,7 @@ record token: function main: input r0 as address.private; input r1 as u64.private; - cast r0 0u64 r1 into r2 as token.record; + token r0 0u64 r1 into r2; output r2 as token.record; function transfer: @@ -15,7 +15,7 @@ function transfer: input r1 as address.private; input r2 as u64.private; sub r0.amount r2 into r3; - cast r1 0u64 r2 into r4 as token.record; - cast r0.owner r0.balance r3 into r5 as token.record; + token r1 0u64 r2 into r4; + token r0.owner r0.balance r3 into r5; output r4 as token.record; output r5 as token.record; diff --git a/leo/commands/build.rs b/leo/commands/build.rs index 54b4bcaa2a..af48f32833 100644 --- a/leo/commands/build.rs +++ b/leo/commands/build.rs @@ -14,21 +14,22 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use super::*; use crate::{commands::Command, context::Context}; use leo_compiler::{Compiler, InputAst, OutputOptions}; -use leo_errors::{CliError, Result}; +use leo_errors::{CliError, CompilerError, PackageError, Result}; +use leo_package::source::{SourceDirectory, MAIN_FILENAME}; use leo_package::{ inputs::InputFile, outputs::{ChecksumFile, OutputsDirectory, OUTPUTS_DIRECTORY_NAME}, - source::{MainFile, MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, }; +use leo_span::symbol::with_session_globals; use aleo::commands::Build as AleoBuild; use clap::StructOpt; use colored::Colorize; use std::io::Write; + use tracing::span::Span; /// Compiler Options wrapper for Build command. Also used by other commands which @@ -91,7 +92,7 @@ impl Command for Build { let path = context.dir()?; // Get the program name. - let program_name = context.program_name()?; + let package_name = context.package_name()?; // Sanitize the package path to the root directory. let mut package_path = path.clone(); @@ -103,105 +104,124 @@ impl Command for Build { let mut output_directory = package_path.clone(); output_directory.push(OUTPUTS_DIRECTORY_NAME); - tracing::info!("Starting..."); - - // Compile the main.leo file along with constraints - if !MainFile::exists_at(&package_path) { - return Err(CliError::package_main_file_not_found().into()); - } - - // Construct the path to the main file in the source directory - let mut main_file_path = package_path.clone(); - main_file_path.push(SOURCE_DIRECTORY_NAME); - main_file_path.push(MAIN_FILENAME); - - // Load the input file at `package_name.in` - let input_path = InputFile::new(&program_name).setup_file_path(&path); - // Create the outputs directory OutputsDirectory::create(&package_path)?; - // Log compilation of files to console - tracing::info!("Compiling '{}' in (in \"{}\")", MAIN_FILENAME, main_file_path.display()); + tracing::info!("Starting..."); // Initialize error handler let handler = leo_errors::emitter::Handler::default(); - // Create a new instance of the Leo compiler. - let mut program = Compiler::new( - program_name.to_string(), - String::from("aleo"), - &handler, - main_file_path, - output_directory, - Some(self.compiler_options.into()), - ); - program.parse_input(input_path.to_path_buf())?; + // Fetch paths to all .leo files in the source directory. + let files = SourceDirectory::files(&package_path)?; - // Compute the current program checksum - let program_checksum = program.checksum()?; + // Compile all `.leo` files into `.aleo` files. + for file_path in files.into_iter().filter(|path| path.is_file()) { + // Construct the Leo file name with extension. + let file_name = file_path + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(PackageError::failed_to_get_file_name)?; - // Compile the program. - { - // Compile the Leo program into Aleo instructions. - let (_, instructions) = program.compile_and_generate_instructions()?; + // Construct program name from file_path name. + let program_name = file_name + .strip_suffix(".leo") + .ok_or_else(PackageError::failed_to_get_file_name)?; - // // Parse the generated Aleo instructions into an Aleo file. - // let file = AleoFile::::from_str(&instructions).map_err(CliError::failed_to_load_instructions)?; - // - // // Create the path to the Aleo file. - // let mut aleo_file_path = package_path.clone(); - // aleo_file_path.push(AleoFile::::main_file_name()); - // - // // Write the Aleo file to `main.aleo`. - // file.write_to(&aleo_file_path) - // .map_err(|err| CliError::failed_to_write_to_aleo_file(aleo_file_path.display(), err))?; + // Construct program id header for aleo file. + // Do not create a program with main.aleo as the ID. + let program_id_name = if file_name.eq(MAIN_FILENAME) { + &package_name + } else { + program_name + }; - // Create the path to the main Aleo file. - let mut aleo_file_path = package_path.clone(); - aleo_file_path.push(AleoFile::::main_file_name()); - - // Write the instructions. - std::fs::File::create(&aleo_file_path) - .map_err(CliError::failed_to_load_instructions)? - .write_all(instructions.as_bytes()) - .map_err(CliError::failed_to_load_instructions)?; - // Prepare the path string. - let path_string = format!("(in \"{}\")", aleo_file_path.display()); - - // Log the build as successful. - tracing::info!( - "✅ Compiled '{}' into Aleo instructions {}", - MAIN_FILENAME, - path_string.dimmed() + // Create a new instance of the Leo compiler. + let mut program = Compiler::new( + program_id_name.to_string(), + String::from("aleo"), // todo: fetch this from Network::Testnet3 + &handler, + file_path.clone(), + output_directory.clone(), + Some(self.compiler_options.clone().into()), ); - // Call the `aleo build` command from the Aleo SDK. - let res = AleoBuild.parse().map_err(CliError::failed_to_execute_aleo_build)?; - // Log the result of the build - tracing::info!("{}", res); + // Check if we need to compile the Leo program. + let checksum_differs = { + // Compute the current program checksum. + let program_checksum = program.checksum()?; + + // Get the current program checksum. + let checksum_file = ChecksumFile::new(program_name); + + // If a checksum file exists, check if it differs from the new checksum. + let checksum_differs = if checksum_file.exists_at(&package_path) { + let previous_checksum = checksum_file.read_from(&package_path)?; + program_checksum != previous_checksum + } else { + // By default, the checksum differs if there is no checksum to compare against. + true + }; + + // If checksum differs, compile the program + if checksum_differs { + // Write the new checksum to the output directory + checksum_file.write_to(&path, program_checksum)?; + + tracing::debug!("Checksum saved ({:?})", path); + } + + checksum_differs + }; + + // Compile the program. + if checksum_differs { + // Compile the Leo program into Aleo instructions. + let (_, instructions) = program.compile_and_generate_instructions()?; + + // Create the path to the main Aleo file. + let mut aleo_file_path = package_path.clone(); + aleo_file_path.push(format!("{}.aleo", program_name)); + + // Write the instructions. + std::fs::File::create(&aleo_file_path) + .map_err(CliError::failed_to_load_instructions)? + .write_all(instructions.as_bytes()) + .map_err(CliError::failed_to_load_instructions)?; + // Prepare the path string. + let path_string = format!("(in \"{}\")", aleo_file_path.display()); + + // Log the build as successful. + tracing::info!( + "✅ Compiled '{}' into Aleo instructions {}", + file_name, + path_string.dimmed() + ); + } } - // If a checksum file exists, check if it differs from the new checksum - let checksum_file = ChecksumFile::new(&program_name); - let checksum_differs = if checksum_file.exists_at(&package_path) { - let previous_checksum = checksum_file.read_from(&package_path)?; - program_checksum != previous_checksum + // Call the `aleo build` command from the Aleo SDK. + let result = AleoBuild.parse().map_err(CliError::failed_to_execute_aleo_build)?; + + // Log the result of the build + tracing::info!("{}", result); + + // Load the input file at `package_name.in` + let input_file_path = InputFile::new(&package_name).setup_file_path(&path); + + // Initialize error handler. + let input_ast = if input_file_path.exists() { + // Load the input file into the source map. + let input_sf = with_session_globals(|s| s.source_map.load_file(&input_file_path)) + .map_err(|e| CompilerError::file_read_error(&input_file_path, e))?; + + leo_parser::parse_input(&handler, &input_sf.src, input_sf.start_pos).ok() } else { - // By default, the checksum differs if there is no checksum to compare against - true + todo!() // throw no input files found error. }; - // If checksum differs, compile the program - if checksum_differs { - // Write the new checksum to the output directory - checksum_file.write_to(&path, program_checksum)?; - - tracing::debug!("Checksum saved ({:?})", path); - } - tracing::info!("Complete"); - Ok(program.input_ast) + Ok(input_ast) } } diff --git a/leo/commands/clean.rs b/leo/commands/clean.rs index 0bd37f4c4a..d3b8f4077c 100644 --- a/leo/commands/clean.rs +++ b/leo/commands/clean.rs @@ -46,7 +46,7 @@ impl Command for Clean { // Removes the outputs directory. let path_string = OutputsDirectory::remove(&path)?; - tracing::info!("✅ Cleaned the build directory {}", path_string.dimmed()); + tracing::info!("✅ Cleaned the outputs directory {}", path_string.dimmed()); // Call the `aleo clean` command. let result = AleoClean.parse().map_err(CliError::failed_to_execute_aleo_clean)?; diff --git a/leo/commands/mod.rs b/leo/commands/mod.rs index 6bea9995c7..0b348a17e8 100644 --- a/leo/commands/mod.rs +++ b/leo/commands/mod.rs @@ -33,8 +33,6 @@ pub use run::Run; use crate::context::*; use leo_errors::Result; -use snarkvm::file::AleoFile; - use std::time::Instant; use tracing::span::Span; diff --git a/leo/context.rs b/leo/context.rs index a24237631a..bef8ed79d9 100644 --- a/leo/context.rs +++ b/leo/context.rs @@ -41,8 +41,8 @@ impl Context { } } - /// Returns the program name as a String. - pub fn program_name(&self) -> Result { + /// Returns the package name as a String. + pub fn package_name(&self) -> Result { // Open the manifest file. let path = self.dir()?; let manifest = Manifest::::open(&path).map_err(CliError::failed_to_open_manifest)?; diff --git a/leo/package/src/outputs/directory.rs b/leo/package/src/outputs/directory.rs index ebf01ce3e3..3d364b7dd9 100644 --- a/leo/package/src/outputs/directory.rs +++ b/leo/package/src/outputs/directory.rs @@ -42,7 +42,7 @@ impl OutputsDirectory { } if path.exists() { - fs::remove_dir_all(&path).map_err(PackageError::failed_to_create_inputs_directory)?; + fs::remove_dir_all(&path).map_err(|e| PackageError::failed_to_remove_directory(path.display(), e))?; } Ok(format!("(in \"{}\")", path.display())) diff --git a/leo/package/src/source/directory.rs b/leo/package/src/source/directory.rs index ac512c57c9..51717b5722 100644 --- a/leo/package/src/source/directory.rs +++ b/leo/package/src/source/directory.rs @@ -16,6 +16,7 @@ use leo_errors::{PackageError, Result}; +use std::fs::ReadDir; use std::{ borrow::Cow, fs, @@ -45,21 +46,30 @@ impl SourceDirectory { let mut path = Cow::from(path); path.to_mut().push(SOURCE_DIRECTORY_NAME); - let directory = fs::read_dir(&path).map_err(PackageError::failed_to_read_inputs_directory)?; - + let directory = fs::read_dir(&path).map_err(|err| PackageError::failed_to_read_file(path.display(), err))?; let mut file_paths = Vec::new(); - for file_entry in directory { - let file_entry = file_entry.map_err(PackageError::failed_to_get_source_file_entry)?; - let file_path = file_entry.path(); - // Verify that the entry is structured as a valid file - let file_type = file_entry - .file_type() - .map_err(|e| PackageError::failed_to_get_source_file_type(file_path.as_os_str().to_owned(), e))?; - if !file_type.is_file() { - return Err(PackageError::invalid_source_file_type(file_path.as_os_str().to_owned(), file_type).into()); - } + parse_file_paths(directory, &mut file_paths)?; + println!("{:?}", file_paths); + + Ok(file_paths) + } +} + +fn parse_file_paths(directory: ReadDir, file_paths: &mut Vec) -> Result<()> { + for file_entry in directory { + let file_entry = file_entry.map_err(PackageError::failed_to_get_source_file_entry)?; + let file_path = file_entry.path(); + + // Verify that the entry is structured as a valid file or directory + if file_path.is_dir() { + let directory = + fs::read_dir(&file_path).map_err(|err| PackageError::failed_to_read_file(file_path.display(), err))?; + + parse_file_paths(directory, file_paths)?; + continue; + } else { // Verify that the file has the default file extension let file_extension = file_path .extension() @@ -74,7 +84,7 @@ impl SourceDirectory { file_paths.push(file_path); } - - Ok(file_paths) } + + Ok(()) }