Merge pull request #632 from AleoHQ/adds-structopt-to-leo

[CLI] Leo CLI is now on StructOpt and Anyhow (reopened)
This commit is contained in:
Howard Wu 2021-02-10 18:49:45 -08:00 committed by GitHub
commit 59fa8fd62b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1589 additions and 1990 deletions

View File

@ -43,6 +43,5 @@ jobs:
leo login -u "$USER" -p "$PASS" leo login -u "$USER" -p "$PASS"
leo add argus4130/xnor leo add argus4130/xnor
leo remove xnor leo remove xnor
leo clean
leo logout leo logout

42
Cargo.lock generated
View File

@ -1010,6 +1010,15 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "heck"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.17" version = "0.1.17"
@ -1388,6 +1397,7 @@ dependencies = [
name = "leo-lang" name = "leo-lang"
version = "1.2.2" version = "1.2.2"
dependencies = [ dependencies = [
"anyhow",
"clap", "clap",
"colored", "colored",
"console", "console",
@ -1416,6 +1426,7 @@ dependencies = [
"snarkvm-gadgets", "snarkvm-gadgets",
"snarkvm-models", "snarkvm-models",
"snarkvm-utilities", "snarkvm-utilities",
"structopt",
"thiserror", "thiserror",
"toml", "toml",
"tracing", "tracing",
@ -2052,6 +2063,7 @@ dependencies = [
"proc-macro-error-attr", "proc-macro-error-attr",
"proc-macro2 1.0.24", "proc-macro2 1.0.24",
"quote 1.0.7", "quote 1.0.7",
"syn 1.0.60",
"version_check", "version_check",
] ]
@ -2809,6 +2821,30 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2 1.0.24",
"quote 1.0.7",
"syn 1.0.60",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "1.0.0" version = "1.0.0"
@ -3152,6 +3188,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.8" version = "0.1.8"

View File

@ -89,6 +89,12 @@ default-features = false
[dependencies.snarkvm-utilities] [dependencies.snarkvm-utilities]
version = "0.0.3" version = "0.0.3"
[dependencies.anyhow]
version = "1.0"
[dependencies.structopt]
version = "0.3"
[dependencies.clap] [dependencies.clap]
version = "2.33.3" version = "2.33.3"

202
leo/api.rs Normal file
View File

@ -0,0 +1,202 @@
// 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 anyhow::{anyhow, Error, Result};
use reqwest::{
blocking::{Client, Response},
Method,
StatusCode,
};
use serde::Serialize;
/// Trait describes API Routes and Request bodies, struct which implements
/// Route MUST also support Serialize to be usable in Api::run_route(r: Route)
pub trait Route {
/// Whether to use bearer auth or not. Some routes may have additional
/// features for logged-in users, so authorization token should be sent
/// if it is created of course
const AUTH: bool;
/// HTTP method to use when requesting
const METHOD: Method;
/// URL path without first forward slash (e.g. v1/package/fetch)
const PATH: &'static str;
/// Output type for this route. For login it is simple - String
/// But for other routes may be more complex.
type Output;
/// Process reqwest Response and turn it into Output
fn process(&self, res: Response) -> Result<Self::Output>;
/// Transform specific status codes into correct errors for this route.
/// For example 404 on package fetch should mean that 'Package is not found'
fn status_to_err(&self, _status: StatusCode) -> Error {
anyhow!("Unidentified API error")
}
}
/// REST API handler with reqwest::blocking inside
#[derive(Clone, Debug)]
pub struct Api {
host: String,
client: Client,
/// Authorization token for API requests
auth_token: Option<String>,
}
impl Api {
/// Create new instance of API, set host and Client is going to be
/// created and set automatically
pub fn new(host: String, auth_token: Option<String>) -> Api {
Api {
client: Client::new(),
auth_token,
host,
}
}
/// Get token for bearer auth, should be passed into Api through Context
pub fn auth_token(&self) -> Option<String> {
self.auth_token.clone()
}
/// Set authorization token for future requests
pub fn set_auth_token(&mut self, token: String) {
self.auth_token = Some(token);
}
/// Run specific route struct. Turn struct into request body
/// and use type constants and Route implementation to get request params
pub fn run_route<T>(&self, route: T) -> Result<T::Output>
where
T: Route,
T: Serialize,
{
let mut res = self.client.request(T::METHOD, &format!("{}{}", self.host, T::PATH));
// add body for POST and PUT requests
if T::METHOD == Method::POST || T::METHOD == Method::PUT {
res = res.json(&route);
};
// if Route::Auth is true and token is present - pass it
if T::AUTH && self.auth_token().is_some() {
res = res.bearer_auth(&self.auth_token().unwrap());
};
// only one error is possible here
let res = res.send().map_err(|_| anyhow!("Unable to connect to Aleo PM"))?;
// where magic begins
route.process(res)
}
}
// --------------------------------------------------
// | Defining routes |
// --------------------------------------------------
/// Handler for 'fetch' route - fetch packages from Aleo PM
/// Route: POST /v1/package/fetch
#[derive(Serialize, Debug)]
pub struct Fetch {
pub author: String,
pub package_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
impl Route for Fetch {
type Output = Response;
const AUTH: bool = true;
const METHOD: Method = Method::POST;
const PATH: &'static str = "api/package/fetch";
fn process(&self, res: Response) -> Result<Self::Output> {
// check status code first
if res.status() != 200 {
return Err(self.status_to_err(res.status()));
};
Ok(res)
}
fn status_to_err(&self, status: StatusCode) -> Error {
match status {
StatusCode::BAD_REQUEST => anyhow!("Package is not found - check author and/or package name"),
// TODO: we should return 404 on not found author/package
// and return BAD_REQUEST if data format is incorrect or some of the arguments
// were not passed
StatusCode::NOT_FOUND => anyhow!("Package is hidden"),
_ => anyhow!("Unknown API error: {}", status),
}
}
}
/// Handler for 'login' route - send username and password and receive JWT
/// Route: POST /v1/account/authenticate
#[derive(Serialize)]
pub struct Login {
pub email_username: String,
pub password: String,
}
impl Route for Login {
type Output = Response;
const AUTH: bool = false;
const METHOD: Method = Method::POST;
const PATH: &'static str = "api/account/authenticate";
fn process(&self, res: Response) -> Result<Self::Output> {
if res.status() != 200 {
return Err(self.status_to_err(res.status()));
}
Ok(res)
}
fn status_to_err(&self, status: StatusCode) -> Error {
match status {
StatusCode::BAD_REQUEST => anyhow!("This username is not yet registered or the password is incorrect"),
// TODO: NOT_FOUND here should be replaced, this error code has no relation to what this route is doing
StatusCode::NOT_FOUND => anyhow!("Incorrect password"),
_ => anyhow!("Unknown API error: {}", status),
}
}
}
/// Handler for 'my_profile' route. Meant to be used to get profile details but
/// in current application is used to check if user is logged in. Any non-200 response
/// is treated as Unauthorized
#[derive(Serialize)]
pub struct Profile {}
impl Route for Profile {
type Output = bool;
const AUTH: bool = true;
const METHOD: Method = Method::GET;
const PATH: &'static str = "api/account/my_profile";
fn process(&self, res: Response) -> Result<Self::Output> {
// this may be extended for more precise error handling
Ok(res.status() == 200)
}
}

View File

@ -1,127 +0,0 @@
// 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::{cli_types::*, errors::CLIError, logger, updater::Updater};
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
pub trait CLI {
type Options;
type Output;
const ABOUT: AboutType;
const ARGUMENTS: &'static [ArgumentType];
const FLAGS: &'static [FlagType];
const NAME: NameType;
const OPTIONS: &'static [OptionType];
const SUBCOMMANDS: &'static [SubCommandType];
#[allow(clippy::new_ret_no_self)]
#[cfg_attr(tarpaulin, skip)]
fn new<'a, 'b>() -> App<'a, 'b> {
let arguments = &Self::ARGUMENTS
.iter()
.map(|a| {
let mut args = Arg::with_name(a.0).help(a.1).required(a.3).index(a.4);
if !a.2.is_empty() {
args = args.possible_values(a.2);
}
args
})
.collect::<Vec<Arg<'static, 'static>>>();
let flags = &Self::FLAGS
.iter()
.map(|a| Arg::from_usage(a))
.collect::<Vec<Arg<'static, 'static>>>();
let options = &Self::OPTIONS
.iter()
.map(|a| match !a.2.is_empty() {
true => Arg::from_usage(a.0)
.conflicts_with_all(a.1)
.possible_values(a.2)
.requires_all(a.3),
false => Arg::from_usage(a.0).conflicts_with_all(a.1).requires_all(a.3),
})
.collect::<Vec<Arg<'static, 'static>>>();
let subcommands = Self::SUBCOMMANDS.iter().map(|s| {
SubCommand::with_name(s.0)
.about(s.1)
.args(
&s.2.iter()
.map(|a| {
let mut args = Arg::with_name(a.0).help(a.1).required(a.3).index(a.4);
if !a.2.is_empty() {
args = args.possible_values(a.2);
}
args
})
.collect::<Vec<Arg<'static, 'static>>>(),
)
.args(
&s.3.iter()
.map(|a| Arg::from_usage(a))
.collect::<Vec<Arg<'static, 'static>>>(),
)
.args(
&s.4.iter()
.map(|a| match !a.2.is_empty() {
true => Arg::from_usage(a.0)
.conflicts_with_all(a.1)
.possible_values(a.2)
.requires_all(a.3),
false => Arg::from_usage(a.0).conflicts_with_all(a.1).requires_all(a.3),
})
.collect::<Vec<Arg<'static, 'static>>>(),
)
.settings(s.5)
});
SubCommand::with_name(Self::NAME)
.about(Self::ABOUT)
.settings(&[
AppSettings::ColoredHelp,
AppSettings::DisableHelpSubcommand,
AppSettings::DisableVersion,
])
.args(arguments)
.args(flags)
.args(options)
.subcommands(subcommands)
}
#[cfg_attr(tarpaulin, skip)]
fn process(arguments: &ArgMatches) -> Result<(), CLIError> {
// Set logging environment
match arguments.is_present("debug") {
true => logger::init_logger("leo", 2),
false => logger::init_logger("leo", 1),
}
if arguments.subcommand().0 != "update" {
Updater::print_cli();
}
let options = Self::parse(arguments)?;
let _output = Self::output(options)?;
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
fn parse(arguments: &ArgMatches) -> Result<Self::Options, CLIError>;
#[cfg_attr(tarpaulin, skip)]
fn output(options: Self::Options) -> Result<Self::Output, CLIError>;
}

View File

@ -1,53 +0,0 @@
// 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 clap::AppSettings;
pub type NameType = &'static str;
pub type AboutType = &'static str;
pub type DescriptionType = &'static str;
pub type RequiredType = bool;
pub type PossibleValuesType = &'static [&'static str];
pub type IndexType = u64;
pub type ArgumentType = (NameType, DescriptionType, PossibleValuesType, RequiredType, IndexType);
// Format
// "[flag] -f --flag 'Add flag description here'"
pub type FlagType = &'static str;
// Format
// (argument, conflicts, possible_values, requires)
pub type OptionType = (
&'static str,
&'static [&'static str],
&'static [&'static str],
&'static [&'static str],
);
pub type SubCommandType = (
NameType,
AboutType,
&'static [ArgumentType],
&'static [FlagType],
&'static [OptionType],
&'static [AppSettings],
);

View File

@ -1,198 +0,0 @@
// 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/>.
//
// Usage:
//
// leo add -a author -p package_name -v version
// leo add -a author -p package_name
//
use crate::{
cli::CLI,
cli_types::*,
config::*,
errors::{AddError::*, CLIError},
};
use leo_package::{
imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME},
root::Manifest,
};
use std::{
collections::HashMap,
convert::TryFrom,
env::current_dir,
fs::{create_dir_all, File},
io::{Read, Write},
};
pub const ADD_URL: &str = "v1/package/fetch";
#[derive(Debug)]
pub struct AddCommand;
impl CLI for AddCommand {
// Format: author, package_name, version
type Options = (Option<String>, Option<String>, Option<String>);
type Output = ();
const ABOUT: AboutType = "Install a package from the Aleo Package Manager";
const ARGUMENTS: &'static [ArgumentType] = &[
// (name, description, possible_values, required, index)
(
"REMOTE",
"Install a package from the Aleo Package Manager with the given remote",
&[],
false,
1u64,
),
];
const FLAGS: &'static [FlagType] = &[];
const NAME: NameType = "add";
const OPTIONS: &'static [OptionType] = &[
// (argument, conflicts, possible_values, requires)
("[author] -a --author=<author> 'Specify a package author'", &[], &[], &[
"package",
]),
(
"[package] -p --package=<package> 'Specify a package name'",
&[],
&[],
&["author"],
),
(
"[version] -v --version=[version] 'Specify a package version'",
&[],
&[],
&["author", "package"],
),
];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
fn parse(arguments: &clap::ArgMatches) -> Result<Self::Options, crate::errors::CLIError> {
// TODO update to new package manager API without an author field
if arguments.is_present("author") && arguments.is_present("package") {
return Ok((
arguments.value_of("author").map(|s| s.to_string()),
arguments.value_of("package").map(|s| s.to_string()),
arguments.value_of("version").map(|s| s.to_string()),
));
}
match arguments.value_of("REMOTE") {
Some(remote) => {
let values: Vec<&str> = remote.split('/').collect();
if values.len() != 2 {
return Err(InvalidRemote.into());
}
let author = values[0].to_string();
let package = values[1].to_string();
Ok((Some(author), Some(package), None))
}
None => Ok((None, None, None)),
}
}
fn output(options: Self::Options) -> Result<Self::Output, CLIError> {
// Begin "Adding" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Adding");
let _enter = span.enter();
let path = current_dir()?;
// Enforce that the current directory is a leo package
Manifest::try_from(path.as_path())?;
let (response, package_name) = match options {
(Some(author), Some(package_name), version) => {
let client = reqwest::blocking::Client::new();
let url = format!("{}{}", PACKAGE_MANAGER_URL, ADD_URL);
let mut json = HashMap::new();
json.insert("author", author);
json.insert("package_name", package_name.clone());
if let Some(version) = version {
json.insert("version", version);
}
let result = match read_token() {
Ok(token) => {
tracing::info!("Logged in, using token to authorize");
client.post(&url).bearer_auth(token)
}
Err(_) => client.post(&url),
}
.json(&json)
.send();
match result {
Ok(response) => (response, package_name),
//Cannot connect to the server
Err(_error) => {
return Err(
ConnectionUnavailable("Could not connect to the Aleo Package Manager".into()).into(),
);
}
}
}
_ => return Err(MissingAuthorOrPackageName.into()),
};
let mut path = current_dir()?;
ImportsDirectory::create(&path)?;
path.push(IMPORTS_DIRECTORY_NAME);
path.push(package_name);
create_dir_all(&path)?;
let bytes = response.bytes()?;
let reader = std::io::Cursor::new(bytes);
let mut zip_arhive = match zip::ZipArchive::new(reader) {
Ok(zip) => zip,
Err(error) => return Err(ZipError(error.to_string().into()).into()),
};
for i in 0..zip_arhive.len() {
let file = match zip_arhive.by_index(i) {
Ok(file) => file,
Err(error) => return Err(ZipError(error.to_string().into()).into()),
};
let file_name = file.name();
let mut file_path = path.clone();
file_path.push(file_name);
if file_name.ends_with('/') {
create_dir_all(file_path)?;
} else {
if let Some(parent_directory) = path.parent() {
create_dir_all(parent_directory)?;
}
File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::<Vec<u8>>())?;
}
}
tracing::info!("Successfully added a package\n");
Ok(())
}
}

