Merge pull request #1119 from AleoHQ/leo-manifest-dependencies

[Feature] Imports Stabilization
This commit is contained in:
Alessandro Coglio 2021-08-20 09:30:36 -07:00 committed by GitHub
commit 92351d0fb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 621 additions and 58 deletions

View File

@ -186,18 +186,18 @@ jobs:
export LEO=/home/circleci/project/project/bin/leo
./project/.circleci/leo-setup.sh
leo-add-remove:
docker:
- image: cimg/rust:1.54.0
resource_class: xlarge
steps:
- attach_workspace:
at: /home/circleci/project/
- run:
name: leo add & remove
command: |
export LEO=/home/circleci/project/project/bin/leo
./project/.circleci/leo-add-remove.sh
# leo-add-remove:
# docker:
# - image: cimg/rust:1.54.0
# resource_class: xlarge
# steps:
# - attach_workspace:
# at: /home/circleci/project/
# - run:
# name: leo add & remove
# command: |
# export LEO=/home/circleci/project/project/bin/leo
# ./project/.circleci/leo-add-remove.sh
leo-check-constraints:
docker:
@ -270,9 +270,9 @@ workflows:
- leo-setup:
requires:
- leo-executable
- leo-add-remove:
requires:
- leo-executable
# - leo-add-remove:
# requires:
# - leo-executable
- leo-check-constraints:
requires:
- leo-executable

View File

2
Cargo.lock generated
View File

