mirror of
https://github.com/AleoHQ/leo.git
synced 2024-11-28 11:16:49 +03:00
Merge pull request #1119 from AleoHQ/leo-manifest-dependencies
[Feature] Imports Stabilization
This commit is contained in:
commit
92351d0fb3
@ -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
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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()),
|
||||
)?;
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
144
leo/commands/package/fetch.rs
Normal file
144
leo/commands/package/fetch.rs
Normal 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(())
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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()),
|
||||
)?;
|
||||
|
||||
|
@ -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.
|
||||
|
60
leo/main.rs
60
leo/main.rs
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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" ]
|
||||
|
@ -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.
|
||||
|
160
package/src/root/lock_file.rs
Normal file
160
package/src/root/lock_file.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user