View File

@ -15,55 +15,50 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{ use crate::{
cli::*, commands::Command,
cli_types::*, context::Context,
errors::CLIError,
synthesizer::{CircuitSynthesizer, SerializedCircuit}, synthesizer::{CircuitSynthesizer, SerializedCircuit},
}; };
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
use leo_package::{ use leo_package::{
inputs::*, inputs::*,
outputs::{ChecksumFile, CircuitFile, OutputsDirectory, OUTPUTS_DIRECTORY_NAME}, outputs::{ChecksumFile, CircuitFile, OutputsDirectory, OUTPUTS_DIRECTORY_NAME},
root::Manifest,
source::{LibraryFile, MainFile, LIBRARY_FILENAME, MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, source::{LibraryFile, MainFile, LIBRARY_FILENAME, MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
}; };
use anyhow::Result;
use snarkvm_curves::{bls12_377::Bls12_377, edwards_bls12::Fq}; use snarkvm_curves::{bls12_377::Bls12_377, edwards_bls12::Fq};
use snarkvm_models::gadgets::r1cs::ConstraintSystem; use snarkvm_models::gadgets::r1cs::ConstraintSystem;
use std::convert::TryFrom;
use structopt::StructOpt;
use tracing::span::Span;
use clap::ArgMatches; /// Compile and build program command
use std::{convert::TryFrom, env::current_dir, time::Instant}; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Build {}
#[derive(Debug)] impl Build {
pub struct BuildCommand; pub fn new() -> Build {
Build {}
}
}
impl CLI for BuildCommand { impl Command for Build {
type Options = (); type Input = ();
type Output = Option<(Compiler<Fq, EdwardsGroupType>, bool)>; type Output = Option<(Compiler<Fq, EdwardsGroupType>, bool)>;
const ABOUT: AboutType = "Compile the current package as a program"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Build")
const FLAGS: &'static [FlagType] = &[]; }
const NAME: NameType = "build";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(()) Ok(())
} }
#[cfg_attr(tarpaulin, skip)] fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output> {
fn output(_options: Self::Options) -> Result<Self::Output, CLIError> { let path = ctx.dir()?;
// Begin "Compiling" context for console logging let package_name = ctx.manifest()?.get_package_name();
let span = tracing::span!(tracing::Level::INFO, "Compiling");
let enter = span.enter();
let path = current_dir()?;
// Get the package name
let manifest = Manifest::try_from(path.as_path())?;
let package_name = manifest.get_package_name();
// Sanitize the package path to the root directory // Sanitize the package path to the root directory
let mut package_path = path.clone(); let mut package_path = path.clone();
@ -77,9 +72,6 @@ impl CLI for BuildCommand {
tracing::info!("Starting..."); tracing::info!("Starting...");
// Start the timer
let start = Instant::now();
// Compile the package starting with the lib.leo file // Compile the package starting with the lib.leo file
if LibraryFile::exists_at(&package_path) { if LibraryFile::exists_at(&package_path) {
// Construct the path to the library file in the source directory // Construct the path to the library file in the source directory
@ -185,21 +177,9 @@ impl CLI for BuildCommand {
tracing::info!("Complete"); tracing::info!("Complete");
// Drop "Compiling" context for console logging
drop(enter);
// Begin "Done" context for console logging todo: @collin figure a way to get this output with tracing without dropping span
tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
tracing::info!("Finished in {} milliseconds\n", start.elapsed().as_millis());
});
return Ok(Some((program, checksum_differs))); return Ok(Some((program, checksum_differs)));
} }
drop(enter);
// Return None when compiling a package for publishing
// The published package does not need to have a main.leo
Ok(None) Ok(None)
} }
} }

View File

@ -14,45 +14,40 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{cli::*, cli_types::*, errors::CLIError}; use crate::{commands::Command, context::Context};
use leo_package::{
outputs::{ChecksumFile, ProofFile, ProvingKeyFile, VerificationKeyFile},
root::Manifest,
};
use clap::ArgMatches;
use leo_compiler::OutputFile; use leo_compiler::OutputFile;
use leo_package::outputs::CircuitFile; use leo_package::outputs::{ChecksumFile, CircuitFile, ProofFile, ProvingKeyFile, VerificationKeyFile};
use std::{convert::TryFrom, env::current_dir};
#[derive(Debug)] use anyhow::Result;
pub struct CleanCommand; use structopt::StructOpt;
use tracing::span::Span;
impl CLI for CleanCommand { /// Clean outputs folder command
type Options = (); #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Clean {}
impl Clean {
pub fn new() -> Clean {
Clean {}
}
}
impl Command for Clean {
type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Clean the output directory"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Cleaning")
const FLAGS: &'static [FlagType] = &[]; }
const NAME: NameType = "clean";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(()) Ok(())
} }
#[cfg_attr(tarpaulin, skip)] fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output> {
fn output(_options: Self::Options) -> Result<Self::Output, CLIError> { let path = ctx.dir()?;
// Begin "Clean" context for console logging let package_name = ctx.manifest()?.get_package_name();
let span = tracing::span!(tracing::Level::INFO, "Cleaning");
let enter = span.enter();
// Get the package name
let path = current_dir()?;
let package_name = Manifest::try_from(path.as_path())?.get_package_name();
// Remove the checksum from the output directory // Remove the checksum from the output directory
ChecksumFile::new(&package_name).remove(&path)?; ChecksumFile::new(&package_name).remove(&path)?;
@ -72,14 +67,6 @@ impl CLI for CleanCommand {
// Remove the proof from the output directory // Remove the proof from the output directory
ProofFile::new(&package_name).remove(&path)?; ProofFile::new(&package_name).remove(&path)?;
// Drop "Compiling" context for console logging
drop(enter);
// Begin "Done" context for console logging
tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
tracing::info!("Program workspace cleaned\n");
});
Ok(()) Ok(())
} }
} }

View File

@ -14,65 +14,36 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{ use crate::{commands::Command, context::Context};
cli::*,
cli_types::*,
commands::BuildCommand,
errors::{CLIError, RunError},
};
use leo_package::{
root::Manifest,
source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
};
use clap::ArgMatches; use anyhow::Result;
use std::{convert::TryFrom, env::current_dir}; use structopt::StructOpt;
use tracing::span::Span;
#[derive(Debug)] /// Deploy Leo program to the network
pub struct DeployCommand; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Deploy {}
impl CLI for DeployCommand { impl Deploy {
type Options = (); pub fn new() -> Deploy {
Deploy {}
}
}
impl Command for Deploy {
type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Deploy the current package as a program to the network (*)"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Deploy")
const FLAGS: &'static [FlagType] = &[]; }
const NAME: NameType = "deploy";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(()) Ok(())
} }
#[cfg_attr(tarpaulin, skip)] fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> {
fn output(options: Self::Options) -> Result<Self::Output, CLIError> { unimplemented!("Deploy command has not been implemented yet");
// Begin "Deploy" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Deploying");
let _enter = span.enter();
let path = current_dir()?;
match BuildCommand::output(options)? {
Some((_program, _checksum_differs)) => {
// Get the package name
let _package_name = Manifest::try_from(path.as_path())?.get_package_name();
tracing::error!("Unimplemented - `leo deploy`");
Ok(())
}
None => {
let mut main_file_path = path;
main_file_path.push(SOURCE_DIRECTORY_NAME);
main_file_path.push(MAIN_FILENAME);
Err(CLIError::RunError(RunError::MainFileDoesNotExist(
main_file_path.into_os_string(),
)))
}
}
} }
} }

View File

@ -14,58 +14,50 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{ use crate::{commands::Command, context::Context};
cli::*,
cli_types::*,
errors::{CLIError, InitError},
};
use leo_package::LeoPackage; use leo_package::LeoPackage;
use clap::ArgMatches; use anyhow::{anyhow, Result};
use std::env::current_dir; use std::env::current_dir;
use structopt::StructOpt;
use tracing::span::Span;
#[derive(Debug)] /// Init Leo project command within current directory
pub struct InitCommand; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Init {}
impl CLI for InitCommand { impl Init {
type Options = bool; pub fn new() -> Init {
type Output = (); Init {}
}
const ABOUT: AboutType = "Create a new Leo package in an existing directory";
const ARGUMENTS: &'static [ArgumentType] = &[];
const FLAGS: &'static [FlagType] = &[("--lib"), ("--bin")];
const NAME: NameType = "init";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(arguments.is_present("lib"))
} }
#[cfg_attr(tarpaulin, skip)] impl Command for Init {
fn output(options: Self::Options) -> Result<Self::Output, CLIError> { type Input = ();
// Begin "Initializing" context for console logging type Output = ();
let span = tracing::span!(tracing::Level::INFO, "Initializing");
let _enter = span.enter();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Initializing")
}
fn prelude(&self) -> Result<Self::Input> {
Ok(())
}
fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> {
let path = current_dir()?; let path = current_dir()?;
// Derive the package name
let package_name = path let package_name = path
.file_stem() .file_stem()
.ok_or_else(|| InitError::ProjectNameInvalid(path.as_os_str().to_owned()))? .ok_or_else(|| anyhow!("Project name invalid"))?
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
// Verify the directory does not exist
if !path.exists() { if !path.exists() {
return Err(InitError::DirectoryDoesNotExist(path.as_os_str().to_owned()).into()); return Err(anyhow!("Directory does not exist"));
} }
LeoPackage::initialize(&package_name, options, &path)?; LeoPackage::initialize(&package_name, false, &path)?;
tracing::info!("Successfully initialized package \"{}\"\n", package_name);
Ok(()) Ok(())
} }

View File

@ -14,65 +14,36 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{ use crate::{commands::Command, context::Context};
cli::*,
cli_types::*,
commands::BuildCommand,
errors::{CLIError, RunError},
};
use leo_package::{
root::Manifest,
source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
};
use clap::ArgMatches; use anyhow::Result;
use std::{convert::TryFrom, env::current_dir}; use structopt::StructOpt;
use tracing::span::Span;
#[derive(Debug)] /// Lint Leo code command
pub struct LintCommand; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Lint {}
impl CLI for LintCommand { impl Lint {
type Options = (); pub fn new() -> Lint {
Lint {}
}
}
impl Command for Lint {
type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Lints the Leo files in the package (*)"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Linting")
const FLAGS: &'static [FlagType] = &[]; }
const NAME: NameType = "lint";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(()) Ok(())
} }
#[cfg_attr(tarpaulin, skip)] fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> {
fn output(options: Self::Options) -> Result<Self::Output, CLIError> { unimplemented!("Lint command has not been implemented yet");
// Begin "Linting" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Linting");
let _enter = span.enter();
let path = current_dir()?;
match BuildCommand::output(options)? {
Some((_program, _checksum_differs)) => {
// Get the package name
let _package_name = Manifest::try_from(path.as_path())?.get_package_name();
tracing::error!("Unimplemented - `leo lint`");
Ok(())
}
None => {
let mut main_file_path = path;
main_file_path.push(SOURCE_DIRECTORY_NAME);
main_file_path.push(MAIN_FILENAME);
Err(CLIError::RunError(RunError::MainFileDoesNotExist(
main_file_path.into_os_string(),
)))
}
}
} }
} }

