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(())
}