686: Adds 'leo clone' command, adds CI for 'leo clone' and 'leo publish' r=collinc97 a=damirka

Closes #685 

## Motivation

Has been described in #685 

- Adds 'leo clone' command
- Adds CI for 'leo clone' and 'leo publish'

## Testing plan

This PR introduces another integration test.
- Published package: https://aleo.pm/@leobot/test-app
- Workflow run: https://app.circleci.com/pipelines/github/AleoHQ/leo/66/workflows/25edb77e-8fbb-4482-95a2-4dab2ebcf15c/jobs/242

## Related PRs

This PR closes testing gap left after #676 


Co-authored-by: damirka <damirka.ru@gmail.com>
Co-authored-by: howardwu <howardwu@berkeley.edu>
This commit is contained in:
bors[bot] 2021-02-25 19:27:39 +00:00 committed by GitHub
commit 2794b9a1ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 294 additions and 19 deletions

View File

@ -138,6 +138,32 @@ jobs:
export LEO=/home/circleci/project/project/bin/leo
./project/.circleci/leo-login-logout.sh
leo-clone:
docker:
- image: cimg/rust:1.50.0
resource_class: xlarge
steps:
- attach_workspace:
at: /home/circleci/project/
- run:
name: leo clone
command: |
export LEO=/home/circleci/project/project/bin/leo
./project/.circleci/leo-clone.sh
leo-publish:
docker:
- image: cimg/rust:1.50.0
resource_class: xlarge
steps:
- attach_workspace:
at: /home/circleci/project/
- run:
name: leo publish
command: |
export LEO=/home/circleci/project/project/bin/leo
./project/.circleci/leo-publish.sh
workflows:
version: 2
main-workflow:
@ -160,4 +186,10 @@ workflows:
- rust-stable
- leo-login-logout:
requires:
- rust-stable
- rust-stable
- leo-clone:
requires:
- rust-stable
- leo-publish:
requires:
- rust-stable

18
.circleci/leo-clone.sh Executable file
View File

@ -0,0 +1,18 @@
# leo clone
# Clone the test-app package.
export PACKAGE="$ALEO_PM_USERNAME/test-app"
$LEO clone $PACKAGE
# Assert that the 'test-app' folder is not empty
cd test-app || exit 1
if [ "$(ls -A $DIR)" ]; then
echo "$DIR is not empty"
else
echo "$DIR is empty"
exit 1
fi
ls -la
$LEO run

56
.circleci/leo-publish.sh Executable file
View File

@ -0,0 +1,56 @@
# leo login, publish and logout
# Login
$LEO login -u "$ALEO_PM_USERNAME" -p "$ALEO_PM_PASSWORD"
# Clone the test-app package.
export PACKAGE="$ALEO_PM_USERNAME/test-app"
$LEO clone $PACKAGE
cd test-app || exit 1
# Fetch the current Leo package version number.
#
# 1. Print out the Leo.toml file.
# 2. Search for a line with the word "version".
# 3. Isolate that into a single line.
# 4. Split the line from the '=' sign and keep the right-hand side.
# 5. Remove the quotes around the version number.
# 6. Trim any excess whitespace.
export CURRENT=$(cat Leo.toml \
| grep version \
| head -1 \
| awk -F= '{ print $2 }' \
| sed 's/[",]//g' \
| xargs)
# Increment the current Leo package version number by 1.
#
# 1. Print out the Leo.toml file.
# 2. Search for a line with the word "version".
# 3. Isolate that into a single line.
# 4. Split the line from the '=' sign and keep the right-hand side.
# 5. Remove the quotes around the version number.
# 6. Trim any excess whitespace.
# 7. Increment the version number by 1 (on the semver patch).
#
# https://stackoverflow.com/questions/8653126/how-to-increment-version-number-in-a-shell-script
export UPDATED=$(cat Leo.toml \
| grep version \
| head -1 \
| awk -F= '{ print $2 }' \
| sed 's/[",]//g' \
| xargs \
| awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}')
# Write the updated Leo package version number to the Leo.toml file.
export TOML=$(cat Leo.toml | sed "s/$CURRENT/$UPDATED/g")
echo "$TOML" > Leo.toml
# Run the package to confirm the manifest remains well-formed.
$LEO run
# Publish the package to Aleo.pm
$LEO publish
# Logout
$LEO logout

View File