View File

@ -1,142 +0,0 @@
// 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/>.
//
// Usage:
//
// leo login <token>
// leo login -u username -p password
//
use crate::{
cli::CLI,
cli_types::*,
config::*,
errors::{CLIError, LoginError::*},
};
use std::collections::HashMap;
pub const LOGIN_URL: &str = "v1/account/authenticate";
pub const PROFILE_URL: &str = "v1/account/my_profile";
#[derive(Debug)]
pub struct LoginCommand;
impl CLI for LoginCommand {
// Format: token, username, password
type Options = (Option<String>, Option<String>, Option<String>);
type Output = String;
const ABOUT: AboutType = "Login to the Aleo Package Manager";
const ARGUMENTS: &'static [ArgumentType] = &[
// (name, description, possible_values, required, index)
(
"NAME",
"Sets the authentication token for login to the package manager",
&[],
false,
1u64,
),
];
const FLAGS: &'static [FlagType] = &[];
const NAME: NameType = "login";
const OPTIONS: &'static [OptionType] = &[
// (argument, conflicts, possible_values, requires)
("[username] -u --user=[username] 'Sets a username'", &[], &[], &[]),
("[password] -p --password=[password] 'Sets a password'", &[], &[], &[]),
];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
fn parse(arguments: &clap::ArgMatches) -> Result<Self::Options, crate::errors::CLIError> {
if arguments.is_present("username") && arguments.is_present("password") {
return Ok((
None,
Some(arguments.value_of("username").unwrap().to_string()),
Some(arguments.value_of("password").unwrap().to_string()),
));
}
match arguments.value_of("NAME") {
Some(name) => Ok((Some(name.to_string()), None, None)),
None => Ok((None, None, None)),
}
}
fn output(options: Self::Options) -> Result<Self::Output, CLIError> {
// Begin "Login" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Login");
let _enter = span.enter();
let token = match options {
// Login using existing token
(Some(token), _, _) => Some(token),
// Login using username and password
(None, Some(username), Some(password)) => {
// prepare JSON data to be sent
let mut json = HashMap::new();
json.insert("email_username", username);
json.insert("password", password);
let client = reqwest::blocking::Client::new();
let url = format!("{}{}", PACKAGE_MANAGER_URL, LOGIN_URL);
let response: HashMap<String, String> = match client.post(&url).json(&json).send() {
Ok(result) => match result.json() {
Ok(json) => json,
Err(_error) => {
return Err(WrongLoginOrPassword.into());
}
},
//Cannot connect to the server
Err(_error) => {
return Err(NoConnectionFound.into());
}
};
match response.get("token") {
Some(token) => Some(token.clone()),
None => {
return Err(CannotGetToken.into());
}
}
}
// Login using stored JWT credentials.
// TODO (raychu86) Package manager re-authentication from token
(_, _, _) => {
let token = read_token().map_err(|_| -> CLIError { NoCredentialsProvided.into() })?;
Some(token)
}
};
match token {
Some(token) => {
write_token(token.as_str())?;
tracing::info!("success");
Ok(token)
}
_ => {
tracing::error!("Failed to login. Please run `leo login -h` for help.");
Err(NoCredentialsProvided.into())
}
}
}
}

View File

@ -14,53 +14,125 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
pub mod add; use crate::context::{get_context, Context};
pub use self::add::*;
use anyhow::Result;
use std::time::Instant;
use tracing::span::Span;
// local program commands
pub mod build; pub mod build;
pub use self::build::*; pub use build::Build;
pub mod clean; pub mod clean;
pub use self::clean::*; pub use clean::Clean;
pub mod deploy; pub mod deploy;
pub use self::deploy::*; pub use deploy::Deploy;
pub mod init; pub mod init;
pub use self::init::*; pub use init::Init;
pub mod lint; pub mod lint;
pub use self::lint::*; pub use lint::Lint;
pub mod login;
pub use self::login::*;
pub mod logout;
pub use self::logout::*;
pub mod new; pub mod new;
pub use self::new::*; pub use new::New;
pub mod prove; pub mod prove;
pub use self::prove::*; pub use prove::Prove;
pub mod publish;
pub use self::publish::*;
pub mod run; pub mod run;
pub use self::run::*; pub use run::Run;
pub mod setup; pub mod setup;
pub use self::setup::*; pub use setup::Setup;
pub mod test; pub mod test;
pub use self::test::*; pub use test::Test;
pub mod remove;
pub use self::remove::*;
pub mod update; pub mod update;
pub use self::update::*; pub use update::{Sub as UpdateAutomatic, Update};
pub mod watch; pub mod watch;
pub use self::watch::*; pub use watch::Watch;
// Aleo PM related commands
pub mod package;
/// Base trait for Leo CLI, see methods and their documentation for details
pub trait Command {
/// If current command requires running another command before
/// and needs its output results, this is the place to set.
/// Example: type Input: <CommandA as Command>::Out
type Input;
/// Define output of the command to be reused as an Input for another
/// command. If this command is not used as a prelude for another, keep empty
type Output;
/// Returns project context, currently keeping it simple but it is possible
/// that in the future leo will not depend on current directory, and we're keeping
/// option for extending current core
fn context(&self) -> Result<Context> {
get_context()
}
/// Add span to the logger tracing::span.
/// Due to specifics of macro implementation it is impossible to set
/// span name with non-literal i.e. dynamic variable even if this
/// variable is &'static str
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
/// Run prelude and get Input for current command. As simple as that.
/// But due to inability to pass default implementation of a type, this
/// method must be present in every trait implementation.
fn prelude(&self) -> Result<Self::Input>
where
Self: std::marker::Sized;
/// Core of the execution - do what is necessary. This function is run within
/// context of 'execute' function, which sets logging and timers
fn apply(self, ctx: Context, input: Self::Input) -> Result<Self::Output>
where
Self: std::marker::Sized;
/// Wrapper around apply function, sets up tracing, time tracking and context
fn execute(self) -> Result<Self::Output>
where
Self: std::marker::Sized,
{
let input = self.prelude()?;
// create span for this command
let span = self.log_span();
let span = span.enter();
// calculate execution time for each run
let timer = Instant::now();
let context = self.context()?;
let out = self.apply(context, input);
drop(span);
// use done context to print time
tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
tracing::info!("Finished in {} milliseconds \n", timer.elapsed().as_millis());
});
out
}
/// Execute command but empty the result. Comes in handy where there's a
/// need to make match arms compatible while keeping implementation-specific
/// output possible. Errors however are all of the type Error
fn try_execute(self) -> Result<()>
where
Self: std::marker::Sized,
{
self.execute().map(|_| Ok(()))?
}
}

View File

@ -14,81 +14,56 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{ use crate::{commands::Command, context::Context};
cli::*,
cli_types::*,
errors::{CLIError, NewError},
};
use leo_package::LeoPackage; use leo_package::LeoPackage;
use clap::ArgMatches; use anyhow::{anyhow, Result};
use std::{env::current_dir, fs}; use std::{env::current_dir, fs};
use structopt::StructOpt;
use tracing::span::Span;
#[derive(Debug)] /// Create new Leo project
pub struct NewCommand; #[derive(StructOpt, Debug)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct New {
#[structopt(name = "NAME", help = "Set package name")]
name: String,
}
impl CLI for NewCommand { impl New {
type Options = (Option<String>, bool); pub fn new(name: String) -> New {
New { name }
}
}
impl Command for New {
type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Create a new Leo package in a new directory"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[ tracing::span!(tracing::Level::INFO, "New")
// (name, description, possible_values, required, index)
(
"NAME",
"Sets the resulting package name, defaults to the directory name",
&[],
true,
1u64,
),
];
const FLAGS: &'static [FlagType] = &[("--lib"), ("--bin")];
const NAME: NameType = "new";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
let is_lib = arguments.is_present("lib");
match arguments.value_of("NAME") {
Some(name) => Ok((Some(name.to_string()), is_lib)),
None => Ok((None, is_lib)),
}
} }
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn output(options: Self::Options) -> Result<Self::Output, CLIError> { Ok(())
// Begin "Initializing" context for console logging }
let span = tracing::span!(tracing::Level::INFO, "Initializing");
let _enter = span.enter();
fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> {
let mut path = current_dir()?; let mut path = current_dir()?;
let package_name = self.name;
// Derive the package name
let package_name = match options.0 {
Some(name) => name,
None => path
.file_stem()
.ok_or_else(|| NewError::ProjectNameInvalid(path.as_os_str().to_owned()))?
.to_string_lossy()
.to_string(),
};
// Derive the package directory path // Derive the package directory path
path.push(&package_name); path.push(&package_name);
// Verify the package directory path does not exist yet // Verify the package directory path does not exist yet
if path.exists() { if path.exists() {
return Err(NewError::DirectoryAlreadyExists(path.as_os_str().to_owned()).into()); return Err(anyhow!("Directory already exists {:?}", path));
} }
// Create the package directory // Create the package directory
fs::create_dir_all(&path) fs::create_dir_all(&path).map_err(|err| anyhow!("Could not create directory {}", err))?;
.map_err(|error| NewError::CreatingRootDirectory(path.as_os_str().to_owned(), error))?;
LeoPackage::initialize(&package_name, options.1, &path)?; LeoPackage::initialize(&package_name, false, &path)?;
tracing::info!("Successfully initialized package \"{}\"\n", package_name);
Ok(()) Ok(())
} }

156
leo/commands/package/add.rs Normal file
View File

@ -0,0 +1,156 @@
// 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::{api::Fetch, commands::Command, context::Context};
use leo_package::imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME};
use anyhow::{anyhow, Result};
use std::{
fs::{create_dir_all, File},
io::{Read, Write},
};
use structopt::StructOpt;
use tracing::Span;
/// Add package from Aleo Package Manager
#[derive(StructOpt, Debug)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Add {
#[structopt(name = "REMOTE")]
remote: Option<String>,
#[structopt(name = "author", help = "Specify a package author", long = "author", short = "a")]
author: Option<String>,
#[structopt(name = "package", help = "Specify a package name", long = "package", short = "p")]
package: Option<String>,
#[structopt(name = "version", help = "Specify a package version", long = "version", short = "v")]
version: Option<String>,
}
impl Add {
pub fn new(
remote: Option<String>,
author: Option<String>,
package: Option<String>,
version: Option<String>,
) -> Add {
Add {
remote,
author,
package,
version,
}
}
/// Try to parse author/package string from self.remote
pub fn try_read_arguments(&self) -> Result<(String, String)> {
if let Some(val) = &self.remote {
let v: Vec<&str> = val.split('/').collect();
if v.len() == 2 {
Ok((v[0].to_string(), v[1].to_string()))
} else {
Err(anyhow!(
"Incorrect argument, please use --help for information on command use"
))
}
} else if let (Some(author), Some(package)) = (&self.author, &self.package) {
Ok((author.clone(), package.clone()))
} else {
Err(anyhow!(
"Incorrect argument, please use --help for information on command use"
))
}
}
}
impl Command for Add {
type Input = ();
type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Adding")
}
fn prelude(&self) -> Result<Self::Input> {
Ok(())
}
fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output> {
// checking that manifest exists...
if ctx.manifest().is_err() {
return Err(anyhow!("Package Manifest not found, try running leo init or leo new"));
};
let (author, package_name) = match self.try_read_arguments() {
Ok((author, package)) => (author, package),
Err(err) => return Err(err),
};
let version = self.version;
// build request body (Options are skipped when sealizing)
let fetch = Fetch {
author,
package_name: package_name.clone(),
version,
};
let bytes = ctx.api.run_route(fetch)?.bytes()?;
let mut path = ctx.dir()?;
{
// setup directory structure since request was success
ImportsDirectory::create(&path)?;
path.push(IMPORTS_DIRECTORY_NAME);
path.push(package_name);
create_dir_all(&path)?;
};
let reader = std::io::Cursor::new(bytes);
let mut zip_archive = match zip::ZipArchive::new(reader) {
Ok(zip) => zip,
Err(error) => return Err(anyhow!(error)),
};
for i in 0..zip_archive.len() {
let file = match zip_archive.by_index(i) {
Ok(file) => file,
Err(error) => return Err(anyhow!(error)),
};
let file_name = file.name();
let mut file_path = path.clone();
file_path.push(file_name);
if file_name.ends_with('/') {
create_dir_all(file_path)?;
} else {
if let Some(parent_directory) = path.parent() {
create_dir_all(parent_directory)?;
}
File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::<Vec<u8>>())?;
}
}
tracing::info!("Successfully added a package");
Ok(())
}
}

View File

