mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-09-21 10:49:59 +03:00
Merge #686
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:
commit
2794b9a1ab
@ -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
18
.circleci/leo-clone.sh
Executable 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
56
.circleci/leo-publish.sh
Executable 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
|
@ -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,
|
||||
|
160
leo/commands/package/clone.rs
Normal file
160
leo/commands/package/clone.rs
Normal file
@ -0,0 +1,160 @@
|
||||
// Copyright (C) 2019-2021 Aleo Systems Inc.
|
||||
// This file is part of the Leo library.
|
||||
|
||||
// The Leo library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// The Leo library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{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(())
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
11
leo/main.rs
11
leo/main.rs
@ -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(),
|
||||
|
Loading…
Reference in New Issue
Block a user