@ -25,7 +25,7 @@ use std::{
use structopt::StructOpt;
use tracing::Span;
/// Add package from Aleo Package Manager
/// Add a package from Aleo Package Manager
#[derive(StructOpt, Debug)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Add {
@ -58,7 +58,7 @@ impl Add {
}
/// Try to parse author/package string from self.remote
pub fn try_read_arguments(&self) -> Result<(String, String)> {
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 {
@ -91,42 +91,41 @@ impl Command for Add {
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// checking that manifest exists...
// Check that a manifest exists for the current package.
if context.manifest().is_err() {
return Err(anyhow!("Package Manifest not found, try running leo init or leo new"));
return Err(anyhow!("Package manifest not found, try running `leo init`"));
};
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,
// Attempt to fetch the package.
let reader = {
let fetch = Fetch {
author,
package_name: package_name.clone(),
version: self.version,
};
let bytes = context.api.run_route(fetch)?.bytes()?;
std::io::Cursor::new(bytes)
};
let bytes = context.api.run_route(fetch)?.bytes()?;
// Construct the directory structure.
let mut path = context.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);
// Proceed to unzip and parse the fetched 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,

View File

@ -0,0 +1,160 @@
// Copyright (C) 2019-2021 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{api::Fetch, commands::Command, context::Context};
use anyhow::{anyhow, Result};
use std::{
borrow::Cow,
fs::{self, File},
io::{Read, Write},
path::Path,
};
use structopt::StructOpt;
use tracing::Span;
/// Clone a package from Aleo Package Manager
#[derive(StructOpt, Debug)]
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Clone {
#[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 Clone {
pub fn new(
remote: Option<String>,
author: Option<String>,
package: Option<String>,
version: Option<String>,
) -> Self {
Self {
remote,
author,
package,
version,
}
}
/// Try to parse author/package string from self.remote
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"
))
}
}
/// Creates a directory at the provided path with the given directory name.
fn create_directory(path: &Path, directory_name: &str) -> Result<()> {
let mut path = Cow::from(path);
// Check that the path ends in the directory name.
// If it does not, proceed to append the directory name to the path.
if path.is_dir() && !path.ends_with(directory_name) {
path.to_mut().push(directory_name);
}
Ok(fs::create_dir_all(&path)?)
}
}
impl Command for Clone {
type Input = ();
type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Cloning")
}
fn prelude(&self) -> Result<Self::Input> {
Ok(())
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
let (author, package_name) = match self.try_read_arguments() {
Ok((author, package)) => (author, package),
Err(err) => return Err(err),
};
// Attempt to fetch the package.
let reader = {
let fetch = Fetch {
author,
package_name: package_name.clone(),
version: self.version,
};
let bytes = context.api.run_route(fetch)?.bytes()?;
std::io::Cursor::new(bytes)
};
// Construct the directory structure.
let mut path = context.dir()?;
path.push(package_name.clone());
Self::create_directory(&path, &package_name)?;
// Proceed to unzip and parse the fetched 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('/') {
fs::create_dir_all(file_path)?;
} else {
if let Some(parent_directory) = path.parent() {
fs::create_dir_all(parent_directory)?;
}
File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::<Vec<u8>>())?;
}
}
tracing::info!("Successfully cloned {}", package_name);
Ok(())
}
}

View File

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

View File

@ -23,7 +23,7 @@ pub mod synthesizer;
pub mod updater;
use commands::{
package::{Add, Login, Logout, Publish, Remove},
package::{Add, Clone, Login, Logout, Publish, Remove},
Build,
Clean,
Command,
@ -121,12 +121,18 @@ enum CommandOpts {
command: Test,
},
#[structopt(about = "Install a package from the Aleo Package Manager")]
#[structopt(about = "Import a package from the Aleo Package Manager")]
Add {
#[structopt(flatten)]
command: Add,
},
#[structopt(about = "Clone a package from the Aleo Package Manager")]
Clone {
#[structopt(flatten)]
command: Clone,
},
#[structopt(about = "Login to the Aleo Package Manager")]
Login {
#[structopt(flatten)]
@ -189,6 +195,7 @@ fn main() {
CommandOpts::Update { command } => command.try_execute(),
CommandOpts::Add { command } => command.try_execute(),
CommandOpts::Clone { command } => command.try_execute(),
CommandOpts::Login { command } => command.try_execute(),
CommandOpts::Logout { command } => command.try_execute(),
CommandOpts::Publish { command } => command.try_execute(),