@ -0,0 +1,115 @@
// 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::{
api::{Login as LoginRoute, Profile as ProfileRoute},
commands::Command,
config::*,
context::Context,
};
use anyhow::{anyhow, Result};
use std::collections::HashMap;
use structopt::StructOpt;
use tracing::Span;
/// Login to Aleo PM and store credentials locally
#[derive(StructOpt, Debug)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Login {
#[structopt(name = "AUTH_TOKEN", about = "Pass authorization token")]
token: Option<String>,
#[structopt(short = "u", long = "user", about = "Username for Aleo PM")]
user: Option<String>,
#[structopt(short = "p", long = "password", about = "Password for Aleo PM")]
pass: Option<String>,
}
impl Login {
pub fn new(token: Option<String>, user: Option<String>, pass: Option<String>) -> Login {
Login { token, user, pass }
}
}
impl Command for Login {
type Input = ();
type Output = String;
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Login")
}
fn prelude(&self) -> Result<Self::Input> {
Ok(())
}
fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output> {
// quick hack to check if user is already logged in. ;)
if ctx.api.auth_token().is_some() {
tracing::info!("You are already logged in");
return Ok(ctx.api.auth_token().unwrap());
};
let mut api = ctx.api;
// ...or trying to use arguments to either get token or user-pass
let token = match (self.token, self.user, self.pass) {
// Login using existing token, use get_profile route for that
(Some(token), _, _) => {
tracing::info!("Token passed, checking...");
api.set_auth_token(token.clone());
let is_ok = api.run_route(ProfileRoute {})?;
if !is_ok {
return Err(anyhow!("Supplied token is incorrect"));
};
token
}
// Login using username and password
(None, Some(email_username), Some(password)) => {
let login = LoginRoute {
email_username,
password,
};
let res = api.run_route(login)?;
let mut res: HashMap<String, String> = res.json()?;
let tok_opt = res.remove("token");
if tok_opt.is_none() {
return Err(anyhow!("Unable to get token"));
};
tok_opt.unwrap()
}
// In case token or login/pass were not passed as arguments
(_, _, _) => return Err(anyhow!("No credentials provided")),
};
// write token either after logging or if it was passed
write_token(token.as_str())?;
tracing::info!("Success! You are now logged in!");
Ok(token)
}
}

View File

@ -14,40 +14,37 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
// use crate::{commands::Command, config::remove_token, context::Context};
// Usage:
//
// leo logout
//
#[derive(Debug)] use anyhow::Result;
pub struct LogoutCommand;
use crate::{cli::CLI, cli_types::*, config::remove_token, errors::CLIError};
use std::io::ErrorKind; use std::io::ErrorKind;
use structopt::StructOpt;
use tracing::Span;
impl CLI for LogoutCommand { /// Remove credentials for Aleo PM from .leo directory
type Options = (); #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Logout {}
impl Logout {
pub fn new() -> Logout {
Logout {}
}
}
impl Command for Logout {
type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Logout from Aleo Package Manager"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Logout")
const FLAGS: &'static [FlagType] = &[]; }
const NAME: NameType = "logout";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
/// no options and no arguments for this buddy fn prelude(&self) -> Result<Self::Input> {
fn parse(_: &clap::ArgMatches) -> Result<Self::Options, CLIError> {
Ok(()) Ok(())
} }
/// as simple as it could be - remove credentials file fn apply(self, _ctx: Context, _: Self::Input) -> Result<Self::Output> {
fn output(_: Self::Options) -> Result<Self::Output, CLIError> {
// we gotta do something about this span issue :confused:
let span = tracing::span!(tracing::Level::INFO, "Logout");
let _ent = span.enter();
// the only error we're interested here is NotFound // the only error we're interested here is NotFound
// however err in this case can also be of kind PermissionDenied or other // however err in this case can also be of kind PermissionDenied or other
if let Err(err) = remove_token() { if let Err(err) = remove_token() {

View File

@ -14,11 +14,19 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
pub mod cli; pub mod add;
pub use self::cli::*; pub use add::Add;
pub mod commands; pub mod login;
pub use self::commands::*; pub use login::Login;
pub mod updater; pub mod logout;
pub use self::updater::*; pub use logout::Logout;
pub mod publish;
pub use publish::Publish;
pub mod remove;
pub use remove::Remove;
pub use super::*;

View File

@ -14,29 +14,20 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use super::build::Build;
use crate::{ use crate::{
cli::*, commands::Command,
cli_types::*, context::{Context, PACKAGE_MANAGER_URL},
commands::{BuildCommand, LoginCommand},
config::{read_token, PACKAGE_MANAGER_URL},
errors::{
commands::PublishError::{ConnectionUnavalaible, PackageNotPublished},
CLIError,
PublishError::{MissingPackageDescription, MissingPackageLicense, MissingPackageRemote},
},
};
use leo_package::{
outputs::OutputsDirectory,
root::{Manifest, ZipFile},
}; };
use leo_package::{outputs::OutputsDirectory, root::ZipFile};
use clap::ArgMatches; use anyhow::{anyhow, Result};
use reqwest::{ use reqwest::{
blocking::{multipart::Form, Client}, blocking::{multipart::Form, Client},
header::{HeaderMap, HeaderValue}, header::{HeaderMap, HeaderValue},
}; };
use serde::Deserialize; use serde::Deserialize;
use std::{convert::TryFrom, env::current_dir}; use structopt::StructOpt;
pub const PUBLISH_URL: &str = "v1/package/publish"; pub const PUBLISH_URL: &str = "v1/package/publish";
@ -45,54 +36,47 @@ struct ResponseJson {
package_id: String, package_id: String,
} }
#[derive(Debug)] /// Publish package to Aleo Package Manager
pub struct PublishCommand; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Publish {}
impl CLI for PublishCommand { impl Publish {
type Options = (); pub fn new() -> Publish {
Publish {}
}
}
impl Command for Publish {
type Input = <Build as Command>::Output;
type Output = Option<String>; type Output = Option<String>;
const ABOUT: AboutType = "Publish the current package to the Aleo Package Manager"; /// Build program before publishing
const ARGUMENTS: &'static [ArgumentType] = &[]; fn prelude(&self) -> Result<Self::Input> {
const FLAGS: &'static [FlagType] = &[]; Build::new().execute()
const NAME: NameType = "publish";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(())
} }
#[cfg_attr(tarpaulin, skip)] fn apply(self, ctx: Context, _input: Self::Input) -> Result<Self::Output> {
fn output(_options: Self::Options) -> Result<Self::Output, CLIError> {
// Build all program files.
let _output = BuildCommand::output(())?;
// Begin "Publishing" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Publishing");
let _enter = span.enter();
// Get the package manifest // Get the package manifest
let path = current_dir()?; let path = ctx.dir()?;
let package_manifest = Manifest::try_from(path.as_path())?; let manifest = ctx.manifest()?;
let package_name = package_manifest.get_package_name(); let package_name = manifest.get_package_name();
let package_version = package_manifest.get_package_version(); let package_version = manifest.get_package_version();
if package_manifest.get_package_description().is_none() { match (
return Err(MissingPackageDescription.into()); manifest.get_package_description(),
} manifest.get_package_license(),
manifest.get_package_remote(),
if package_manifest.get_package_license().is_none() { ) {
return Err(MissingPackageLicense.into()); (None, _, _) => return Err(anyhow!("No package description")),
} (_, None, _) => return Err(anyhow!("Missing package license")),
(_, _, None) => return Err(anyhow!("Missing package remote")),
let package_remote = match package_manifest.get_package_remote() { (_, _, _) => (),
Some(remote) => remote,
None => return Err(MissingPackageRemote.into()),
}; };
let package_remote = manifest.get_package_remote().unwrap();
// Create the output directory // Create the output directory
OutputsDirectory::create(&path)?; OutputsDirectory::create(&path)?;
@ -115,18 +99,9 @@ impl CLI for PublishCommand {
// Client for make POST request // Client for make POST request
let client = Client::new(); let client = Client::new();
// Get token to make an authorized request let token = match ctx.api.auth_token() {
let token = match read_token() { Some(token) => token,
Ok(token) => token, None => return Err(anyhow!("Login before publishing package: try leo login --help")),
// If not logged in, then try logging in using JWT.
Err(_error) => {
tracing::warn!("You should be logged in before attempting to publish a package");
tracing::info!("Trying to log in using JWT...");
let options = (None, None, None);
LoginCommand::output(options)?
}
}; };
// Headers for request to publish package // Headers for request to publish package
@ -149,12 +124,12 @@ impl CLI for PublishCommand {
Ok(json) => json, Ok(json) => json,
Err(error) => { Err(error) => {
tracing::warn!("{:?}", error); tracing::warn!("{:?}", error);
return Err(PackageNotPublished("Package not published".into()).into()); return Err(anyhow!("Package not published"));
} }
}, },
Err(error) => { Err(error) => {
tracing::warn!("{:?}", error); tracing::warn!("{:?}", error);
return Err(ConnectionUnavalaible("Connection error".into()).into()); return Err(anyhow!("Connection unavailable"));
} }
}; };

View File

@ -0,0 +1,59 @@
// 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::Command, context::Context};
use leo_package::LeoPackage;
use anyhow::Result;
use structopt::StructOpt;
use tracing::span::Span;
/// Remove imported package
#[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Remove {
#[structopt(name = "PACKAGE")]
name: String,
}
impl Remove {
pub fn new(name: String) -> Remove {
Remove { name }
}
}
impl Command for Remove {
type Input = ();
type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Removing")
}
fn prelude(&self) -> Result<Self::Input> {
Ok(())
}
fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output> {
let path = ctx.dir()?;
let package_name = self.name;
LeoPackage::remove_imported_package(&package_name, &path)?;
tracing::info!("Successfully removed package \"{}\"\n", package_name);
Ok(())
}
}

View File

@ -14,73 +14,62 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{cli::*, cli_types::*, commands::SetupCommand, errors::CLIError}; use super::setup::Setup;
use leo_package::{outputs::ProofFile, root::Manifest}; use crate::{commands::Command, context::Context};
use leo_package::outputs::ProofFile;
use snarkvm_algorithms::snark::groth16::{Groth16, PreparedVerifyingKey, Proof}; use snarkvm_algorithms::snark::groth16::{Groth16, PreparedVerifyingKey, Proof};
use snarkvm_curves::bls12_377::{Bls12_377, Fr}; use snarkvm_curves::bls12_377::{Bls12_377, Fr};
use snarkvm_models::algorithms::SNARK; use snarkvm_models::algorithms::SNARK;
use snarkvm_utilities::bytes::ToBytes; use snarkvm_utilities::bytes::ToBytes;
use clap::ArgMatches; use anyhow::Result;
use rand::thread_rng; use rand::thread_rng;
use std::{convert::TryFrom, env::current_dir, time::Instant}; use structopt::StructOpt;
use tracing::span::Span;
#[derive(Debug)] /// Run the program and produce a proof
pub struct ProveCommand; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
impl CLI for ProveCommand { pub struct Prove {
type Options = bool; #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")]
type Output = (Proof<Bls12_377>, PreparedVerifyingKey<Bls12_377>); skip_key_check: bool,
const ABOUT: AboutType = "Run the program and produce a proof";
const ARGUMENTS: &'static [ArgumentType] = &[];
const FLAGS: &'static [FlagType] = &[("--skip-key-check")];
const NAME: NameType = "prove";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(!arguments.is_present("skip-key-check"))
} }
#[cfg_attr(tarpaulin, skip)] impl Prove {
fn output(do_setup_check: Self::Options) -> Result<Self::Output, CLIError> { pub fn new(skip_key_check: bool) -> Prove {
let (program, parameters, prepared_verifying_key) = SetupCommand::output(do_setup_check)?; Prove { skip_key_check }
}
}
// Begin "Proving" context for console logging impl Command for Prove {
let span = tracing::span!(tracing::Level::INFO, "Proving"); type Input = <Setup as Command>::Output;
let enter = span.enter(); type Output = (Proof<Bls12_377>, PreparedVerifyingKey<Bls12_377>);
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Proving")
}
fn prelude(&self) -> Result<Self::Input> {
Setup::new(self.skip_key_check).execute()
}
fn apply(self, ctx: Context, input: Self::Input) -> Result<Self::Output> {
let (program, parameters, prepared_verifying_key) = input;
// Get the package name // Get the package name
let path = current_dir()?; let path = ctx.dir()?;
let package_name = Manifest::try_from(path.as_path())?.get_package_name(); let package_name = ctx.manifest()?.get_package_name();
tracing::info!("Starting..."); tracing::info!("Starting...");
// Start the timer
let start = Instant::now();
let rng = &mut thread_rng(); let rng = &mut thread_rng();
let program_proof = Groth16::<Bls12_377, _, Vec<Fr>>::prove(&parameters, &program, rng)?; let program_proof = Groth16::<Bls12_377, _, Vec<Fr>>::prove(&parameters, &program, rng)?;
// Finish the timer
let end = start.elapsed().as_millis();
// Write the proof file to the output directory // Write the proof file to the output directory
let mut proof = vec![]; let mut proof = vec![];
program_proof.write(&mut proof)?; program_proof.write(&mut proof)?;
ProofFile::new(&package_name).write_to(&path, &proof)?; ProofFile::new(&package_name).write_to(&path, &proof)?;
// Drop "Proving" context for console logging
drop(enter);
// Begin "Done" context for console logging
tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
tracing::info!("Finished in {:?} milliseconds\n", end);
});
Ok((program_proof, prepared_verifying_key)) Ok((program_proof, prepared_verifying_key))
} }
} }

View File

