diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..d758e2dadd --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,163 @@ +version: 2.1 +commands: + setup_environment: + description: "Setup environment" + parameters: + cache_key: + type: string + default: leo-stable-cache + steps: + - run: set -e + - setup_remote_docker + - run: + name: Prepare environment and install dependencies + command: | + export SCCACHE_CACHE_SIZE=200M + export WORK_DIR="$CIRCLE_WORKING_DIRECTORY/.cache/sccache" + export SCCACHE_DIR="$CIRCLE_WORKING_DIRECTORY/.cache/sccache" + mkdir -p "$CIRCLE_WORKING_DIRECTORY/.bin" + wget https://github.com/mozilla/sccache/releases/download/0.2.13/sccache-0.2.13-x86_64-unknown-linux-musl.tar.gz + tar -C "$CIRCLE_WORKING_DIRECTORY/.bin" -xvf sccache-0.2.13-x86_64-unknown-linux-musl.tar.gz + mv $CIRCLE_WORKING_DIRECTORY/.bin/sccache-0.2.13-x86_64-unknown-linux-musl/sccache $CIRCLE_WORKING_DIRECTORY/.bin/sccache + export PATH="$PATH:$CIRCLE_WORKING_DIRECTORY/.bin" + export RUSTC_WRAPPER="sccache" + rm -rf "$CIRCLE_WORKING_DIRECTORY/.cargo/registry" + sudo apt-get update && sudo apt-get install -y clang llvm-dev llvm pkg-config xz-utils make libssl-dev libssl-dev + - restore_cache: + keys: + - << parameters.cache_key >> + clear_environment: + description: "Clear environment" + parameters: + cache_key: + type: string + default: leo-stable-cache + steps: + - run: (sccache -s||true) + - run: set +e + - save_cache: + key: << parameters.cache_key >> + paths: + - .cache/sccache + - .cargo +jobs: + + rust-stable: + docker: + - image: cimg/rust:1.50.0 + resource_class: xlarge + steps: + - checkout + - setup_environment: + cache_key: leo-stable-cache + - run: + name: Build and run tests + no_output_timeout: 30m + command: cargo install --path . --root . + - persist_to_workspace: + root: ~/ + paths: project/ + - clear_environment: + cache_key: leo-stable-cache + + leo-new: + docker: + - image: cimg/rust:1.50.0 + resource_class: xlarge + steps: + - attach_workspace: + at: /home/circleci/project/ + - run: + name: leo new + command: | + export LEO=/home/circleci/project/project/bin/leo + ./project/.circleci/leo-new.sh + + leo-init: + docker: + - image: cimg/rust:1.50.0 + resource_class: xlarge + steps: + - attach_workspace: + at: /home/circleci/project/ + - run: + name: leo init + command: | + export LEO=/home/circleci/project/project/bin/leo + ./project/.circleci/leo-init.sh + + leo-clean: + docker: + - image: cimg/rust:1.50.0 + resource_class: xlarge + steps: + - attach_workspace: + at: /home/circleci/project/ + - run: + name: leo clean + command: | + export LEO=/home/circleci/project/project/bin/leo + ./project/.circleci/leo-clean.sh + + leo-setup: + docker: + - image: cimg/rust:1.50.0 + resource_class: xlarge + steps: + - attach_workspace: + at: /home/circleci/project/ + - run: + name: leo setup + command: | + export LEO=/home/circleci/project/project/bin/leo + ./project/.circleci/leo-setup.sh + + leo-add-remove: + docker: + - image: cimg/rust:1.50.0 + resource_class: xlarge + steps: + - attach_workspace: + at: /home/circleci/project/ + - run: + name: leo add & remove + command: | + export LEO=/home/circleci/project/project/bin/leo + ./project/.circleci/leo-add-remove.sh + + leo-login-logout: + docker: + - image: cimg/rust:1.50.0 + resource_class: xlarge + steps: + - attach_workspace: + at: /home/circleci/project/ + - run: + name: leo login & logout + command: | + export LEO=/home/circleci/project/project/bin/leo + ./project/.circleci/leo-login-logout.sh + +workflows: + version: 2 + main-workflow: + jobs: + - rust-stable + - leo-new: + requires: + - rust-stable + - leo-init: + requires: + - rust-stable + - leo-clean: + requires: + - rust-stable + - leo-setup: + requires: + - rust-stable + - leo-add-remove: + requires: + - rust-stable + - leo-login-logout: + requires: + - rust-stable \ No newline at end of file diff --git a/.circleci/leo-add-remove.sh b/.circleci/leo-add-remove.sh new file mode 100755 index 0000000000..e7eb352934 --- /dev/null +++ b/.circleci/leo-add-remove.sh @@ -0,0 +1,6 @@ +# leo add (w/o login) & remove + +$LEO new my-app && cd my-app +$LEO add howard/silly-sudoku +$LEO remove silly-sudoku +$LEO clean diff --git a/.circleci/leo-clean.sh b/.circleci/leo-clean.sh new file mode 100755 index 0000000000..46bd90880c --- /dev/null +++ b/.circleci/leo-clean.sh @@ -0,0 +1,34 @@ +# leo new hello-world + +$LEO new hello-world +ls -la +cd hello-world && ls -la +$LEO run + +# Assert that the 'outputs' folder is not empty + +cd outputs || exit 1 +if [ "$(ls -A $DIR)" ]; then + echo "$DIR is not empty" +else + echo "$DIR is empty" + exit 1 +fi +cd .. + +# leo clean + +$LEO clean +cd outputs && ls -la +cd .. + +# Assert that the 'outputs' folder is empty + +cd outputs || exit 1 +if [ "$(ls -A $DIR)" ]; then + echo "$DIR is not empty" + exit 1 +else + echo "$DIR is empty" + exit 0 +fi diff --git a/.circleci/leo-init.sh b/.circleci/leo-init.sh new file mode 100755 index 0000000000..ab7724cd4a --- /dev/null +++ b/.circleci/leo-init.sh @@ -0,0 +1,4 @@ +mkdir hello-world && cd hello-world || exit 1 +$LEO init +ls -la +$LEO run diff --git a/.circleci/leo-login-logout.sh b/.circleci/leo-login-logout.sh new file mode 100755 index 0000000000..1353b18d72 --- /dev/null +++ b/.circleci/leo-login-logout.sh @@ -0,0 +1,7 @@ +# leo login & logout + +$LEO new my-app && cd my-app || exit 1 +$LEO login -u "$ALEO_PM_USERNAME" -p "$ALEO_PM_PASSWORD" +$LEO add howard/silly-sudoku +$LEO remove silly-sudoku +$LEO logout diff --git a/.circleci/leo-new.sh b/.circleci/leo-new.sh new file mode 100755 index 0000000000..cb24618546 --- /dev/null +++ b/.circleci/leo-new.sh @@ -0,0 +1,4 @@ +$LEO new hello-world +ls -la +cd hello-world && ls -la +$LEO run diff --git a/.circleci/leo-setup.sh b/.circleci/leo-setup.sh new file mode 100755 index 0000000000..ae6e23803f --- /dev/null +++ b/.circleci/leo-setup.sh @@ -0,0 +1,7 @@ +# leo setup + +cd ./project/examples/pedersen-hash || exit 1 +$LEO setup +$LEO setup +$LEO setup --skip-key-check +$LEO clean diff --git a/.github/workflows/leo-add-remove.yml b/.github/workflows/leo-add-remove.yml deleted file mode 100644 index 8a0912ba96..0000000000 --- a/.github/workflows/leo-add-remove.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: leo-add-remove -on: - pull_request: - push: - branches: - - master - paths-ignore: - - 'docs/**' - - 'documentation/**' -env: - RUST_BACKTRACE: 1 - -jobs: - add: - name: Add Package ('leo add') - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt - - - name: Install Leo - uses: actions-rs/cargo@v1 - env: - CARGO_NET_GIT_FETCH_WITH_CLI: true - with: - command: install - args: --path . - - - name: 'leo add (w/o login) & remove' - run: | - cd .. && leo new my-app && cd my-app - leo add argus4130/xnor - leo remove xnor - leo clean - diff --git a/.github/workflows/leo-clean.yml b/.github/workflows/leo-clean.yml deleted file mode 100644 index 177ed73fdc..0000000000 --- a/.github/workflows/leo-clean.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: leo-clean -on: - pull_request: - push: - branches: - - master - paths-ignore: - - 'docs/**' - - 'documentation/**' -env: - RUST_BACKTRACE: 1 - -jobs: - new: - name: Hello Leo ('leo new hello-world') - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt - - - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install Leo - uses: actions-rs/cargo@v1 - with: - command: install - args: --path . - - - name: 'leo new hello-world' - run: | - cd .. - leo new hello-world - ls -la - cd hello-world && ls -la - leo run - - - name: Assert that the 'outputs' folder is not empty - run: | - cd ../hello-world/outputs - if [ "$(ls -A $DIR)" ]; then - echo "$DIR is not empty" - else - echo "$DIR is empty" - exit 1 - fi - - - name: 'leo clean' - run: | - cd ../hello-world - leo clean - cd outputs && ls -la - - - name: Assert that the 'outputs' folder is empty - run: | - cd ../hello-world/outputs - if [ "$(ls -A $DIR)" ]; then - echo "$DIR is not empty" - exit 1 - else - echo "$DIR is empty" - exit 0 - fi diff --git a/.github/workflows/leo-init.yml b/.github/workflows/leo-init.yml deleted file mode 100644 index 1821d1de86..0000000000 --- a/.github/workflows/leo-init.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: leo-init -on: - pull_request: - push: - branches: - - master - paths-ignore: - - 'docs/**' - - 'documentation/**' -env: - RUST_BACKTRACE: 1 - -jobs: - init: - name: Hello Leo ('leo init') - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt - - - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install Leo - uses: actions-rs/cargo@v1 - with: - command: install - args: --path . - - - name: 'leo init' - run: | - cd .. && mkdir hello-world && cd hello-world - leo init - ls -la - leo run diff --git a/.github/workflows/leo-login-logout.yml b/.github/workflows/leo-login-logout.yml deleted file mode 100644 index 95a17e42ed..0000000000 --- a/.github/workflows/leo-login-logout.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: leo-login-logout -on: - pull_request: - push: - branches: - - master - paths-ignore: - - 'docs/**' - - 'documentation/**' -env: - RUST_BACKTRACE: 1 - -jobs: - add: - name: Add Package ('leo add') - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt - - - name: Install Leo - uses: actions-rs/cargo@v1 - env: - CARGO_NET_GIT_FETCH_WITH_CLI: true - with: - command: install - args: --path . - - - name: 'leo login & logout' - env: - USER: ${{ secrets.ALEO_PM_USERNAME }} - PASS: ${{ secrets.ALEO_PM_PASSWORD }} - run: | - cd .. && leo new my-app && cd my-app - leo login -u "$USER" -p "$PASS" - leo add argus4130/xnor - leo remove xnor - leo logout - diff --git a/.github/workflows/leo-new.yml b/.github/workflows/leo-new.yml deleted file mode 100644 index 842d553897..0000000000 --- a/.github/workflows/leo-new.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: leo-new -on: - pull_request: - push: - branches: - - master - paths-ignore: - - 'docs/**' - - 'documentation/**' -env: - RUST_BACKTRACE: 1 - -jobs: - new: - name: Hello Leo ('leo new hello-world') - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt - - - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install Leo - uses: actions-rs/cargo@v1 - with: - command: install - args: --path . - - - name: 'leo new hello-world' - run: | - cd .. - leo new hello-world - ls -la - cd hello-world && ls -la - leo run diff --git a/.github/workflows/leo-setup.yml b/.github/workflows/leo-setup.yml deleted file mode 100644 index 7bad2924c2..0000000000 --- a/.github/workflows/leo-setup.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: leo-setup -on: - pull_request: - push: - branches: - - master - paths-ignore: - - 'docs/**' - - 'documentation/**' -env: - RUST_BACKTRACE: 1 - -jobs: - add: - name: Add Package ('leo add') - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - components: rustfmt - - - name: Install Leo - uses: actions-rs/cargo@v1 - env: - CARGO_NET_GIT_FETCH_WITH_CLI: true - with: - command: install - args: --path . - - - name: 'leo setup for examples' - env: - USER: ${{ secrets.ALEO_PM_USERNAME }} - PASS: ${{ secrets.ALEO_PM_PASSWORD }} - run: | - cd examples/pedersen-hash - leo setup - leo setup - leo setup --skip-key-check - leo clean - diff --git a/.gitignore b/.gitignore index 169d72f25d..f9e224d6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,9 @@ /tmp/ **.idea/ *.DS_Store + +**/process.yml + +**/.crates.toml +**/.crates2.json +**/bin/ diff --git a/.resources/leo.png b/.resources/banner.png similarity index 100% rename from .resources/leo.png rename to .resources/banner.png diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000000..357d549c32 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,31 @@ +# Development Guide + +## Running CircleCI locally + +### Step 1: Install CircleCI + +If you wish to run CircleCI locally, start by installing it: + +- macOS +``` +brew install circleci +``` + +- Linux (via Snap) +``` +sudo snap install docker circleci +sudo snap connect circleci:docker docker +``` + +- Windows (via Chocolatey) +``` +choco install circleci-cli -y +``` + +### Step 2: Run CircleCI + +To run a job, export the config to `process.yml`, and specify it when executing: +```shell +circleci config process .circleci/config.yml > process.yml +circleci local execute -c process.yml --job JOB_NAME +``` diff --git a/README.md b/README.md index ca19dd7fe9..e09580624c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