@ -1320,6 +1320,7 @@ dependencies = [
"console",
"dirs",
"from-pest",
"indexmap",
"lazy_static",
"leo-ast",
"leo-compiler",
@ -1359,6 +1360,7 @@ version = "1.5.3"
name = "leo-package"
version = "1.5.3"
dependencies = [
"indexmap",
"lazy_static",
"leo-errors",
"serde",

View File

@ -117,6 +117,10 @@ version = "0.14.0"
[dependencies.from-pest]
version = "0.3.1"
[dependencies.indexmap]
version = "1.7"
features = ["serde"]
[dependencies.lazy_static]
version = "1.4.0"

View File

@ -23,6 +23,7 @@ pub use leo_asg::{new_context, AsgContext as Context, AsgContext};
use leo_asg::{Asg, AsgPass, Program as AsgProgram};
use leo_ast::{Input, MainInput, Program as AstProgram};
use leo_errors::{CompilerError, Result};
use leo_imports::ImportParser;
use leo_input::LeoInputParser;
use leo_package::inputs::InputPairs;
use leo_parser::parse_ast;
@ -34,6 +35,7 @@ use snarkvm_r1cs::{ConstraintSynthesizer, ConstraintSystem, SynthesisError};
use sha2::{Digest, Sha256};
use std::{
collections::HashMap,
fs,
marker::PhantomData,
path::{Path, PathBuf},
@ -62,6 +64,7 @@ pub struct Compiler<'a, F: PrimeField, G: GroupType<F>> {
context: AsgContext<'a>,
asg: Option<AsgProgram<'a>>,
options: CompilerOptions,
imports_map: HashMap<String, String>,
ast_snapshot_options: AstSnapshotOptions,
_engine: PhantomData<F>,
_group: PhantomData<G>,
@ -77,6 +80,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
output_directory: PathBuf,
context: AsgContext<'a>,
options: Option<CompilerOptions>,
imports_map: HashMap<String, String>,
ast_snapshot_options: Option<AstSnapshotOptions>,
) -> Self {
Self {
@ -88,6 +92,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
asg: None,
context,
options: options.unwrap_or_default(),
imports_map,
ast_snapshot_options: ast_snapshot_options.unwrap_or_default(),
_engine: PhantomData,
_group: PhantomData,
@ -107,6 +112,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
output_directory: PathBuf,
context: AsgContext<'a>,
options: Option<CompilerOptions>,
imports_map: HashMap<String, String>,
ast_snapshot_options: Option<AstSnapshotOptions>,
) -> Result<Self> {
let mut compiler = Self::new(
@ -115,6 +121,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
output_directory,
context,
options,
imports_map,
ast_snapshot_options,
);
@ -146,6 +153,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
state_path: &Path,
context: AsgContext<'a>,
options: Option<CompilerOptions>,
imports_map: HashMap<String, String>,
ast_snapshot_options: Option<AstSnapshotOptions>,
) -> Result<Self> {
let mut compiler = Self::new(
@ -154,6 +162,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
output_directory,
context,
options,
imports_map,
ast_snapshot_options,
);
@ -239,7 +248,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
ast.to_json_file(self.output_directory.clone(), "initial_ast.json")?;
}
// Preform canonicalization of AST always.
// Perform canonicalization of AST always.
ast.canonicalize()?;
if self.ast_snapshot_options.canonicalized {
@ -256,7 +265,7 @@ impl<'a, F: PrimeField, G: GroupType<F>> Compiler<'a, F, G> {
let asg = Asg::new(
self.context,
&self.program,
&mut leo_imports::ImportParser::new(self.main_file_path.clone()),
&mut ImportParser::new(self.main_file_path.clone(), self.imports_map.clone()),
)?;
if self.ast_snapshot_options.type_inferenced {

View File

@ -17,7 +17,7 @@
///
/// Toggles compiler optimizations on the program.
///
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct CompilerOptions {
pub constant_folding_enabled: bool,
pub dead_code_elimination_enabled: bool,

View File

@ -14,7 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use std::path::{Path, PathBuf};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use leo_asg::*;
use leo_ast::{Ast, Program};
@ -50,6 +53,7 @@ fn new_compiler(path: PathBuf, theorem_options: Option<AstSnapshotOptions>) -> E
output_dir,
make_test_context(),
None,
HashMap::new(),
theorem_options,
)
}

View File

@ -157,7 +157,7 @@ create_errors!(
/// For when the CLI cannot find the manifest file.
@backtraced
mainifest_file_not_found {
manifest_file_not_found {
args: (),
msg: "Package manifest not found, try running `leo init`",
help: None,
@ -379,6 +379,27 @@ create_errors!(
msg: format!("Old release version {} {}", current, latest),
help: None,
}
@backtraced
dependencies_are_not_installed {
args: (),
msg: "dependencies are not installed, please run `leo fetch` first",
help: None,
}
@backtraced
recursive_dependency_found {
args: (message: impl Display),
msg: format!("recursive dependency found \n{}", message),
help: None,
}
@backtraced
unable_to_read_imported_dependency_manifest {
args: (),
msg: "unable to parse imported dependency's manifest",
help: None,
}
);
impl CliError {

View File

@ -497,4 +497,66 @@ create_errors!(
msg: format!("invalid project name {}", package),
help: None,
}
@backtraced
failed_to_create_lock_file {
args: (filename: impl Display, error: impl ErrorArg),
msg: format!("failed creating manifest file `{}` {}", filename, error),
help: None,
}
/// For when getting the lock file metadata failed.
@backtraced
failed_to_get_lock_file_metadata {
args: (filename: impl Display, error: impl ErrorArg),
msg: format!("failed getting lock file metadata `{}` {}", filename, error),
help: None,
}
/// For when opening the lock file failed.
@backtraced
failed_to_open_lock_file {
args: (filename: impl Display, error: impl ErrorArg),
msg: format!("failed openining lock file `{}` {}", filename, error),
help: None,
}
/// For when parsing the lock file failed.
@backtraced
failed_to_parse_lock_file {
args: (filename: impl Display, error: impl ErrorArg),
msg: format!("failed parsing lock file `{}` {}", filename, error),
help: None,
}
/// For when reading the lock file failed.
@backtraced
failed_to_read_lock_file {
args: (filename: impl Display, error: impl ErrorArg),
msg: format!("failed reading lock file `{}` {}", filename, error),
help: None,
}
/// For when writing the lock file failed.
@backtraced
failed_to_write_lock_file {
args: (filename: impl Display, error: impl ErrorArg),
msg: format!("failed writing lock file `{}` {}", filename, error),
help: None,
}
/// For when the lock file has an IO error.
@backtraced
io_error_lock_file {
args: (error: impl ErrorArg),
msg: format!("IO error lock file from the provided file path - {}", error),
help: None,
}
@backtraced
failed_to_serialize_lock_file {
args: (error: impl ErrorArg),
msg: format!("serialization failed: {}", error),
help: None,
}
);

View File

@ -18,7 +18,7 @@ use leo_asg::{AsgContext, ImportResolver, Program};
use leo_errors::{ImportError, LeoError, Result, Span};
use indexmap::{IndexMap, IndexSet};
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf};
/// Stores imported packages.
///
@ -29,14 +29,16 @@ pub struct ImportParser<'a> {
program_path: PathBuf,
partial_imports: IndexSet<String>,
imports: IndexMap<String, Program<'a>>,
pub imports_map: HashMap<String, String>,
}
impl<'a> ImportParser<'a> {
pub fn new(program_path: PathBuf) -> Self {
pub fn new(program_path: PathBuf, imports_map: HashMap<String, String>) -> Self {
ImportParser {
program_path,
partial_imports: Default::default(),
imports: Default::default(),
imports_map,
}
}
}

View File

@ -104,10 +104,19 @@ impl<'a> ImportParser<'a> {
.collect::<Result<Vec<_>, std::io::Error>>()
.map_err(|error| ImportError::directory_error(error, error_path, span))?;
// Keeping backward compatibilty for existing packages.
// If index_map contains key, use it or try to access directly.
// TODO: Remove when migration is possible.
let package_name = self
.imports_map
.get(package_name)
.unwrap_or(&package_name.to_string())
.clone();
// Check if the imported package name is in the imports directory.
let matched_import_entry = entries
.into_iter()
.find(|entry| entry.file_name().into_string().unwrap().eq(package_name));
.find(|entry| entry.file_name().into_string().unwrap().eq(&package_name));
// Check if the package name was found in both the source and imports directory.
match (matched_source_entry, matched_import_entry) {

View File

@ -56,9 +56,9 @@ pub struct BuildOptions {
impl Default for BuildOptions {
fn default() -> Self {
Self {
disable_constant_folding: true,
disable_code_elimination: true,
disable_all_optimizations: true,
disable_constant_folding: false,
disable_code_elimination: false,
disable_all_optimizations: false,
enable_all_ast_snapshots: false,
enable_initial_ast_snapshot: false,
enable_canonicalized_ast_snapshot: false,
@ -69,10 +69,10 @@ impl Default for BuildOptions {
impl From<BuildOptions> for CompilerOptions {
fn from(options: BuildOptions) -> Self {
if !options.disable_all_optimizations {
if options.disable_all_optimizations {
CompilerOptions {
constant_folding_enabled: true,
dead_code_elimination_enabled: true,
constant_folding_enabled: false,
dead_code_elimination_enabled: false,
}
} else {
CompilerOptions {
@ -123,7 +123,14 @@ impl Command for Build {
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
let path = context.dir()?;
let package_name = context.manifest()?.get_package_name();
let manifest = context.manifest().map_err(|_| CliError::manifest_file_not_found())?;
let package_name = manifest.get_package_name();
let imports_map = manifest.get_imports_map().unwrap_or_default();
// Error out if there are dependencies but no lock file found.
if !imports_map.is_empty() && !context.lock_file_exists()? {
return Err(CliError::dependencies_are_not_installed().into());
}
// Sanitize the package path to the root directory.
let mut package_path = path.clone();
@ -159,6 +166,12 @@ impl Command for Build {
// Log compilation of files to console
tracing::info!("Compiling main program... ({:?})", main_file_path);
let imports_map = if context.lock_file_exists()? {
context.lock_file()?.to_import_map()
} else {
Default::default()
};
// Load the program at `main_file_path`
let program = Compiler::<Fq, EdwardsGroupType>::parse_program_with_input(
package_name.clone(),
@ -170,6 +183,7 @@ impl Command for Build {
&state_path,
thread_leaked_context(),
Some(self.compiler_options.clone().into()),
imports_map,
Some(self.compiler_options.into()),
)?;

View File

@ -14,6 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!
// COMMAND TEMPORARILY DISABLED
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!
use crate::{api::Fetch, commands::Command, context::Context};
use leo_errors::{CliError, Result};
use leo_package::imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME};
@ -21,6 +25,7 @@ use leo_package::imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME};
use std::{
fs::{create_dir_all, File},
io::{Read, Write},
path::PathBuf,
};
use structopt::StructOpt;
use tracing::Span;
@ -76,7 +81,7 @@ impl Add {
impl Command for Add {
type Input = ();
type Output = ();
type Output = PathBuf;
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Adding")
@ -88,18 +93,20 @@ impl Command for Add {
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Check that a manifest exists for the current package.
context.manifest().map_err(|_| CliError::mainifest_file_not_found())?;
context.manifest().map_err(|_| CliError::manifest_file_not_found())?;
let (author, package_name) = self
.try_read_arguments()
.map_err(CliError::cli_bytes_conversion_error)?;
tracing::info!("Package: {}/{}", &author, &package_name);
// Attempt to fetch the package.
let reader = {
let fetch = Fetch {
author,
author: author.clone(),
package_name: package_name.clone(),
version: self.version,
version: self.version.clone(),
};
let bytes = context
.api
@ -114,7 +121,14 @@ impl Command for Add {
{
ImportsDirectory::create(&path)?;
path.push(IMPORTS_DIRECTORY_NAME);
path.push(package_name);
// Dumb compatibility hack.
// TODO: Remove once `leo add` functionality is discussed.
if self.version.is_some() {
path.push(format!("{}-{}@{}", author, package_name, self.version.unwrap()));
} else {
path.push(package_name.clone());
}
create_dir_all(&path).map_err(CliError::cli_io_error)?;
};
@ -142,8 +156,8 @@ impl Command for Add {
}
}
tracing::info!("Successfully added a package");
tracing::info!("Successfully added package {}/{}", author, package_name);
Ok(())
Ok(path)
}
}

View File

@ -0,0 +1,144 @@
// Copyright (C) 2019-2021 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{
commands::{package::Add, Command},
context::{create_context, Context},
};
use leo_package::root::{
lock_file::{LockFile, Package},
Dependency,
};
use indexmap::{set::IndexSet, IndexMap};
use leo_errors::{CliError, Result};
use structopt::StructOpt;
use tracing::span::Span;
/// Pull dependencies specified in Leo toml.
#[derive(StructOpt, Debug)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Fetch {}
impl Command for Fetch {
/// Names of dependencies in the current branch of a dependency tree.
type Input = IndexSet<String>;
type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Fetching")
}
fn prelude(&self, context: Context) -> Result<Self::Input> {
let package_name = context.manifest()?.get_package_name();
let mut set = IndexSet::new();
set.insert(package_name);
Ok(set)
}
fn apply(self, context: Context, tree: Self::Input) -> Result<Self::Output> {
let dependencies = context
.manifest()
.map_err(|_| CliError::manifest_file_not_found())?
.get_package_dependencies();
// If program has no dependencies in the Leo.toml, exit with success.
let dependencies = match dependencies {
Some(dependencies) => dependencies,
None => return Ok(()),
};
if dependencies.is_empty() {
return Ok(());
}
let mut lock_file = LockFile::new();
self.add_dependencies(context.clone(), tree, &mut lock_file, dependencies)?;
lock_file.write_to(&context.dir()?)?;
Ok(())
}
}
impl Fetch {
/// Pulls dependencies and fills in the lock file. Also checks for
/// recursive dependencies with dependency tree.
fn add_dependencies(
&self,
context: Context,
mut tree: IndexSet<String>,
lock_file: &mut LockFile,
dependencies: IndexMap<String, Dependency>,
) -> Result<()> {
// Go through each dependency in Leo.toml and add it to the imports.
// While adding, pull dependencies of this package as well and check for recursion.
for (import_name, dependency) in dependencies.into_iter() {
let mut package = Package::from(&dependency);
package.import_name = Some(import_name);
// Pull the dependency first.
let path = Add::new(
None,
Some(package.author.clone()),
Some(package.name.clone()),
Some(package.version.clone()),
)
.apply(context.clone(), ())?;
// Try inserting a new dependency to the branch. If not inserted,
// then fail because this dependency was added on a higher level.
if !tree.insert(package.name.clone()) {
// Pretty format for the message - show dependency structure.
let mut message: Vec<String> = tree
.into_iter()
.enumerate()
.map(|(i, val)| format!("{}└─{}", " ".repeat(i * 2), val))
.collect();
message.push(format!("{}└─{} (FAILURE)", " ".repeat(message.len() * 2), package.name));
return Err(CliError::recursive_dependency_found(message.join("\n")).into());
}
// Check imported dependency's dependencies.
let imported_dependencies = create_context(path, None)?
.manifest()
.map_err(|_| CliError::unable_to_read_imported_dependency_manifest())?
.get_package_dependencies();
if let Some(dependencies) = imported_dependencies {
if !dependencies.is_empty() {
// Fill in the lock file with imported dependency and information about its dependencies.
package.add_dependencies(&dependencies);
lock_file.add_package(package);
// Recursively call this method for imported program.
self.add_dependencies(context.clone(), tree.clone(), lock_file, dependencies)?;
continue;
}
}
// If there are no dependencies for the new import, add a single record.
lock_file.add_package(package);
}
Ok(())
}
}

View File

@ -20,6 +20,9 @@ pub use add::Add;
pub mod clone;
pub use clone::Clone;
pub mod fetch;
pub use fetch::Fetch;
pub mod login;
pub use login::Login;

View File

@ -14,6 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!
// COMMAND TEMPORARILY DISABLED
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!
use crate::{commands::Command, context::Context};
use leo_errors::Result;
use leo_package::LeoPackage;

View File

@ -109,6 +109,7 @@ impl Command for Test {
output_directory.clone(),
thread_leaked_context(),
Some(self.compiler_options.clone().into()),
std::collections::HashMap::new(),
Some(self.compiler_options.clone().into()),
)?;

View File

@ -16,7 +16,7 @@
use crate::{api::Api, config};
use leo_errors::{CliError, Result};
use leo_package::root::Manifest;
use leo_package::root::{LockFile, Manifest};
use std::{convert::TryFrom, env::current_dir, path::PathBuf};
@ -41,10 +41,20 @@ impl Context {
}
}
/// Get package manifest for current context
/// Get package manifest for current context.
pub fn manifest(&self) -> Result<Manifest> {
Ok(Manifest::try_from(self.dir()?.as_path())?)
}
/// Get lock file for current context.
pub fn lock_file(&self) -> Result<LockFile> {
Ok(LockFile::try_from(self.dir()?.as_path())?)
}
/// Check if lock file exists.
pub fn lock_file_exists(&self) -> Result<bool> {
Ok(LockFile::exists_at(&self.dir()?))
}
}
/// Create a new context for the current directory.

View File

@ -22,7 +22,7 @@ pub mod logger;
pub mod updater;
use commands::{
package::{Add, Clone, Login, Logout, Publish, Remove},
package::{Clone, Fetch, Login, Logout, Publish},
Build, Clean, Command, Deploy, Init, Lint, New, Prove, Run, Setup, Test, Update, Watch,
};
use leo_errors::Result;
@ -119,10 +119,15 @@ enum CommandOpts {
command: Test,
},
#[structopt(about = "Import a package from the Aleo Package Manager")]
Add {
// #[structopt(about = "Import a package from the Aleo Package Manager")]
// Add {
// #[structopt(flatten)]
// command: Add,
// },
#[structopt(about = "Pull dependencies from Aleo Package Manager")]
Fetch {
#[structopt(flatten)]
command: Add,
command: Fetch,
},
#[structopt(about = "Clone a package from the Aleo Package Manager")]
@ -149,12 +154,11 @@ enum CommandOpts {
command: Publish,
},
#[structopt(about = "Uninstall a package from the current package")]
Remove {
#[structopt(flatten)]
command: Remove,
},
// #[structopt(about = "Uninstall a package from the current package")]
// Remove {
// #[structopt(flatten)]
// command: Remove,
// },
#[structopt(about = "Lints the Leo files in the package (*)")]
Lint {
#[structopt(flatten)]
@ -204,13 +208,13 @@ fn run_with_args(opt: Opt) -> Result<()> {
CommandOpts::Watch { command } => command.try_execute(context),
CommandOpts::Update { command } => command.try_execute(context),
CommandOpts::Add { command } => command.try_execute(context),
// CommandOpts::Add { command } => command.try_execute(context),
CommandOpts::Fetch { command } => command.try_execute(context),
CommandOpts::Clone { command } => command.try_execute(context),
CommandOpts::Login { command } => command.try_execute(context),
CommandOpts::Logout { command } => command.try_execute(context),
CommandOpts::Publish { command } => command.try_execute(context),
CommandOpts::Remove { command } => command.try_execute(context),
// CommandOpts::Remove { command } => command.try_execute(context),
CommandOpts::Lint { command } => command.try_execute(context),
CommandOpts::Deploy { command } => command.try_execute(context),
}
@ -231,6 +235,7 @@ mod cli_tests {
use crate::{run_with_args, Opt};
use leo_errors::{CliError, Result};
use snarkvm_utilities::Write;
use std::path::PathBuf;
use structopt::StructOpt;
use test_dir::{DirBuilder, FileType, TestDir};
@ -347,6 +352,7 @@ mod cli_tests {
}
#[test]
#[ignore]
fn test_import() {
let dir = testdir("test");
let path = dir.path("test");
@ -388,4 +394,32 @@ mod cli_tests {
assert!(run_cmd("leo test -f examples/silly-sudoku/src/lib.leo", path).is_ok());
assert!(run_cmd("leo test -f examples/silly-sudoku/src/main.leo", path).is_ok());
}
#[test]
fn test_install() {
let dir = testdir("test");
let path = dir.path("test");
assert!(run_cmd("leo new install", &Some(path.clone())).is_ok());
let install_path = &Some(path.join("install"));
let mut file = std::fs::OpenOptions::new()
.write(true)
.append(true)
.open(path.join("install/Leo.toml"))
.unwrap();
assert!(run_cmd("leo fetch", install_path).is_ok());
assert!(file
.write_all(
br#"
sudoku = {author = "justice-league", package = "u8u32", version = "0.1.0"}
"#
)
.is_ok());
assert!(run_cmd("leo fetch", install_path).is_ok());
assert!(run_cmd("leo build", install_path).is_ok());
}
}

View File

@ -21,6 +21,10 @@ edition = "2018"
path = "../errors"
version = "1.5.3"
[dependencies.indexmap]
version = "1.7"
features = ["serde"]
[dependencies.serde]
version = "1.0"
features = [ "derive" ]

View File

@ -4,6 +4,27 @@
[![Authors](https://img.shields.io/badge/authors-Aleo-orange.svg)](../AUTHORS)
[![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE.md)
This module defines the structure of a Leo project package.
## Description
Each file in this directory mirrors a corresponding file generated in a new Leo project package.
This module defines the structure of a Leo project package. And describes behavior of package internals, such
as Leo Manifest (Leo.toml), Lock File (Leo.lock), source files and imports.
Mainly used by Leo binary.
## Structure
Each directory in this crate mirrors a corresponding file generated in a new Leo project package:
```bash
package/src
├── errors # crate level error definitions
├── imports # program imports management
├── inputs # program inputs directory
├── outputs # program outputs directory
├── root # program root: Leo.toml, Leo.lock
└── source # source files directory
```
## Testing
Package features functional tests in the `tests/` directory.

View File

@ -0,0 +1,160 @@
// Copyright (C) 2019-2021 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::root::Dependency;
use leo_errors::{PackageError, Result};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
collections::HashMap,
convert::TryFrom,
fmt::{self, Display},
fs::File,
io::{Read, Write},
path::Path,
};
pub const LOCKFILE_FILENAME: &str = "Leo.lock";
/// Lock-file struct, contains all information about imported dependencies
/// and their relationships.
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct LockFile {
pub package: Vec<Package>,
}
/// Single dependency record.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Package {
pub name: String,
pub version: String,
pub author: String,
pub import_name: Option<String>,
#[serde(skip_serializing_if = "IndexMap::is_empty", default)]
pub dependencies: IndexMap<String, String>,
}
impl LockFile {
pub fn new() -> Self {
LockFile { package: vec![] }
}
/// Check if LockFile exists in a directory.
pub fn exists_at(path: &Path) -> bool {
let mut path = Cow::from(path);
if path.is_dir() {
path.to_mut().push(LOCKFILE_FILENAME);
}
path.exists()
}
/// Add Package record to the lock file. Chainable.
pub fn add_package(&mut self, package: Package) -> &mut Self {
self.package.push(package);
self
}
/// Print LockFile as toml.
pub fn to_string(&self) -> Result<String> {
Ok(toml::to_string(self).map_err(PackageError::failed_to_serialize_lock_file)?)
}
/// Form a HashMap of kind:
/// ``` imported_name => package_name ```
/// for all imported packages.
pub fn to_import_map(&self) -> HashMap<String, String> {
let mut result = HashMap::new();
for package in self.package.iter() {
match &package.import_name {
Some(name) => result.insert(name.clone(), package.to_string()),
None => result.insert(package.name.clone(), package.to_string()),
};
}
result
}
/// Write Leo.lock to the given location.
pub fn write_to(self, path: &Path) -> Result<()> {
let mut path = Cow::from(path);
if path.is_dir() {
path.to_mut().push(LOCKFILE_FILENAME);
}
File::create(&path)
.map_err(|error| PackageError::failed_to_create_lock_file(LOCKFILE_FILENAME, error))?
.write_all(self.to_string()?.as_bytes())
.map_err(|error| PackageError::failed_to_write_lock_file(LOCKFILE_FILENAME, error))?;
Ok(())
}
}
impl TryFrom<&Path> for LockFile {
type Error = PackageError;
fn try_from(path: &Path) -> Result<Self, Self::Error> {
let mut path = Cow::from(path);
if path.is_dir() {
path.to_mut().push(LOCKFILE_FILENAME);
}
let mut file = File::open(path.clone())
.map_err(|error| PackageError::failed_to_open_lock_file(LOCKFILE_FILENAME, error))?;
let size = file
.metadata()
.map_err(|error| PackageError::failed_to_get_lock_file_metadata(LOCKFILE_FILENAME, error))?
.len() as usize;
let mut buffer = String::with_capacity(size);
file.read_to_string(&mut buffer)
.map_err(|error| PackageError::failed_to_read_lock_file(LOCKFILE_FILENAME, error))?;
toml::from_str(&buffer).map_err(|error| PackageError::failed_to_parse_lock_file(LOCKFILE_FILENAME, error))
}
}
impl Package {
/// Fill dependencies from Leo Manifest data.
pub fn add_dependencies(&mut self, dependencies: &IndexMap<String, Dependency>) {
for (import_name, dependency) in dependencies.iter() {
self.dependencies
.insert(import_name.clone(), Package::from(dependency).to_string());
}
}
}
impl Display for Package {
/// Form an path identifier for a package. It is the path under which package is stored
/// inside the `imports/` directory.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}-{}@{}", self.author, self.name, self.version)
}
}
impl From<&Dependency> for Package {
fn from(dependency: &Dependency) -> Package {
Package {
name: dependency.package.clone(),
author: dependency.author.clone(),
version: dependency.version.clone(),
dependencies: Default::default(),
import_name: None,
}
}
}

View File

@ -17,9 +17,11 @@
use crate::package::Package;
use leo_errors::{PackageError, Result};
use indexmap::IndexMap;
use serde::Deserialize;
use std::{
borrow::Cow,
collections::HashMap,
convert::TryFrom,
fs::File,
io::{Read, Write},
@ -34,10 +36,18 @@ pub struct Remote {
pub author: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Dependency {
pub author: String,
pub version: String,
pub package: String,
}
#[derive(Deserialize)]
pub struct Manifest {
pub project: Package,
pub remote: Option<Remote>,
pub dependencies: Option<IndexMap<String, Dependency>>,
}
impl Manifest {
@ -45,6 +55,7 @@ impl Manifest {
Ok(Self {
project: Package::new(package_name)?,
remote: author.map(|author| Remote { author }),
dependencies: Some(IndexMap::<String, Dependency>::new()),
})
}
@ -72,6 +83,27 @@ impl Manifest {
self.project.description.clone()
}
pub fn get_package_dependencies(&self) -> Option<IndexMap<String, Dependency>> {
self.dependencies.clone()
}
/// Get HashMap of kind:
/// import name => import directory
/// Which then used in AST/ASG to resolve import paths.
pub fn get_imports_map(&self) -> Option<HashMap<String, String>> {
self.dependencies.clone().map(|dependencies| {
dependencies
.into_iter()
.map(|(name, dependency)| {
(
name,
format!("{}-{}@{}", dependency.author, dependency.package, dependency.version),
)
})
.collect()
})
}
pub fn get_package_license(&self) -> Option<String> {
self.project.license.clone()
}
@ -109,6 +141,14 @@ license = "MIT"
[remote]
author = "{author}" # Add your Aleo Package Manager username or team name.
[target]
curve = "bls12_377"
proving_system = "groth16"
[dependencies]
# Define dependencies here in format:
# name = {{ package = "package-name", author = "author", version = "version" }}
"#,
name = self.project.name,
author = author

View File

@ -17,6 +17,9 @@
pub mod gitignore;
pub use self::gitignore::*;
pub mod lock_file;
pub use self::lock_file::*;
pub mod manifest;
pub use self::manifest::*;

View File

@ -156,10 +156,8 @@ impl ZipFile {
/// Check if the file path should be included in the package zip file.
fn is_included(path: &Path) -> bool {
// excluded directories: `output`, `imports`
if path.ends_with(OUTPUTS_DIRECTORY_NAME.trim_end_matches('/'))
| path.ends_with(IMPORTS_DIRECTORY_NAME.trim_end_matches('/'))
{
// DO NOT include `imports` and `outputs` directories.
if path.starts_with(IMPORTS_DIRECTORY_NAME) || path.starts_with(OUTPUTS_DIRECTORY_NAME) {
return false;
}