@ -1,69 +0,0 @@
// 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::{cli::*, cli_types::*, errors::CLIError};
use leo_package::LeoPackage;
use clap::ArgMatches;
use std::env::current_dir;
#[derive(Debug)]
pub struct RemoveCommand;
impl CLI for RemoveCommand {
type Options = Option<String>;
type Output = ();
const ABOUT: AboutType = "Uninstall a package from the current package";
const ARGUMENTS: &'static [ArgumentType] = &[
// (name, description, possible_values, required, index)
(
"NAME",
"Removes the package from the current directory",
&[],
true,
1u64,
),
];
const FLAGS: &'static [FlagType] = &[];
const NAME: NameType = "remove";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(match arguments.value_of("NAME") {
Some(name) => Some(name.to_string()),
None => unreachable!(),
})
}
#[cfg_attr(tarpaulin, skip)]
fn output(options: Self::Options) -> Result<Self::Output, CLIError> {
// Begin "Removing" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Removing");
let _enter = span.enter();
let path = current_dir()?;
if let Some(package_name) = options {
LeoPackage::remove_imported_package(&package_name, &path)?;
tracing::info!("Successfully removed package \"{}\"\n", package_name);
}
Ok(())
}
}

View File

@ -14,48 +14,48 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{cli::*, cli_types::*, commands::ProveCommand, errors::CLIError}; use super::prove::Prove;
use crate::{commands::Command, context::Context};
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
use anyhow::Result;
use snarkvm_algorithms::snark::groth16::Groth16; use snarkvm_algorithms::snark::groth16::Groth16;
use snarkvm_curves::bls12_377::{Bls12_377, Fr}; use snarkvm_curves::bls12_377::{Bls12_377, Fr};
use snarkvm_models::algorithms::SNARK; use snarkvm_models::algorithms::SNARK;
use structopt::StructOpt;
use tracing::span::Span;
use clap::ArgMatches; /// Build, Prove and Run Leo program with inputs
use std::time::Instant; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
#[derive(Debug)] pub struct Run {
pub struct RunCommand; #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")]
skip_key_check: bool,
impl CLI for RunCommand {
type Options = bool;
type Output = ();
const ABOUT: AboutType = "Run a program with input variables";
const ARGUMENTS: &'static [ArgumentType] = &[];
const FLAGS: &'static [FlagType] = &[("--skip-key-check")];
const NAME: NameType = "run";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(!arguments.is_present("skip-key-check"))
} }
#[cfg_attr(tarpaulin, skip)] impl Run {
fn output(do_setup_check: Self::Options) -> Result<(), CLIError> { pub fn new(skip_key_check: bool) -> Run {
let (proof, prepared_verifying_key) = ProveCommand::output(do_setup_check)?; Run { skip_key_check }
}
}
// Begin "Verifying" context for console logging impl Command for Run {
let span = tracing::span!(tracing::Level::INFO, "Verifying"); type Input = <Prove as Command>::Output;
let enter = span.enter(); type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Verifying")
}
fn prelude(&self) -> Result<Self::Input> {
Prove::new(self.skip_key_check).execute()
}
fn apply(self, _ctx: Context, input: Self::Input) -> Result<Self::Output> {
let (proof, prepared_verifying_key) = input;
tracing::info!("Starting..."); tracing::info!("Starting...");
// Start the timer
let start = Instant::now();
// Run the verifier // Run the verifier
let is_success = Groth16::<Bls12_377, Compiler<Fr, EdwardsGroupType>, Vec<Fr>>::verify( let is_success = Groth16::<Bls12_377, Compiler<Fr, EdwardsGroupType>, Vec<Fr>>::verify(
&prepared_verifying_key, &prepared_verifying_key,
@ -63,23 +63,12 @@ impl CLI for RunCommand {
&proof, &proof,
)?; )?;
// End the timer
let end = start.elapsed().as_millis();
// Log the verifier output // Log the verifier output
match is_success { match is_success {
true => tracing::info!("Proof is valid"), true => tracing::info!("Proof is valid"),
false => tracing::error!("Proof is invalid"), false => tracing::error!("Proof is invalid"),
}; };
// Drop "Verifying" context for console logging
drop(enter);
// Begin "Done" context for console logging
tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
tracing::info!("Finished in {:?} milliseconds\n", end);
});
Ok(()) Ok(())
} }
} }

View File

@ -14,81 +14,71 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{ use super::build::Build;
cli::*, use crate::{commands::Command, context::Context};
cli_types::*,
commands::BuildCommand,
errors::{CLIError, RunError},
};
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
use leo_package::{ use leo_package::{
outputs::{ProvingKeyFile, VerificationKeyFile}, outputs::{ProvingKeyFile, VerificationKeyFile},
root::Manifest,
source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
}; };
use anyhow::{anyhow, Result};
use rand::thread_rng;
use snarkvm_algorithms::snark::groth16::{Groth16, Parameters, PreparedVerifyingKey, VerifyingKey}; use snarkvm_algorithms::snark::groth16::{Groth16, Parameters, PreparedVerifyingKey, VerifyingKey};
use snarkvm_curves::bls12_377::{Bls12_377, Fr}; use snarkvm_curves::bls12_377::{Bls12_377, Fr};
use snarkvm_models::algorithms::snark::SNARK; use snarkvm_models::algorithms::snark::SNARK;
use structopt::StructOpt;
use tracing::span::Span;
use clap::ArgMatches; /// Executes the setup command for a Leo program
use rand::thread_rng; #[derive(StructOpt, Debug, Default)]
use std::{convert::TryFrom, env::current_dir, time::Instant}; #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Setup {
#[structopt(long = "skip-key-check", help = "Skip key verification")]
skip_key_check: bool,
}
#[derive(Debug)] impl Setup {
pub struct SetupCommand; pub fn new(skip_key_check: bool) -> Setup {
Setup { skip_key_check }
}
}
impl CLI for SetupCommand { impl Command for Setup {
type Options = bool; type Input = <Build as Command>::Output;
type Output = ( type Output = (
Compiler<Fr, EdwardsGroupType>, Compiler<Fr, EdwardsGroupType>,
Parameters<Bls12_377>, Parameters<Bls12_377>,
PreparedVerifyingKey<Bls12_377>, PreparedVerifyingKey<Bls12_377>,
); );
const ABOUT: AboutType = "Run a program setup"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Setup")
const FLAGS: &'static [FlagType] = &[("--skip-key-check")];
const NAME: NameType = "setup";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(!arguments.is_present("skip-key-check"))
} }
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn output(do_check: Self::Options) -> Result<Self::Output, CLIError> { Build::new().execute()
// Get the package name }
let path = current_dir()?;
let package_name = Manifest::try_from(path.as_path())?.get_package_name();
match BuildCommand::output(())? { fn apply(self, ctx: Context, input: Self::Input) -> Result<Self::Output> {
let path = ctx.dir()?;
let package_name = ctx.manifest()?.get_package_name();
match input {
Some((program, checksum_differs)) => { Some((program, checksum_differs)) => {
// Begin "Setup" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Setup");
let enter = span.enter();
// Check if a proving key and verification key already exists // Check if a proving key and verification key already exists
let keys_exist = ProvingKeyFile::new(&package_name).exists_at(&path) let keys_exist = ProvingKeyFile::new(&package_name).exists_at(&path)
&& VerificationKeyFile::new(&package_name).exists_at(&path); && VerificationKeyFile::new(&package_name).exists_at(&path);
// If keys do not exist or the checksum differs, run the program setup // If keys do not exist or the checksum differs, run the program setup
// If keys do not exist or the checksum differs, run the program setup // If keys do not exist or the checksum differs, run the program setup
let (end, proving_key, prepared_verifying_key) = if !keys_exist || checksum_differs { let (proving_key, prepared_verifying_key) = if !keys_exist || checksum_differs {
tracing::info!("Starting..."); tracing::info!("Starting...");
// Start the timer for setup
let setup_start = Instant::now();
// Run the program setup operation // Run the program setup operation
let rng = &mut thread_rng(); let rng = &mut thread_rng();
let (proving_key, prepared_verifying_key) = let (proving_key, prepared_verifying_key) =
Groth16::<Bls12_377, Compiler<Fr, _>, Vec<Fr>>::setup(&program, rng).unwrap(); Groth16::<Bls12_377, Compiler<Fr, _>, Vec<Fr>>::setup(&program, rng)?;
// End the timer
let end = setup_start.elapsed().as_millis();
// TODO (howardwu): Convert parameters to a 'proving key' struct for serialization. // TODO (howardwu): Convert parameters to a 'proving key' struct for serialization.
// Write the proving key file to the output directory // Write the proving key file to the output directory
@ -107,20 +97,19 @@ impl CLI for SetupCommand {
let _ = verification_key_file.write_to(&path, &verification_key)?; let _ = verification_key_file.write_to(&path, &verification_key)?;
tracing::info!("Complete"); tracing::info!("Complete");
(end, proving_key, prepared_verifying_key) (proving_key, prepared_verifying_key)
} else { } else {
tracing::info!("Detected saved setup"); tracing::info!("Detected saved setup");
// Start the timer for setup
let setup_start = Instant::now();
// Read the proving key file from the output directory // Read the proving key file from the output directory
tracing::info!("Loading proving key..."); tracing::info!("Loading proving key...");
if !do_check {
if self.skip_key_check {
tracing::info!("Skipping curve check"); tracing::info!("Skipping curve check");
} }
let proving_key_bytes = ProvingKeyFile::new(&package_name).read_from(&path)?; let proving_key_bytes = ProvingKeyFile::new(&package_name).read_from(&path)?;
let proving_key = Parameters::<Bls12_377>::read(proving_key_bytes.as_slice(), do_check)?; let proving_key =
Parameters::<Bls12_377>::read(proving_key_bytes.as_slice(), !self.skip_key_check)?;
tracing::info!("Complete"); tracing::info!("Complete");
// Read the verification key file from the output directory // Read the verification key file from the output directory
@ -132,20 +121,9 @@ impl CLI for SetupCommand {
let prepared_verifying_key = PreparedVerifyingKey::<Bls12_377>::from(verifying_key); let prepared_verifying_key = PreparedVerifyingKey::<Bls12_377>::from(verifying_key);
tracing::info!("Complete"); tracing::info!("Complete");
// End the timer (proving_key, prepared_verifying_key)
let end = setup_start.elapsed().as_millis();
(end, proving_key, prepared_verifying_key)
}; };
// Drop "Setup" context for console logging
drop(enter);
// Begin "Done" context for console logging
tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
tracing::info!("Finished in {:?} milliseconds\n", end);
});
Ok((program, proving_key, prepared_verifying_key)) Ok((program, proving_key, prepared_verifying_key))
} }
None => { None => {
@ -153,9 +131,7 @@ impl CLI for SetupCommand {
main_file_path.push(SOURCE_DIRECTORY_NAME); main_file_path.push(SOURCE_DIRECTORY_NAME);
main_file_path.push(MAIN_FILENAME); main_file_path.push(MAIN_FILENAME);
Err(CLIError::RunError(RunError::MainFileDoesNotExist( Err(anyhow!("Unable to build, check that main file exists"))
main_file_path.into_os_string(),
)))
} }
} }
} }

View File

@ -14,67 +14,75 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{ use crate::{commands::Command, context::Context};
cli::*,
cli_types::*,
errors::{CLIError, TestError::ProgramFileDoesNotExist},
};
use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType};
use leo_package::{ use leo_package::{
inputs::*, inputs::*,
outputs::{OutputsDirectory, OUTPUTS_DIRECTORY_NAME}, outputs::{OutputsDirectory, OUTPUTS_DIRECTORY_NAME},
root::Manifest, source::{MainFile, MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
source::{LibraryFile, MainFile, LIBRARY_FILENAME, MAIN_FILENAME, SOURCE_DIRECTORY_NAME},
}; };
use anyhow::{anyhow, Result};
use snarkvm_curves::edwards_bls12::Fq; use snarkvm_curves::edwards_bls12::Fq;
use std::{convert::TryFrom, path::PathBuf, time::Instant};
use structopt::StructOpt;
use tracing::span::Span;
use clap::ArgMatches; /// Build program and run tests command
use std::{convert::TryFrom, env::current_dir, time::Instant}; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Test {
#[structopt(short = "f", long = "file", name = "file")]
files: Vec<PathBuf>,
}
#[derive(Debug)] impl Test {
pub struct TestCommand; pub fn new(files: Vec<PathBuf>) -> Test {
Test { files }
}
}
impl CLI for TestCommand { impl Command for Test {
type Options = (); type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Compile and run all tests in the current package"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Test")
const FLAGS: &'static [FlagType] = &[]; }
const NAME: NameType = "test";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(()) Ok(())
} }
#[cfg_attr(tarpaulin, skip)] fn apply(self, ctx: Context, _: Self::Input) -> Result<Self::Output> {
fn output(_options: Self::Options) -> Result<Self::Output, CLIError> {
let path = current_dir()?;
// Get the package name // Get the package name
let manifest = Manifest::try_from(path.as_path())?; let package_name = ctx.manifest()?.get_package_name();
let package_name = manifest.get_package_name();
// Sanitize the package path to the root directory // Sanitize the package path to the root directory
let mut package_path = path; let mut package_path = ctx.dir()?;
if package_path.is_file() { if package_path.is_file() {
package_path.pop(); package_path.pop();
} }
let mut to_test: Vec<PathBuf> = Vec::new();
// if -f flag was used, then we'll test only this files
if !self.files.is_empty() {
to_test.extend(self.files.iter().cloned());
// if args were not passed - try main file
} else if MainFile::exists_at(&package_path) {
let mut file_path = package_path.clone(); let mut file_path = package_path.clone();
file_path.push(SOURCE_DIRECTORY_NAME); file_path.push(SOURCE_DIRECTORY_NAME);
// Verify a main or library file exists
if MainFile::exists_at(&package_path) {
file_path.push(MAIN_FILENAME); file_path.push(MAIN_FILENAME);
} else if LibraryFile::exists_at(&package_path) { to_test.push(file_path);
file_path.push(LIBRARY_FILENAME);
// when no main file and no files marked - error
} else { } else {
return Err(ProgramFileDoesNotExist(package_path.into()).into()); return Err(anyhow!(
"Program file does not exist {}",
package_path.to_string_lossy()
));
} }
// Construct the path to the output directory; // Construct the path to the output directory;
@ -84,49 +92,45 @@ impl CLI for TestCommand {
// Create the output directory // Create the output directory
OutputsDirectory::create(&package_path)?; OutputsDirectory::create(&package_path)?;
// Begin "Test" context for console logging // Finally test every passed file
let span = tracing::span!(tracing::Level::INFO, "Test"); for file_path in to_test {
let enter = span.enter(); tracing::info!("Running tests in file {:?}", file_path);
// Start the timer let input_pairs = match InputPairs::try_from(package_path.as_path()) {
let start = Instant::now(); Ok(pairs) => pairs,
Err(_) => {
tracing::warn!("Unable to find inputs, ignore this message or put them into /inputs folder");
InputPairs::new()
}
};
// Parse the current main program file let timer = Instant::now();
let program = let program = Compiler::<Fq, EdwardsGroupType>::parse_program_without_input(
Compiler::<Fq, EdwardsGroupType>::parse_program_without_input(package_name, file_path, output_directory)?; package_name.clone(),
file_path,
output_directory.clone(),
)?;
// Parse all inputs as input pairs
let pairs = InputPairs::try_from(package_path.as_path())?;
// Run tests
let temporary_program = program; let temporary_program = program;
let (passed, failed) = temporary_program.compile_test_constraints(pairs)?; let (passed, failed) = temporary_program.compile_test_constraints(input_pairs)?;
let time_taken = timer.elapsed().as_millis();
// Drop "Test" context for console logging
drop(enter);
// Set the result of the test command to passed if no tests failed.
if failed == 0 { if failed == 0 {
// Begin "Done" context for console logging
tracing::span!(tracing::Level::INFO, "Done").in_scope(|| {
tracing::info!( tracing::info!(
"Tests passed in {} milliseconds. {} passed; {} failed;\n", "Tests passed in {} milliseconds. {} passed; {} failed;\n",
start.elapsed().as_millis(), time_taken,
passed, passed,
failed failed
); );
});
} else { } else {
// Begin "Done" context for console logging
tracing::span!(tracing::Level::ERROR, "Done").in_scope(|| {
tracing::error!( tracing::error!(
"Tests failed in {} milliseconds. {} passed; {} failed;\n", "Tests failed in {} milliseconds. {} passed; {} failed;\n",
start.elapsed().as_millis(), time_taken,
passed, passed,
failed failed
); );
}); }
}; }
Ok(()) Ok(())
} }