The Leo Programming Language

@@ -27,6 +27,7 @@ Leo is a functional, statically-typed programming language built for writing pri ## 1. Overview + Welcome to the Leo programming language. Leo provides a high-level language that abstracts low-level cryptographic concepts and makes it easy to diff --git a/leo/commands/build.rs b/leo/commands/build.rs index 1c2662f203..9f562dd85a 100644 --- a/leo/commands/build.rs +++ b/leo/commands/build.rs @@ -36,16 +36,10 @@ use structopt::StructOpt; use tracing::span::Span; /// Compile and build program command -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Build {} -impl Build { - pub fn new() -> Build { - Build {} - } -} - impl Command for Build { type Input = (); type Output = Option<(Compiler<'static, Fq, EdwardsGroupType>, bool)>; @@ -140,8 +134,8 @@ impl Command for Build { let temporary_program = program.clone(); let output = temporary_program.compile_constraints(&mut cs)?; - tracing::debug!("Compiled constraints - {:#?}", output); - tracing::debug!("Number of constraints - {:#?}", cs.num_constraints()); + tracing::debug!("Compiled output - {:#?}", output); + tracing::info!("Number of constraints - {:#?}", cs.num_constraints()); // Serialize the circuit let circuit_object = SerializedCircuit::from(cs); diff --git a/leo/commands/clean.rs b/leo/commands/clean.rs index ecb13ea5a0..0b39661f07 100644 --- a/leo/commands/clean.rs +++ b/leo/commands/clean.rs @@ -23,16 +23,10 @@ use structopt::StructOpt; use tracing::span::Span; /// Clean outputs folder command -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Clean {} -impl Clean { - pub fn new() -> Clean { - Clean {} - } -} - impl Command for Clean { type Input = (); type Output = (); diff --git a/leo/commands/deploy.rs b/leo/commands/deploy.rs index f27da1dae5..06eff0c2e6 100644 --- a/leo/commands/deploy.rs +++ b/leo/commands/deploy.rs @@ -21,16 +21,10 @@ use structopt::StructOpt; use tracing::span::Span; /// Deploy Leo program to the network -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Deploy {} -impl Deploy { - pub fn new() -> Deploy { - Deploy {} - } -} - impl Command for Deploy { type Input = (); type Output = (); diff --git a/leo/commands/init.rs b/leo/commands/init.rs index 7ebe2a3214..7c6b3e7745 100644 --- a/leo/commands/init.rs +++ b/leo/commands/init.rs @@ -23,16 +23,10 @@ use structopt::StructOpt; use tracing::span::Span; /// Init Leo project command within current directory -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Init {} -impl Init { - pub fn new() -> Init { - Init {} - } -} - impl Command for Init { type Input = (); type Output = (); @@ -46,13 +40,20 @@ impl Command for Init { } fn apply(self, _: Context, _: Self::Input) -> Result { + // Derive the package directory path. let path = current_dir()?; + + // Check that the given package name is valid. let package_name = path .file_stem() .ok_or_else(|| anyhow!("Project name invalid"))? .to_string_lossy() .to_string(); + if !LeoPackage::is_package_name_valid(&package_name) { + return Err(anyhow!("Invalid Leo project name")); + } + // Check that the current package directory path exists. if !path.exists() { return Err(anyhow!("Directory does not exist")); } diff --git a/leo/commands/lint.rs b/leo/commands/lint.rs index 0aeda51844..3e60d7ec6e 100644 --- a/leo/commands/lint.rs +++ b/leo/commands/lint.rs @@ -21,16 +21,10 @@ use structopt::StructOpt; use tracing::span::Span; /// Lint Leo code command -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Lint {} -impl Lint { - pub fn new() -> Lint { - Lint {} - } -} - impl Command for Lint { type Input = (); type Output = (); diff --git a/leo/commands/mod.rs b/leo/commands/mod.rs index f59db22864..47d5994575 100644 --- a/leo/commands/mod.rs +++ b/leo/commands/mod.rs @@ -52,7 +52,7 @@ pub mod test; pub use test::Test; pub mod update; -pub use update::{Sub as UpdateAutomatic, Update}; +pub use update::{Automatic as UpdateAutomatic, Update}; pub mod watch; pub use watch::Watch; @@ -60,57 +60,55 @@ pub use watch::Watch; // Aleo PM related commands pub mod package; -/// Base trait for Leo CLI, see methods and their documentation for details +/// Base trait for the 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. + /// If the current command requires running another command beforehand + /// and needs its output result, this is where the result type is defined. /// Example: type Input: ::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 + /// Defines the output of this command, which may be used as `Input` for another + /// command. If this command is not used as a prelude for another command, + /// this field may be left 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 + /// Returns the project context, which is defined as the current directory. fn context(&self) -> Result { 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 + /// Adds a span to the logger via `tracing::span`. + /// Because of the specifics of the macro implementation, it is not possible + /// to set the span name with a non-literal i.e. a dynamic variable even if this + /// variable is a &'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. + /// Runs the prelude and returns the Input of the current command. fn prelude(&self) -> Result 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 + /// Runs the main operation of this command. This function is run within + /// context of 'execute' function, which sets logging and timers. fn apply(self, context: Context, input: Self::Input) -> Result where Self: std::marker::Sized; - /// Wrapper around apply function, sets up tracing, time tracking and context + /// A wrapper around the `apply` method. + /// This function sets up tracing, timing, and the context. fn execute(self) -> Result where Self: std::marker::Sized, { let input = self.prelude()?; - // create span for this command + // Create the span for this command. let span = self.log_span(); let span = span.enter(); - // calculate execution time for each run + // Calculate the execution time for this command. let timer = Instant::now(); let context = self.context()?; @@ -118,7 +116,7 @@ pub trait Command { drop(span); - // use done context to print time + // Use the done context to print the execution time for this command. tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { tracing::info!("Finished in {} milliseconds \n", timer.elapsed().as_millis()); }); @@ -126,7 +124,7 @@ pub trait Command { out } - /// Execute command but empty the result. Comes in handy where there's a + /// Executes 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<()> diff --git a/leo/commands/new.rs b/leo/commands/new.rs index 2509e7563e..a4da67c309 100644 --- a/leo/commands/new.rs +++ b/leo/commands/new.rs @@ -30,12 +30,6 @@ pub struct New { name: String, } -impl New { - pub fn new(name: String) -> New { - New { name } - } -} - impl Command for New { type Input = (); type Output = (); @@ -49,13 +43,17 @@ impl Command for New { } fn apply(self, _: Context, _: Self::Input) -> Result { - let mut path = current_dir()?; + // Check that the given package name is valid. let package_name = self.name; + if !LeoPackage::is_package_name_valid(&package_name) { + return Err(anyhow!("Invalid Leo project name")); + } - // Derive the package directory path + // Derive the package directory path. + let mut path = current_dir()?; 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() { return Err(anyhow!("Directory already exists {:?}", path)); } diff --git a/leo/commands/package/logout.rs b/leo/commands/package/logout.rs index 56497d8b23..6bd76a2575 100644 --- a/leo/commands/package/logout.rs +++ b/leo/commands/package/logout.rs @@ -22,16 +22,10 @@ use structopt::StructOpt; use tracing::Span; /// Remove credentials for Aleo PM from .leo directory -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Logout {} -impl Logout { - pub fn new() -> Logout { - Logout {} - } -} - impl Command for Logout { type Input = (); type Output = (); diff --git a/leo/commands/package/publish.rs b/leo/commands/package/publish.rs index 6fb949046f..ffcc1725e2 100644 --- a/leo/commands/package/publish.rs +++ b/leo/commands/package/publish.rs @@ -37,23 +37,17 @@ struct ResponseJson { } /// Publish package to Aleo Package Manager -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Publish {} -impl Publish { - pub fn new() -> Publish { - Publish {} - } -} - impl Command for Publish { type Input = ::Output; type Output = Option; /// Build program before publishing fn prelude(&self) -> Result { - Build::new().execute() + (Build {}).execute() } fn apply(self, context: Context, _input: Self::Input) -> Result { diff --git a/leo/commands/package/remove.rs b/leo/commands/package/remove.rs index 83de4a6ce8..f0df722146 100644 --- a/leo/commands/package/remove.rs +++ b/leo/commands/package/remove.rs @@ -22,19 +22,13 @@ use structopt::StructOpt; use tracing::span::Span; /// Remove imported package -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[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 = (); diff --git a/leo/commands/prove.rs b/leo/commands/prove.rs index 202cb688d3..dbe19b5777 100644 --- a/leo/commands/prove.rs +++ b/leo/commands/prove.rs @@ -28,17 +28,11 @@ use structopt::StructOpt; use tracing::span::Span; /// Run the program and produce a proof -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Prove { #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] - skip_key_check: bool, -} - -impl Prove { - pub fn new(skip_key_check: bool) -> Prove { - Prove { skip_key_check } - } + pub(crate) skip_key_check: bool, } impl Command for Prove { @@ -50,7 +44,8 @@ impl Command for Prove { } fn prelude(&self) -> Result { - Setup::new(self.skip_key_check).execute() + let skip_key_check = self.skip_key_check; + (Setup { skip_key_check }).execute() } fn apply(self, context: Context, input: Self::Input) -> Result { diff --git a/leo/commands/run.rs b/leo/commands/run.rs index c9973dfd6b..b1bf652ed7 100644 --- a/leo/commands/run.rs +++ b/leo/commands/run.rs @@ -26,17 +26,11 @@ use structopt::StructOpt; use tracing::span::Span; /// Build, Prove and Run Leo program with inputs -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Run { #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] - skip_key_check: bool, -} - -impl Run { - pub fn new(skip_key_check: bool) -> Run { - Run { skip_key_check } - } + pub(crate) skip_key_check: bool, } impl Command for Run { @@ -48,7 +42,8 @@ impl Command for Run { } fn prelude(&self) -> Result { - Prove::new(self.skip_key_check).execute() + let skip_key_check = self.skip_key_check; + (Prove { skip_key_check }).execute() } fn apply(self, _context: Context, input: Self::Input) -> Result { diff --git a/leo/commands/setup.rs b/leo/commands/setup.rs index a2b3fb9388..fad11bbe20 100644 --- a/leo/commands/setup.rs +++ b/leo/commands/setup.rs @@ -31,17 +31,11 @@ use structopt::StructOpt; use tracing::span::Span; /// Executes the setup command for a Leo program -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Setup { #[structopt(long = "skip-key-check", help = "Skip key verification")] - skip_key_check: bool, -} - -impl Setup { - pub fn new(skip_key_check: bool) -> Setup { - Setup { skip_key_check } - } + pub(crate) skip_key_check: bool, } impl Command for Setup { @@ -57,7 +51,7 @@ impl Command for Setup { } fn prelude(&self) -> Result { - Build::new().execute() + (Build {}).execute() } fn apply(self, context: Context, input: Self::Input) -> Result { @@ -70,7 +64,6 @@ impl Command for Setup { let keys_exist = ProvingKeyFile::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 let (proving_key, prepared_verifying_key) = if !keys_exist || checksum_differs { tracing::info!("Starting..."); diff --git a/leo/commands/test.rs b/leo/commands/test.rs index d6a3a30840..f5de8672f2 100644 --- a/leo/commands/test.rs +++ b/leo/commands/test.rs @@ -32,17 +32,11 @@ use structopt::StructOpt; use tracing::span::Span; /// Build program and run tests command -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Test { #[structopt(short = "f", long = "file", name = "file")] - files: Vec, -} - -impl Test { - pub fn new(files: Vec) -> Test { - Test { files } - } + pub(crate) files: Vec, } impl Command for Test { diff --git a/leo/commands/update.rs b/leo/commands/update.rs index 09e0ca640a..f3027e5907 100644 --- a/leo/commands/update.rs +++ b/leo/commands/update.rs @@ -22,7 +22,7 @@ use tracing::span::Span; /// Setting for automatic updates of Leo #[derive(Debug, StructOpt, PartialEq)] -pub enum Sub { +pub enum Automatic { Automatic { #[structopt(name = "bool", help = "Boolean value: true or false", parse(try_from_str))] value: bool, @@ -30,30 +30,20 @@ pub enum Sub { } /// Update Leo to the latest version -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Update { /// List all available versions of Leo #[structopt(short, long)] - list: bool, + pub(crate) list: bool, /// For Aleo Studio only #[structopt(short, long)] - studio: bool, + pub(crate) studio: bool, /// Setting for automatic updates of Leo #[structopt(subcommand)] - automatic: Option, -} - -impl Update { - pub fn new(list: bool, studio: bool, automatic: Option) -> Update { - Update { - list, - studio, - automatic, - } - } + pub(crate) automatic: Option, } impl Command for Update { @@ -69,13 +59,13 @@ impl Command for Update { } fn apply(self, _: Context, _: Self::Input) -> Result { - // if --list is passed - simply list everything and exit + // If --list is passed, list all available versions and return. 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 { + // Handles enabling and disabling automatic updates in the config file. + if let Some(Automatic::Automatic { value }) = self.automatic { Config::set_update_automatic(value)?; match value { diff --git a/leo/commands/watch.rs b/leo/commands/watch.rs index 529dd0ce84..7a98b9604f 100644 --- a/leo/commands/watch.rs +++ b/leo/commands/watch.rs @@ -27,7 +27,7 @@ use tracing::span::Span; const LEO_SOURCE_DIR: &str = "src/"; /// Watch file changes in src/ directory and run Build Command -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Debug)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] pub struct Watch { /// Set up watch interval @@ -35,12 +35,6 @@ pub struct Watch { interval: u64, } -impl Watch { - pub fn new(interval: u64) -> Watch { - Watch { interval } - } -} - impl Command for Watch { type Input = (); type Output = (); @@ -70,7 +64,7 @@ impl Command for Watch { match rx.recv() { // See changes on the write event Ok(DebouncedEvent::Write(_write)) => { - match Build::new().execute() { + match (Build {}).execute() { Ok(_output) => { tracing::info!("Built successfully"); } diff --git a/leo/tests/mod.rs b/leo/tests/mod.rs index 0ebc7b26ee..7bc611f49d 100644 --- a/leo/tests/mod.rs +++ b/leo/tests/mod.rs @@ -39,34 +39,34 @@ const PEDERSEN_HASH_PATH: &str = "./examples/pedersen-hash/"; #[test] pub fn build_pedersen_hash() -> Result<()> { - Build::new().apply(context()?, ())?; + (Build {}).apply(context()?, ())?; Ok(()) } #[test] pub fn setup_pedersen_hash() -> Result<()> { - let build = Build::new().apply(context()?, ())?; - Setup::new(false).apply(context()?, build.clone())?; - Setup::new(true).apply(context()?, build)?; + let build = (Build {}).apply(context()?, ())?; + (Setup { skip_key_check: false }).apply(context()?, build.clone())?; + (Setup { skip_key_check: true }).apply(context()?, build)?; Ok(()) } #[test] pub fn prove_pedersen_hash() -> Result<()> { - let build = Build::new().apply(context()?, ())?; - let setup = Setup::new(false).apply(context()?, build)?; - Prove::new(false).apply(context()?, setup.clone())?; - Prove::new(true).apply(context()?, setup)?; + let build = (Build {}).apply(context()?, ())?; + let setup = (Setup { skip_key_check: false }).apply(context()?, build)?; + (Prove { skip_key_check: false }).apply(context()?, setup.clone())?; + (Prove { skip_key_check: true }).apply(context()?, setup)?; Ok(()) } #[test] pub fn run_pedersen_hash() -> Result<()> { - let build = Build::new().apply(context()?, ())?; - let setup = Setup::new(false).apply(context()?, build)?; - let prove = Prove::new(false).apply(context()?, setup)?; - Run::new(false).apply(context()?, prove.clone())?; - Run::new(true).apply(context()?, prove)?; + let build = (Build {}).apply(context()?, ())?; + let setup = (Setup { skip_key_check: false }).apply(context()?, build)?; + let prove = (Prove { skip_key_check: false }).apply(context()?, setup)?; + (Run { skip_key_check: false }).apply(context()?, prove.clone())?; + (Run { skip_key_check: true }).apply(context()?, prove)?; Ok(()) } @@ -75,14 +75,14 @@ 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(context()?, ())?; - Test::new(vec![main_file]).apply(context()?, ())?; + (Test { files: vec![] }).apply(context()?, ())?; + (Test { files: vec![main_file] }).apply(context()?, ())?; Ok(()) } #[test] pub fn test_logout() -> Result<()> { - Logout::new().apply(context()?, ())?; + (Logout {}).apply(context()?, ())?; Ok(()) } @@ -111,12 +111,40 @@ pub fn login_incorrect_credentials_or_token() -> Result<()> { #[test] pub fn leo_update_and_update_automatic() -> Result<()> { - Update::new(true, true, None).apply(context()?, ())?; - Update::new(false, true, None).apply(context()?, ())?; - Update::new(false, false, None).apply(context()?, ())?; + let update = Update { + list: true, + studio: true, + automatic: None, + }; + update.apply(context()?, ())?; - Update::new(false, false, Some(UpdateAutomatic::Automatic { value: true })).apply(context()?, ())?; - Update::new(false, false, Some(UpdateAutomatic::Automatic { value: false })).apply(context()?, ())?; + let update = Update { + list: false, + studio: true, + automatic: None, + }; + update.apply(context()?, ())?; + + let update = Update { + list: false, + studio: false, + automatic: None, + }; + update.apply(context()?, ())?; + + let update = Update { + list: false, + studio: false, + automatic: Some(UpdateAutomatic::Automatic { value: true }), + }; + update.apply(context()?, ())?; + + let update = Update { + list: false, + studio: false, + automatic: Some(UpdateAutomatic::Automatic { value: false }), + }; + update.apply(context()?, ())?; Ok(()) } diff --git a/package/src/errors/package.rs b/package/src/errors/package.rs index 63bab79fd8..f0a07ea1ee 100644 --- a/package/src/errors/package.rs +++ b/package/src/errors/package.rs @@ -26,6 +26,9 @@ pub enum PackageError { #[error("Failed to initialize package {:?} ({:?})", _0, _1)] FailedToInitialize(String, OsString), + #[error("Invalid project name: {:?}", _0)] + InvalidPackageName(String), + #[error("`{}` metadata: {}", _0, _1)] Removing(&'static str, io::Error), } diff --git a/package/src/errors/root/manifest.rs b/package/src/errors/root/manifest.rs index b9645830d3..1e732a817f 100644 --- a/package/src/errors/root/manifest.rs +++ b/package/src/errors/root/manifest.rs @@ -18,6 +18,9 @@ use std::io; #[derive(Debug, Error)] pub enum ManifestError { + #[error("{}: {}", _0, _1)] + Crate(&'static str, String), + #[error("`{}` creating: {}", _0, _1)] Creating(&'static str, io::Error), @@ -36,3 +39,9 @@ pub enum ManifestError { #[error("`{}` writing: {}", _0, _1)] Writing(&'static str, io::Error), } + +impl From for ManifestError { + fn from(error: crate::errors::PackageError) -> Self { + ManifestError::Crate("leo-package", error.to_string()) + } +} diff --git a/package/src/lib.rs b/package/src/lib.rs index fb3d000893..eb7cf4bb00 100644 --- a/package/src/lib.rs +++ b/package/src/lib.rs @@ -37,6 +37,11 @@ impl LeoPackage { package::Package::initialize(package_name, is_lib, path) } + /// Returns `true` if the given Leo package name is valid. + pub fn is_package_name_valid(package_name: &str) -> bool { + package::Package::is_package_name_valid(package_name) + } + /// Removes an imported Leo package pub fn remove_imported_package(package_name: &str, path: &Path) -> Result<(), PackageError> { package::Package::remove_imported_package(package_name, path) diff --git a/package/src/package.rs b/package/src/package.rs index f149c4ddf1..f5c46e2696 100644 --- a/package/src/package.rs +++ b/package/src/package.rs @@ -34,17 +34,79 @@ pub struct Package { } impl Package { - pub fn new(package_name: &str) -> Self { - Self { + pub fn new(package_name: &str) -> Result { + // Check that the package name is valid. + if !Self::is_package_name_valid(package_name) { + return Err(PackageError::InvalidPackageName(package_name.to_string())); + } + + Ok(Self { name: package_name.to_owned(), version: "0.1.0".to_owned(), description: None, license: None, + }) + } + + /// Returns `true` if the package name is valid. + /// + /// Package names must be lowercase and composed solely + /// of ASCII alphanumeric characters, and may be word-separated + /// by a single dash '-'. + pub fn is_package_name_valid(package_name: &str) -> bool { + // Check that the package name is nonempty. + if package_name.is_empty() { + tracing::error!("Project names must be nonempty"); + return false; } + + let mut previous = package_name.chars().next().unwrap(); + + // Check that the first character is not a dash. + if previous == '-' { + tracing::error!("Project names cannot begin with a dash"); + return false; + } + + // Iterate and check that the package name is valid. + for current in package_name.chars() { + // Check that the package name is lowercase. + if !current.is_ascii_lowercase() && current != '-' { + tracing::error!("Project names must be all lowercase"); + return false; + } + + // Check that the package name is only ASCII alphanumeric or a dash. + if !current.is_ascii_alphanumeric() && current != '-' { + tracing::error!("Project names must be ASCII alphanumeric, and may be word-separated with a dash"); + return false; + } + + // If the previous character was a dash, check that the current character is not a dash. + if previous == '-' && current == '-' { + tracing::error!("Project names may only be word-separated by one dash"); + return false; + } + + previous = current; + } + + // Check that the last character is not a dash. + if previous == '-' { + tracing::error!("Project names cannot end with a dash"); + return false; + } + + true } /// Returns `true` if a package is can be initialized at a given path. pub fn can_initialize(package_name: &str, is_lib: bool, path: &Path) -> bool { + // Check that the package name is valid. + if !Self::is_package_name_valid(package_name) { + return false; + } + let mut result = true; let mut existing_files = vec![]; @@ -91,6 +153,11 @@ impl Package { /// Returns `true` if a package is initialized at the given path pub fn is_initialized(package_name: &str, is_lib: bool, path: &Path) -> bool { + // Check that the package name is valid. + if !Self::is_package_name_valid(package_name) { + return false; + } + // Check if the manifest file exists. if !Manifest::exists_at(&path) { return false; @@ -137,7 +204,7 @@ impl Package { // Next, initialize this directory as a Leo package. { // Create the manifest file. - Manifest::new(&package_name).write_to(&path)?; + Manifest::new(&package_name)?.write_to(&path)?; // Verify that the .gitignore file does not exist. if !Gitignore::exists_at(&path) { @@ -190,3 +257,27 @@ impl Package { Ok(ImportsDirectory::remove_import(path, package_name)?) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_package_name_valid() { + assert!(Package::is_package_name_valid("foo")); + assert!(Package::is_package_name_valid("foo-bar")); + assert!(Package::is_package_name_valid("foo-bar-baz")); + + assert!(!Package::is_package_name_valid("")); + assert!(!Package::is_package_name_valid("-")); + assert!(!Package::is_package_name_valid("-foo")); + assert!(!Package::is_package_name_valid("-foo-")); + assert!(!Package::is_package_name_valid("foo--bar")); + assert!(!Package::is_package_name_valid("foo---bar")); + assert!(!Package::is_package_name_valid("foo--bar--baz")); + assert!(!Package::is_package_name_valid("foo---bar---baz")); + assert!(!Package::is_package_name_valid("foo*bar")); + assert!(!Package::is_package_name_valid("foo,bar")); + assert!(!Package::is_package_name_valid("foo_bar")); + } +} diff --git a/package/src/root/manifest.rs b/package/src/root/manifest.rs index 3a85f60447..540749fcdc 100644 --- a/package/src/root/manifest.rs +++ b/package/src/root/manifest.rs @@ -39,11 +39,11 @@ pub struct Manifest { } impl Manifest { - pub fn new(package_name: &str) -> Self { - Self { - project: Package::new(package_name), + pub fn new(package_name: &str) -> Result { + Ok(Self { + project: Package::new(package_name)?, remote: None, - } + }) } pub fn filename() -> String { diff --git a/package/tests/initialize/initialize.rs b/package/tests/initialize/initialize.rs index e3cffcfd8c..5e1b98bde8 100644 --- a/package/tests/initialize/initialize.rs +++ b/package/tests/initialize/initialize.rs @@ -52,7 +52,10 @@ fn initialize_fails_with_existing_manifest() { assert!(Package::can_initialize(TEST_PACKAGE_NAME, false, &test_directory)); // Manually add a manifest file to the `test_directory` - Manifest::new(TEST_PACKAGE_NAME).write_to(&test_directory).unwrap(); + Manifest::new(TEST_PACKAGE_NAME) + .unwrap() + .write_to(&test_directory) + .unwrap(); // Attempt to initialize a package at the `test_directory` assert!(Package::initialize(TEST_PACKAGE_NAME, false, &test_directory).is_err());