View File

@ -14,179 +14,97 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{cli::CLI, cli_types::*, config::Config, updater::Updater}; use crate::{commands::Command, config::Config, context::Context, updater::Updater};
use clap::AppSettings; use anyhow::{anyhow, Result};
use structopt::StructOpt;
use tracing::span::Span;
#[derive(Debug)] /// Setting for automatic updates of Leo
pub struct UpdateCommand; #[derive(Debug, StructOpt, PartialEq)]
pub enum Sub {
Automatic {
#[structopt(name = "bool", help = "Boolean value: true or false", parse(try_from_str))]
value: bool,
},
}
impl CLI for UpdateCommand { /// Update Leo to the latest version
// (show_all_versions, quiet) #[derive(StructOpt, Debug, Default)]
type Options = Option<(bool, bool, bool)>; #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Update {
/// List all available versions of Leo
#[structopt(short, long)]
list: bool,
/// For Aleo Studio only
#[structopt(short, long)]
studio: bool,
/// Setting for automatic updates of Leo
#[structopt(subcommand)]
automatic: Option<Sub>,
}
impl Update {
pub fn new(list: bool, studio: bool, automatic: Option<Sub>) -> Update {
Update {
list,
studio,
automatic,
}
}
}
impl Command for Update {
type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Update Leo to the latest version"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Updating")
const FLAGS: &'static [FlagType] = &[
"[list] -l --list 'List all available versions of Leo'",
"[quiet] -q --quiet 'Suppress outputs to terminal'",
"[studio] -s --studio 'For Aleo Studio only'",
];
const NAME: NameType = "update";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[
// (name, description, options, settings)
(
UpdateAutomatic::NAME,
UpdateAutomatic::ABOUT,
UpdateAutomatic::ARGUMENTS,
UpdateAutomatic::FLAGS,
&UpdateAutomatic::OPTIONS,
&[
AppSettings::ColoredHelp,
AppSettings::DisableHelpSubcommand,
AppSettings::DisableVersion,
],
),
];
fn parse(arguments: &clap::ArgMatches) -> Result<Self::Options, crate::errors::CLIError> {
if let ("automatic", Some(arguments)) = arguments.subcommand() {
// Run the `automatic` subcommand
let options = UpdateAutomatic::parse(arguments)?;
let _output = UpdateAutomatic::output(options)?;
return Ok(None);
};
let show_all_versions = arguments.is_present("list");
let quiet = arguments.is_present("quiet");
let studio = arguments.is_present("studio");
Ok(Some((show_all_versions, quiet, studio)))
} }
fn output(options: Self::Options) -> Result<Self::Output, crate::errors::CLIError> { fn prelude(&self) -> Result<Self::Input> {
// Begin "Updating" context for console logging Ok(())
let span = tracing::span!(tracing::Level::INFO, "Updating");
let _enter = span.enter();
let (show_all_versions, quiet, studio) = match options {
Some(options) => options,
None => return Ok(()),
};
match show_all_versions {
true => match Updater::show_available_releases() {
Ok(_) => return Ok(()),
Err(e) => {
tracing::error!("Could not fetch that latest version of Leo");
tracing::error!("{}", e);
} }
},
fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> {
// if --list is passed - simply list everything and exit
if self.list {
return Updater::show_available_releases().map_err(|e| anyhow!("Could not fetch versions: {}", e));
}
// in case automatic subcommand was called
if let Some(Sub::Automatic { value }) = self.automatic {
Config::set_update_automatic(value)?;
match value {
true => tracing::info!("Automatic updates are enabled. Leo will update as new versions are released"),
false => { false => {
tracing::info!("Automatic updates are disabled. Leo will not update as new versions are released")
}
};
return Ok(());
}
let config = Config::read_config()?; let config = Config::read_config()?;
// If update is run with studio and the automatic update is off, finish quietly // If update is run with studio and the automatic update is off, finish quietly
if studio && !config.update.automatic { if self.studio && !config.update.automatic {
return Ok(()); return Ok(());
} }
match Updater::update_to_latest_release(!quiet) { match Updater::update_to_latest_release(true) {
Ok(status) => { Ok(status) => match (status.uptodate(), status.updated()) {
if !quiet { (true, _) => tracing::info!("Leo is already on the latest version"),
if status.uptodate() { (_, true) => tracing::info!("Leo has successfully updated to version {}", status.version()),
tracing::info!("Leo is already on the latest version"); (_, _) => (),
} else if status.updated() { },
tracing::info!("Leo has successfully updated to version {}", status.version());
}
}
return Ok(());
}
Err(e) => { Err(e) => {
if !quiet {
tracing::error!("Could not update Leo to the latest version"); tracing::error!("Could not update Leo to the latest version");
tracing::error!("{}", e); tracing::error!("{}", e);
} }
} }
}
}
}
Ok(())
}
}
//TODO (raychu86) Move this to dedicated file/module
#[derive(Debug)]
pub struct UpdateAutomatic;
impl CLI for UpdateAutomatic {
// (is_automatic, quiet)
type Options = (Option<bool>, bool);
type Output = ();
const ABOUT: AboutType = "Setting for automatic updates of Leo";
const ARGUMENTS: &'static [ArgumentType] = &[
// (name, description, possible_values, required, index)
(
"automatic",
"Enable or disable automatic updates",
&["true", "false"],
false,
1u64,
),
];
const FLAGS: &'static [FlagType] = &["[quiet] -q --quiet 'Suppress outputs to terminal'"];
const NAME: NameType = "automatic";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
fn parse(arguments: &clap::ArgMatches) -> Result<Self::Options, crate::errors::CLIError> {
let quiet = arguments.is_present("quiet");
match arguments.value_of("automatic") {
Some(automatic) => {
// TODO enforce that the possible values is true or false
let automatic = match automatic {
"true" => Some(true),
"false" => Some(false),
_ => unreachable!(),
};
Ok((automatic, quiet))
}
None => Ok((None, quiet)),
}
}
fn output(options: Self::Options) -> Result<Self::Output, crate::errors::CLIError> {
// Begin "Settings" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Settings");
let enter = span.enter();
// If a boolean value is provided, update the saved
// `automatic` configuration value to this boolean value.
if let Some(automatic) = options.0 {
Config::set_update_automatic(automatic)?;
}
// If --quiet is not enabled, log the output.
if !options.1 {
// Read the `automatic` value now.
let automatic = Config::read_config()?.update.automatic;
// Log the output.
tracing::debug!("automatic = {}", automatic);
match automatic {
true => tracing::info!("Automatic updates are enabled. Leo will update as new versions are released."),
false => {
tracing::info!("Automatic updates are disabled. Leo will not update as new versions are released.")
}
};
}
// Drop "Settings" context for console logging.
drop(enter);
Ok(()) Ok(())
} }

View File

@ -14,42 +14,55 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{cli::CLI, cli_types::*, commands::BuildCommand, errors::CLIError}; use super::build::Build;
use clap::ArgMatches; use crate::{commands::Command, context::Context};
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::{sync::mpsc::channel, time::Duration}; use std::{sync::mpsc::channel, time::Duration};
use anyhow::{anyhow, Result};
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use structopt::StructOpt;
use tracing::span::Span;
const LEO_SOURCE_DIR: &str = "src/"; const LEO_SOURCE_DIR: &str = "src/";
// Time interval for watching files, in seconds /// Watch file changes in src/ directory and run Build Command
const INTERVAL: u64 = 3; #[derive(StructOpt, Debug, Default)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Watch {
/// Set up watch interval
#[structopt(short, long, default_value = "3")]
interval: u64,
}
pub struct WatchCommand; impl Watch {
pub fn new(interval: u64) -> Watch {
Watch { interval }
}
}
impl CLI for WatchCommand { impl Command for Watch {
type Options = (); type Input = ();
type Output = (); type Output = ();
const ABOUT: AboutType = "Watch for changes of Leo source files"; fn log_span(&self) -> Span {
const ARGUMENTS: &'static [ArgumentType] = &[]; tracing::span!(tracing::Level::INFO, "Watching")
const FLAGS: &'static [FlagType] = &[]; }
const NAME: NameType = "watch";
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)] fn prelude(&self) -> Result<Self::Input> {
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(()) Ok(())
} }
fn output(_options: Self::Options) -> Result<Self::Output, CLIError> { fn apply(self, _ctx: Context, _: Self::Input) -> Result<Self::Output> {
// Begin "Watching" context for console logging
let span = tracing::span!(tracing::Level::INFO, "Watching");
let _enter = span.enter();
let (tx, rx) = channel(); let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(INTERVAL)).unwrap(); let mut watcher = watcher(tx, Duration::from_secs(self.interval)).unwrap();
watcher.watch(LEO_SOURCE_DIR, RecursiveMode::Recursive).unwrap();
watcher.watch(LEO_SOURCE_DIR, RecursiveMode::Recursive).map_err(|e| {
anyhow!(
"Unable to watch, check that directory contains Leo.toml file. Error: {}",
e
)
})?;
tracing::info!("Watching Leo source code"); tracing::info!("Watching Leo source code");
@ -57,7 +70,7 @@ impl CLI for WatchCommand {
match rx.recv() { match rx.recv() {
// See changes on the write event // See changes on the write event
Ok(DebouncedEvent::Write(_write)) => { Ok(DebouncedEvent::Write(_write)) => {
match BuildCommand::output(()) { match Build::new().execute() {
Ok(_output) => { Ok(_output) => {
tracing::info!("Built successfully"); tracing::info!("Built successfully");
} }

View File

@ -14,11 +14,6 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::errors::CLIError;
use dirs::home_dir;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::{ use std::{
fs::{self, create_dir_all, File}, fs::{self, create_dir_all, File},
io, io,
@ -26,7 +21,10 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
pub const PACKAGE_MANAGER_URL: &str = "https://api.aleo.pm/"; use anyhow::Error;
use dirs::home_dir;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
pub const LEO_CREDENTIALS_FILE: &str = "credentials"; pub const LEO_CREDENTIALS_FILE: &str = "credentials";
pub const LEO_CONFIG_FILE: &str = "config.toml"; pub const LEO_CONFIG_FILE: &str = "config.toml";
@ -75,7 +73,7 @@ impl Default for Config {
impl Config { impl Config {
/// Read the config from the `config.toml` file /// Read the config from the `config.toml` file
pub fn read_config() -> Result<Self, CLIError> { pub fn read_config() -> Result<Self, Error> {
let config_dir = LEO_CONFIG_DIRECTORY.clone(); let config_dir = LEO_CONFIG_DIRECTORY.clone();
let config_path = LEO_CONFIG_PATH.clone(); let config_path = LEO_CONFIG_PATH.clone();
@ -112,7 +110,7 @@ impl Config {
} }
/// Update the `automatic` configuration in the `config.toml` file. /// Update the `automatic` configuration in the `config.toml` file.
pub fn set_update_automatic(automatic: bool) -> Result<(), CLIError> { pub fn set_update_automatic(automatic: bool) -> Result<(), Error> {
let mut config = Self::read_config()?; let mut config = Self::read_config()?;
if config.update.automatic != automatic { if config.update.automatic != automatic {

72
leo/context.rs Normal file
View File

@ -0,0 +1,72 @@
// 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::{api::Api, config};
use leo_package::root::Manifest;
use anyhow::Result;
use std::{convert::TryFrom, env::current_dir, path::PathBuf};
pub const PACKAGE_MANAGER_URL: &str = "https://api.aleo.pm/";
/// Project context, manifest, current directory etc
/// All the info that is relevant in most of the commands
#[derive(Clone)]
pub struct Context {
/// Api client for Aleo PM
pub api: Api,
/// Path at which the command is called, None when default
pub path: Option<PathBuf>,
}
impl Context {
pub fn dir(&self) -> Result<PathBuf> {
match &self.path {
Some(path) => Ok(path.clone()),
None => Ok(current_dir()?),
}
}
/// Get package manifest for current context
pub fn manifest(&self) -> Result<Manifest> {
Ok(Manifest::try_from(self.dir()?.as_path())?)
}
}
/// Create a new context for the current directory.
pub fn create_context(path: PathBuf) -> Result<Context> {
let token = match config::read_token() {
Ok(token) => Some(token),
Err(_) => None,
};
let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token);
Ok(Context { api, path: Some(path) })
}
/// Returns project context.
pub fn get_context() -> Result<Context> {
let token = match config::read_token() {
Ok(token) => Some(token),
Err(_) => None,
};
let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token);
Ok(Context { api, path: None })
}

View File

@ -1,213 +0,0 @@
// 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::errors::*;
use leo_compiler::errors::OutputFileError;
use leo_package::errors::*;
#[derive(Debug, Error)]
pub enum CLIError {
#[error("{}", _0)]
AddError(AddError),
#[error("{}", _0)]
BuildError(BuildError),
#[error("{}", _0)]
ZipFileError(ZipFileError),
#[error("{}", _0)]
ChecksumFileError(ChecksumFileError),
#[error("{}", _0)]
CircuitFileError(CircuitFileError),
#[error("{}: {}", _0, _1)]
Crate(&'static str, String),
#[error("{}", _0)]
GitignoreError(GitignoreError),
#[error("{}", _0)]
InitError(InitError),
#[error("{}", _0)]
ImportsDirectoryError(ImportsDirectoryError),
#[error("{}", _0)]
InputsDirectoryError(InputsDirectoryError),
#[error("{}", _0)]
InputFileError(InputFileError),
#[error("{}", _0)]
LibraryFileError(LibraryFileError),
#[error("{}", _0)]
LoginError(LoginError),
#[error("{}", _0)]
MainFileError(MainFileError),
#[error("{}", _0)]
ManifestError(ManifestError),
#[error("{}", _0)]
NewError(NewError),
#[error("{}", _0)]
OutputFileError(OutputFileError),
#[error("{}", _0)]
OutputsDirectoryError(OutputsDirectoryError),
#[error("{}", _0)]
PackageError(PackageError),
#[error("{}", _0)]
ProofFileError(ProofFileError),
#[error("{}", _0)]
ProvingKeyFileError(ProvingKeyFileError),
#[error("{}", _0)]
PublishError(PublishError),
#[error("{}", _0)]
READMEError(READMEError),
#[error("{}", _0)]
RunError(RunError),
#[error("{}", _0)]
SNARKError(snarkvm_errors::algorithms::snark::SNARKError),
#[error("{}", _0)]
SourceDirectoryError(SourceDirectoryError),
#[error("{}", _0)]
StateFileError(StateFileError),
#[error("{}", _0)]
TestError(TestError),
#[error("TomlSerError: {0}")]
TomlSerError(#[from] toml::ser::Error),
#[error("TomlDeError: {0}")]
TomlDeError(#[from] toml::de::Error),
#[error("{}", _0)]
VerificationKeyFileError(VerificationKeyFileError),
}
macro_rules! impl_cli_error {
($($t:tt), +) => {
$(impl From<$t> for CLIError {
fn from(error: $t) -> Self {
tracing::error!("{}\n", error);
CLIError::$t(error)
}
})*
}
}
impl_cli_error!(
AddError,
BuildError,
CircuitFileError,
ChecksumFileError,
GitignoreError,
ImportsDirectoryError,
InitError,
InputsDirectoryError,
InputFileError,
LibraryFileError,
LoginError,
MainFileError,
ManifestError,
NewError,
OutputFileError,
OutputsDirectoryError,
PackageError,
ProofFileError,
ProvingKeyFileError,
PublishError,
READMEError,
RunError,
SourceDirectoryError,
StateFileError,
TestError,
VerificationKeyFileError,
ZipFileError
);
impl From<clap::Error> for CLIError {
fn from(error: clap::Error) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("clap", error.to_string())
}
}
impl From<leo_compiler::errors::CompilerError> for CLIError {
fn from(error: leo_compiler::errors::CompilerError) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("leo-compiler", "Program failed due to previous error".into())
}
}
impl From<leo_input::errors::InputParserError> for CLIError {
fn from(error: leo_input::errors::InputParserError) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("leo-input", "Program failed due to previous error".into())
}
}
impl From<reqwest::Error> for CLIError {
fn from(error: reqwest::Error) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("rewquest", error.to_string())
}
}
impl From<snarkvm_errors::algorithms::snark::SNARKError> for CLIError {
fn from(error: snarkvm_errors::algorithms::snark::SNARKError) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("snarkvm_errors", error.to_string())
}
}
impl From<snarkvm_errors::gadgets::SynthesisError> for CLIError {
fn from(error: snarkvm_errors::gadgets::SynthesisError) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("snarkvm_errors", error.to_string())
}
}
impl From<serde_json::error::Error> for CLIError {
fn from(error: serde_json::error::Error) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("serde_json", error.to_string())
}
}
impl From<std::io::Error> for CLIError {
fn from(error: std::io::Error) -> Self {
tracing::error!("{}\n", error);
CLIError::Crate("std::io", error.to_string())
}
}

View File

@ -1,35 +0,0 @@
// 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 std::ffi::OsString;
#[derive(Debug, Error)]
pub enum AddError {
#[error("Connection unavailable {:?}", _0)]
ConnectionUnavailable(OsString),
#[error("Missing author or package name: leo add author/package")]
MissingAuthorOrPackageName,
#[error("Invalid remote")]
InvalidRemote,
#[error("Not authorized in package manager. Use leo login to sign in")]
NotAuthorized,
#[error("{:?}", _0)]
ZipError(OsString),
}

View File

@ -1,28 +0,0 @@
// 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 leo_package::errors::ManifestError;
use std::ffi::OsString;
#[derive(Debug, Error)]
pub enum BuildError {
#[error("main file {:?} does not exist", _0)]
MainFileDoesNotExist(OsString),
#[error("{}", _0)]
ManifestError(#[from] ManifestError),
}

View File

@ -1,34 +0,0 @@
// 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 leo_package::errors::ManifestError;
use std::{ffi::OsString, io};
#[derive(Debug, Error)]
pub enum InitError {
#[error("root directory {:?} creating: {}", _0, _1)]
CreatingRootDirectory(OsString, io::Error),
#[error("directory {:?} does not exist", _0)]
DirectoryDoesNotExist(OsString),
#[error("{}", _0)]
ManifestError(#[from] ManifestError),
#[error("package name is missing - {:?}", _0)]
ProjectNameInvalid(OsString),
}

View File

@ -1,30 +0,0 @@
// 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/>.
#[derive(Debug, Error)]
pub enum LoginError {
#[error("No token was provided in the response")]
CannotGetToken,
#[error("Could not connect to the package manager")]
NoConnectionFound,
#[error("No login credentials were provided")]
NoCredentialsProvided,
#[error("Wrong login or password")]
WrongLoginOrPassword,
}

View File

@ -1,39 +0,0 @@
// 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/>.
pub mod add;
pub use self::add::*;
pub mod build;
pub use self::build::*;
pub mod init;
pub use self::init::*;
pub mod login;
pub use self::login::*;
pub mod new;
pub use self::new::*;
pub mod publish;
pub use self::publish::*;
pub mod run;
pub use self::run::*;
pub mod test;
pub use self::test::*;

View File

@ -1,34 +0,0 @@
// 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 leo_package::errors::ManifestError;
use std::{ffi::OsString, io};
#[derive(Debug, Error)]
pub enum NewError {
#[error("root directory {:?} creating: {}", _0, _1)]
CreatingRootDirectory(OsString, io::Error),
#[error("directory {:?} already exists", _0)]
DirectoryAlreadyExists(OsString),
#[error("{}", _0)]
ManifestError(#[from] ManifestError),
#[error("package name is missing - {:?}", _0)]
ProjectNameInvalid(OsString),
}

View File

@ -1,35 +0,0 @@
// 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 std::ffi::OsString;
#[derive(Debug, Error)]
pub enum PublishError {
#[error("connection unavailable {:?}", _0)]
ConnectionUnavalaible(OsString),
#[error("package toml file is missing a description")]
MissingPackageDescription,
#[error("package toml file is missing a license")]
MissingPackageLicense,
#[error("package toml file is missing a remote")]
MissingPackageRemote,
#[error("package not published {:?}", _0)]
PackageNotPublished(OsString),
}

View File

@ -1,28 +0,0 @@
// 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 leo_package::errors::ManifestError;
use std::ffi::OsString;
#[derive(Debug, Error)]
pub enum RunError {
#[error("main file {:?} does not exist", _0)]
MainFileDoesNotExist(OsString),
#[error("{}", _0)]
ManifestError(#[from] ManifestError),
}

View File

@ -1,22 +0,0 @@
// 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 std::ffi::OsString;
#[derive(Debug, Error)]
pub enum TestError {
#[error("could not find main or library file in {:?}", _0)]
ProgramFileDoesNotExist(OsString),
}

View File

@ -1,31 +0,0 @@
// 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/>.
#[derive(Debug, Error)]
pub enum UpdaterError {
#[error("{}: {}", _0, _1)]
Crate(&'static str, String),
#[error("The current version {} is more recent than the release version {}", _0, _1)]
OldReleaseVersion(String, String),
}
impl From<self_update::errors::Error> for UpdaterError {
fn from(error: self_update::errors::Error) -> Self {
tracing::error!("{}\n", error);
UpdaterError::Crate("self_update", error.to_string())
}
}

View File

@ -14,15 +14,13 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
#[macro_use] pub mod api;
extern crate thiserror;
pub mod cli;
pub mod cli_types;
pub mod commands; pub mod commands;
#[cfg_attr(tarpaulin, skip)]
pub mod config; pub mod config;
pub mod errors; pub mod context;
pub mod logger; pub mod logger;
pub mod synthesizer; pub mod synthesizer;
pub mod updater; pub mod updater;
#[cfg(test)]
mod tests;

View File

@ -14,8 +14,9 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use colored::Colorize;
use std::fmt; use std::fmt;
use colored::Colorize;
use tracing::{event::Event, subscriber::Subscriber}; use tracing::{event::Event, subscriber::Subscriber};
use tracing_subscriber::{ use tracing_subscriber::{
fmt::{format::*, time::*, FmtContext, FormattedFields}, fmt::{format::*, time::*, FmtContext, FormattedFields},

View File

@ -14,80 +14,197 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use leo_lang::{cli::*, commands::*, errors::CLIError, logger, updater::Updater}; pub mod api;
pub mod commands;
pub mod config;
pub mod context;
pub mod logger;
pub mod synthesizer;
pub mod updater;
use clap::{App, AppSettings, Arg}; use commands::{
package::{Add, Login, Logout, Publish, Remove},
Build,
Clean,
Command,
Deploy,
Init,
Lint,
New,
Prove,
Run,
Setup,
Test,
Update,
Watch,
};
#[cfg_attr(tarpaulin, skip)] use anyhow::Error;
fn main() -> Result<(), CLIError> { use std::process::exit;
let app = App::new("leo") use structopt::{clap::AppSettings, StructOpt};
.version(env!("CARGO_PKG_VERSION"))
.about("Leo compiler and package manager")
.author("The Aleo Team <hello@aleo.org>")
.settings(&[
AppSettings::ColoredHelp,
AppSettings::DisableHelpSubcommand,
AppSettings::DisableVersion,
])
.args(&[Arg::with_name("debug")
.short("d")
.long("debug")
.help("Enables debugging mode")
.global(true)])
.subcommands(vec![
NewCommand::new().display_order(0),
InitCommand::new().display_order(1),
BuildCommand::new().display_order(2),
WatchCommand::new().display_order(3),
TestCommand::new().display_order(4),
SetupCommand::new().display_order(5),
ProveCommand::new().display_order(6),
RunCommand::new().display_order(7),
LoginCommand::new().display_order(8),
AddCommand::new().display_order(9),
RemoveCommand::new().display_order(10),
PublishCommand::new().display_order(11),
DeployCommand::new().display_order(12),
CleanCommand::new().display_order(13),
LintCommand::new().display_order(14),
UpdateCommand::new().display_order(15),
LogoutCommand::new().display_order(16),
])
.set_term_width(0);
let mut help = app.clone(); /// CLI Arguments entry point - includes global parameters and subcommands
let arguments = app.get_matches(); #[derive(StructOpt, Debug)]
#[structopt(name = "leo", author = "The Aleo Team <hello@aleo.org>", setting = AppSettings::ColoredHelp)]
struct Opt {
#[structopt(short, long, help = "Print additional information for debugging")]
debug: bool,
match arguments.subcommand() { #[structopt(short, long, help = "Suppress CLI output")]
("new", Some(arguments)) => NewCommand::process(arguments), quiet: bool,
("init", Some(arguments)) => InitCommand::process(arguments),
("build", Some(arguments)) => BuildCommand::process(arguments), #[structopt(subcommand)]
("watch", Some(arguments)) => WatchCommand::process(arguments), command: CommandOpts,
("test", Some(arguments)) => TestCommand::process(arguments),
("setup", Some(arguments)) => SetupCommand::process(arguments),
("prove", Some(arguments)) => ProveCommand::process(arguments),
("run", Some(arguments)) => RunCommand::process(arguments),
("login", Some(arguments)) => LoginCommand::process(arguments),
("add", Some(arguments)) => AddCommand::process(arguments),
("remove", Some(arguments)) => RemoveCommand::process(arguments),
("publish", Some(arguments)) => PublishCommand::process(arguments),
("deploy", Some(arguments)) => DeployCommand::process(arguments),
("clean", Some(arguments)) => CleanCommand::process(arguments),
("lint", Some(arguments)) => LintCommand::process(arguments),
("update", Some(arguments)) => UpdateCommand::process(arguments),
("logout", Some(arguments)) => LogoutCommand::process(arguments),
_ => {
// Set logging environment
match arguments.is_present("debug") {
true => logger::init_logger("leo", 2),
false => logger::init_logger("leo", 1),
} }
Updater::print_cli(); ///Leo compiler and package manager
#[derive(StructOpt, Debug)]
#[structopt(setting = AppSettings::ColoredHelp)]
enum CommandOpts {
#[structopt(about = "Create a new Leo package in an existing directory")]
Init {
#[structopt(flatten)]
command: Init,
},
help.print_help()?; #[structopt(about = "Create a new Leo package in a new directory")]
println!(); New {
Ok(()) #[structopt(flatten)]
command: New,
},
#[structopt(about = "Compile the current package as a program")]
Build {
#[structopt(flatten)]
command: Build,
},
#[structopt(about = "Run a program setup")]
Setup {
#[structopt(flatten)]
command: Setup,
},
#[structopt(about = "Run the program and produce a proof")]
Prove {
#[structopt(flatten)]
command: Prove,
},
#[structopt(about = "Run a program with input variables")]
Run {
#[structopt(flatten)]
command: Run,
},
#[structopt(about = "Clean the output directory")]
Clean {
#[structopt(flatten)]
command: Clean,
},
#[structopt(about = "Watch for changes of Leo source files")]
Watch {
#[structopt(flatten)]
command: Watch,
},
#[structopt(about = "Update Leo to the latest version")]
Update {
#[structopt(flatten)]
command: Update,
},
#[structopt(about = "Compile and run all tests in the current package")]
Test {
#[structopt(flatten)]
command: Test,
},
#[structopt(about = "Install a package from the Aleo Package Manager")]
Add {
#[structopt(flatten)]
command: Add,
},
#[structopt(about = "Login to the Aleo Package Manager")]
Login {
#[structopt(flatten)]
command: Login,
},
#[structopt(about = "Logout of the Aleo Package Manager")]
Logout {
#[structopt(flatten)]
command: Logout,
},
#[structopt(about = "Publish the current package to the Aleo Package Manager")]
Publish {
#[structopt(flatten)]
command: Publish,
},
#[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)]
command: Lint,
},
#[structopt(about = "Deploy the current package as a program to the network (*)")]
Deploy {
#[structopt(flatten)]
command: Deploy,
},
}
fn main() {
// read command line arguments
let opt = Opt::from_args();
if !opt.quiet {
// init logger with optional debug flag
logger::init_logger("leo", match opt.debug {
false => 1,
true => 2,
});
}
handle_error(match opt.command {
CommandOpts::Init { command } => command.try_execute(),
CommandOpts::New { command } => command.try_execute(),
CommandOpts::Build { command } => command.try_execute(),
CommandOpts::Setup { command } => command.try_execute(),
CommandOpts::Prove { command } => command.try_execute(),
CommandOpts::Test { command } => command.try_execute(),
CommandOpts::Run { command } => command.try_execute(),
CommandOpts::Clean { command } => command.try_execute(),
CommandOpts::Watch { command } => command.try_execute(),
CommandOpts::Update { command } => command.try_execute(),
CommandOpts::Add { command } => command.try_execute(),
CommandOpts::Login { command } => command.try_execute(),
CommandOpts::Logout { command } => command.try_execute(),
CommandOpts::Publish { command } => command.try_execute(),
CommandOpts::Remove { command } => command.try_execute(),
CommandOpts::Lint { command } => command.try_execute(),
CommandOpts::Deploy { command } => command.try_execute(),
});
}
fn handle_error<T>(res: Result<T, Error>) -> T {
match res {
Ok(t) => t,
Err(err) => {
eprintln!("Error: {}", err);
exit(1);
} }
} }
} }

View File

@ -14,8 +14,9 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::synthesizer::{CircuitSynthesizer, SerializedField, SerializedIndex}; use std::convert::TryFrom;
use serde::{Deserialize, Serialize};
use snarkvm_curves::bls12_377::Bls12_377; use snarkvm_curves::bls12_377::Bls12_377;
use snarkvm_errors::curves::FieldError; use snarkvm_errors::curves::FieldError;
use snarkvm_models::{ use snarkvm_models::{
@ -23,8 +24,7 @@ use snarkvm_models::{
gadgets::r1cs::{ConstraintSystem, Index}, gadgets::r1cs::{ConstraintSystem, Index},
}; };
use serde::{Deserialize, Serialize}; use crate::synthesizer::{CircuitSynthesizer, SerializedField, SerializedIndex};
use std::convert::TryFrom;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SerializedCircuit { pub struct SerializedCircuit {

View File

@ -14,12 +14,12 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use snarkvm_errors::curves::FieldError; use std::{convert::TryFrom, str::FromStr};
use snarkvm_models::curves::{Field, Fp256, Fp256Parameters};
use num_bigint::BigUint; use num_bigint::BigUint;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, str::FromStr}; use snarkvm_errors::curves::FieldError;
use snarkvm_models::curves::{Field, Fp256, Fp256Parameters};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SerializedField(pub String); pub struct SerializedField(pub String);

View File

@ -14,9 +14,8 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use snarkvm_models::gadgets::r1cs::Index;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use snarkvm_models::gadgets::r1cs::Index;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub enum SerializedIndex { pub enum SerializedIndex {

130
leo/tests/mod.rs Normal file
View File

@ -0,0 +1,130 @@
// 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 std::path::PathBuf;
use anyhow::Result;
use crate::{
commands::{
package::{Login, Logout},
Build,
Command,
Prove,
Run,
Setup,
Test,
Update,
UpdateAutomatic,
},
context::{create_context, Context},
};
/// Path to the only complex Leo program that we have
/// - relative to source dir - where Cargo.toml is located
const PEDERSEN_HASH_PATH: &str = "./examples/pedersen-hash/";
#[test]
pub fn build_pedersen_hash() -> Result<()> {
Build::new().apply(ctx()?, ())?;
Ok(())
}
#[test]
pub fn setup_pedersen_hash() -> Result<()> {
let build = Build::new().apply(ctx()?, ())?;
Setup::new(false).apply(ctx()?, build.clone())?;
Setup::new(true).apply(ctx()?, build)?;
Ok(())
}
#[test]
pub fn prove_pedersen_hash() -> Result<()> {
let build = Build::new().apply(ctx()?, ())?;
let setup = Setup::new(false).apply(ctx()?, build)?;
Prove::new(false).apply(ctx()?, setup.clone())?;
Prove::new(true).apply(ctx()?, setup)?;
Ok(())
}
#[test]
pub fn run_pedersen_hash() -> Result<()> {
let build = Build::new().apply(ctx()?, ())?;
let setup = Setup::new(false).apply(ctx()?, build)?;
let prove = Prove::new(false).apply(ctx()?, setup)?;
Run::new(false).apply(ctx()?, prove.clone())?;
Run::new(true).apply(ctx()?, prove)?;
Ok(())
}
#[test]
pub fn test_pedersen_hash() -> Result<()> {
let mut main_file = PathBuf::from(PEDERSEN_HASH_PATH);
main_file.push("src/main.leo");
Test::new(Vec::new()).apply(ctx()?, ())?;
Test::new(vec![main_file]).apply(ctx()?, ())?;
Ok(())
}
#[test]
pub fn test_logout() -> Result<()> {
Logout::new().apply(ctx()?, ())?;
Ok(())
}
// Decided to not go all-in on error messages since they might change in the future
// So this test only tells that error cases are errors
#[test]
pub fn login_incorrect_credentials_or_token() -> Result<()> {
// no credentials passed
let login = Login::new(None, None, None).apply(ctx()?, ());
assert!(login.is_err());
// incorrect token
let login = Login::new(Some("none".to_string()), None, None).apply(ctx()?, ());
assert!(login.is_err());
// only user, no pass
let login = Login::new(None, Some("user".to_string()), None).apply(ctx()?, ());
assert!(login.is_err());
// no user, only pass
let login = Login::new(None, None, Some("pass".to_string())).apply(ctx()?, ());
assert!(login.is_err());
Ok(())
}
#[test]
pub fn leo_update_and_update_automatic() -> Result<()> {
Update::new(true, true, None).apply(ctx()?, ())?;
Update::new(false, true, None).apply(ctx()?, ())?;
Update::new(false, false, None).apply(ctx()?, ())?;
Update::new(false, false, Some(UpdateAutomatic::Automatic { value: true })).apply(ctx()?, ())?;
Update::new(false, false, Some(UpdateAutomatic::Automatic { value: false })).apply(ctx()?, ())?;
Ok(())
}
/// Create context for Pedersen Hash example
fn ctx() -> Result<Context> {
let path = PathBuf::from(&PEDERSEN_HASH_PATH);
let ctx = create_context(path)?;
Ok(ctx)
}

View File

@ -13,8 +13,10 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{config::Config, errors::UpdaterError};
use crate::config::Config;
use anyhow::{anyhow, Result};
use colored::Colorize; use colored::Colorize;
use self_update::{backends::github, version::bump_is_greater, Status}; use self_update::{backends::github, version::bump_is_greater, Status};
@ -27,7 +29,7 @@ impl Updater {
const LEO_REPO_OWNER: &'static str = "AleoHQ"; const LEO_REPO_OWNER: &'static str = "AleoHQ";
/// Show all available releases for `leo`. /// Show all available releases for `leo`.
pub fn show_available_releases() -> Result<(), UpdaterError> { pub fn show_available_releases() -> Result<()> {
let releases = github::ReleaseList::configure() let releases = github::ReleaseList::configure()
.repo_owner(Self::LEO_REPO_OWNER) .repo_owner(Self::LEO_REPO_OWNER)
.repo_name(Self::LEO_REPO_NAME) .repo_name(Self::LEO_REPO_NAME)
@ -46,7 +48,7 @@ impl Updater {
} }
/// Update `leo` to the latest release. /// Update `leo` to the latest release.
pub fn update_to_latest_release(show_output: bool) -> Result<Status, UpdaterError> { pub fn update_to_latest_release(show_output: bool) -> Result<Status> {
let status = github::Update::configure() let status = github::Update::configure()
.repo_owner(Self::LEO_REPO_OWNER) .repo_owner(Self::LEO_REPO_OWNER)
.repo_name(Self::LEO_REPO_NAME) .repo_name(Self::LEO_REPO_NAME)
@ -62,7 +64,7 @@ impl Updater {
} }
/// Check if there is an available update for `leo` and return the newest release. /// Check if there is an available update for `leo` and return the newest release.
pub fn update_available() -> Result<String, UpdaterError> { pub fn update_available() -> Result<String> {
let updater = github::Update::configure() let updater = github::Update::configure()
.repo_owner(Self::LEO_REPO_OWNER) .repo_owner(Self::LEO_REPO_OWNER)
.repo_name(Self::LEO_REPO_NAME) .repo_name(Self::LEO_REPO_NAME)
@ -76,7 +78,11 @@ impl Updater {
if bump_is_greater(&current_version, &latest_release.version)? { if bump_is_greater(&current_version, &latest_release.version)? {
Ok(latest_release.version) Ok(latest_release.version)
} else { } else {
Err(UpdaterError::OldReleaseVersion(current_version, latest_release.version)) Err(anyhow!(
"Old release version {} {}",
current_version,
latest_release.version
))
} }
} }