diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5d2a3e..2c4b7de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ env: RELEASE_DIR: artifacts GITHUB_REF: '${{ github.ref }}' WINDOWS_TARGET: x86_64-pc-windows-msvc - MACOS_TARGET: x86_64-apple-darwin + MACOS_TARGET: aarch64-apple-darwin LINUX_TARGET: x86_64-unknown-linux-gnu jobs: @@ -21,10 +21,10 @@ jobs: matrix: include: - target: x86_64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 rust: stable - - target: x86_64-apple-darwin - os: macos-12 + - target: aarch64-apple-darwin + os: macos-14 rust: stable - target: x86_64-pc-windows-msvc os: windows-2022 @@ -44,7 +44,7 @@ jobs: echo ::set-output name=version::"${GITHUB_REF:10}" - name: Install p7zip (MacOS) - if: matrix.os == 'macos-12' + if: matrix.os == 'macos-14' run: brew install p7zip - name: Add rustup target @@ -63,7 +63,7 @@ jobs: mkdir -p ${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }} - name: Move binaries (Linux/MacOS) - if: matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-12' + if: matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-14' run: | mv ./target/${{ matrix.target }}/release/${{ env.RELEASE_BIN }} ${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}/${{ env.RELEASE_BIN }} diff --git a/Cargo.toml b/Cargo.toml index 4f49c89..53725c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pchain_compile" -version = "0.4.2" +version = "0.4.4" authors = ["ParallelChain Lab "] edition = "2021" description = "ParallelChain Smart Contract Compile CLI - A command line tool for compiling ParallelChain Smart Contract." @@ -17,17 +17,24 @@ path = "src/bin/main.rs" [dependencies] bollard = "0.14.0" -clap = {version = "4.3.11", features = ["derive"]} +clap = { version = "4.3.11", features = ["derive"] } cargo = "0.72.2" -cargo_toml = "0.11.5" +cargo_toml = "0.20.4" dunce = "1.0.2" faccess = "0.2.4" rand = "0.6.0" thiserror = "1.0.31" -tokio = {version = "1.19", features = ["full"]} +tokio = { version = "1.19", features = ["full"] } wasm-snip = "=0.4.0" futures-util = "0.3.28" flate2 = "1.0.26" tar = "0.4.38" wasm-opt = "=0.114.0" -walrus = "=0.12" \ No newline at end of file +walrus = "=0.12" +bstr = "=1.6.0" +log = "0.4" +fern = { version = "0.6", features = ["date-based"] } +chrono = { version = "0.4.22", default-features = false } + +[dev-dependencies] +assert_cmd = "2.0.16" diff --git a/README.md b/README.md index 387a4dd..b7d44b6 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,27 @@ # ParallelChain Mainnet Contract Compiler (pchain_compile) -`pchain_compile` is a command line tool for reproducibly building Rust code into compact, gas-efficient WebAssembly [ParallelChain Mainnet Smart Contracts](https://github.com/parallelchain-io/parallelchain-protocol/blob/master/Contracts.md). +`pchain_compile` is a command line tool for reproducibly building [Rust](https://www.rust-lang.org/) code into compact, gas-efficient WebAssembly [ParallelChain Mainnet Smart Contracts](https://github.com/parallelchain-io/parallelchain-protocol/blob/master/Contracts.md). ## Pre-Requisites -`pchain_compile` builds the source code in a docker environment. To know more about Docker and install it, refer to the [official instructions](https://docs.docker.com/get-docker/). +`pchain_compile` compiles the ParallelChain smart contract code from Rust into a WebAssembly binary. To know more about developing ParallelChain smart contracts, visit [ParallelChain Mainnet Contract SDK](https://crates.io/crates/pchain-sdk). + +By default, the compiler requires **docker** to be installed in your local machine. In detail, it pulls the [Docker Image](#using-the-pchain_compile-docker-image) from DockerHub, and starts a docker container which provides a complete environment for building WebAssembly binary. To install docker, follow the instructions in [Docker Docs](https://docs.docker.com/get-docker/). ## Installation -Prebuilt binaries can be downloaded from assets in Github [releases page](https://github.com/parallelchain-io/pchain-compile/releases). Alternatively, you can install by `cargo install` if Rust has been installed already. + +Download prebuilt executables from the Github [Releases page](https://github.com/parallelchain-io/pchain-compile/releases). + +Alternatively, you can install the binary crate [pchain_compile](https://crates.io/crates/pchain_compile) by [cargo install](https://doc.rust-lang.org/cargo/commands/cargo-install.html) if Rust has been installed already. ```sh cargo install pchain_compile ``` -## Build the Source Code +## Build Smart Contract -Suppose you have the source code of smart contract in the folder `contract` under your home directory. +Let's say your smart contract source code is in the folder `contract` under your home directory. ```text /home/ @@ -27,18 +32,23 @@ Suppose you have the source code of smart contract in the folder `contract` unde |- Cargo.toml ``` -To build smart contract into WebAssembly bytecode (file extension `.wasm`), you can simply run the program by specifying the arguments **source** and **destination**. +Run `pchain_compile` with the arguments **source** and **destination** to specify the folder of the source code and the folder for saving the result. -On a Linux Bash Shell: - ```sh -$ ./pchain_compile build --source /home/user/contract --destination /home/user/result +pchain_compile build --source /home/user/contract --destination /home/user/result +``` + +Once complete, the console displays message: + +```text Build process started. This could take several minutes for large contracts. Finished compiling. ParallelChain Mainnet smart contract(s) ["contract.wasm"] are saved at (/home/user/result). ``` -On a Windows Shell: +Your WebAssembly smart contract is now saved with file extension `.wasm` at the destination folder. + +If you are running on Windows, here is the example output: ```powershell $ .\pchain_compile.exe build --source 'C:\Users\user\contract' --destination 'C:\Users\user\result' Build process started. This could take several minutes for large contracts. @@ -46,18 +56,34 @@ Build process started. This could take several minutes for large contracts. Finished compiling. ParallelChain Mainnet smart contract(s) ["contract.wasm"] are saved at (C:\Users\user\result). ``` -Explanation about the command and its arguments can be displayed by appending "help" or "--help" to `pchain_compile`. +To understand more about the commands and arguments, run `pchain_compile build --help`. -## Toolchain +## Compile with Docker (default) -`pchain_compile` utilizes a docker_image hosted on a public DockerHub repository of ParallelChain see [here](https://hub.docker.com/r/parallelchainlab/pchain_compile) for the build process. Required components include: +`pchain_compile` pulls a docker image from ParallelChain Lab's official DockerHub [repository](https://hub.docker.com/r/parallelchainlab/pchain_compile) for the build process. The docker image provides an environment with installed components: - rustc: compiler for Rust. - wasm-snip: WASM utility which removes functions that are never called at runtime. - wasm-opt: WASM utility to load WebAssembly in text format and run Binaryen IR passes to optimize its size. For more information on Binaryen IR see [here](http://webassembly.github.io/binaryen/). -The docker images utilize a toolchain whose versions of each component are shown in the following table: +There are different tags of the docker image. They vary on the versions of the components. The table below describes the tags and their differences. + +| Image Tag | rustc | wasm-snip | wasm-opt | +| :-------- | :----- | :-------- | :------- | +| latest | 1.80.1 | 0.4.0 | 114 | +| 0.4.3 | 1.77.1 | 0.4.0 | 114 | +| 0.4.2 | 1.71.0 | 0.4.0 | 114 | +| mainnet01 | 1.66.1 | 0.4.0 | 109 | + +To build a smart contract in a specific docker environment, run with argument **use-docker-tag**. For example, + +```sh +pchain_compile build --source /home/user/contract --destination /home/user/result --use-docker-tag 0.4.3 +``` + +If **use-docker-tag** is not used, will pull the docker image with tag `latest`. -|Image Tag |rustc |wasm-snip |wasm-opt | -|:---|:---|:---|:---| -|0.4.2 |1.71.0 |0.4.0 |114 | -|mainnet01 |1.66.1 |0.4.0 |109 | +## Compile without Docker +Run `pchain_compile` with the argument **dockerless** will compile smart contract without docker. User needs to make sure **wasm32-unknown-unknown** has already been installed to the active rust tool chain. +For linux user: +1. Run `rustup target install wasm32-unknown-unknown` to install **wasm32-unknown-unknown** +2. Run `rustup show` to check if **wasm32-unknown-unknown** been installed to acitve toolchain succesfully or not \ No newline at end of file diff --git a/docker_image/Dockerfile b/docker_image/Dockerfile index 913d689..8abb431 100644 --- a/docker_image/Dockerfile +++ b/docker_image/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.71.0 as cache +FROM rust:1.80.1 as cache # Extract and compile wasm-opt & install wasm-snip RUN apt update && apt-get -y install wget && \ @@ -7,7 +7,7 @@ RUN apt update && apt-get -y install wget && \ rm -rf binaryen-version_114* # pchain-compile base image -FROM rust:1.71.0 as base-image +FROM rust:1.80.1 as base-image # Setup rust with wasm support RUN cargo install wasm-snip && rustup target add wasm32-unknown-unknown && mkdir -p /root/bin diff --git a/src/bin/main.rs b/src/bin/main.rs index f89f807..e0b34d2 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -8,8 +8,11 @@ //! in a docker environment. use clap::Parser; -use pchain_compile::{config::Config, DockerConfig, DockerOption, BuildOptions}; -use std::path::{Path, PathBuf}; +use pchain_compile::{config::Config, BuildOptions, DockerConfig, DockerOption}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; #[derive(Debug, Parser)] #[clap( @@ -39,16 +42,16 @@ enum PchainCompile { /// the contract will be built with the dependencies specified in the file. It is equivalent to /// running "cargo build" with the flag "--locked". If the file does not exist, the building process continues /// without using the version-locked dependencies. - /// + /// /// With or without the file "Cargo.lock", the compilation output includes the file "Cargo.lock" which was used or /// generated in the building process. #[clap(long = "locked", display_order = 3, verbatim_doc_comment)] locked: bool, /// Compile contract without using docker. This option requires installation of Rust and target "wasm32-unknown-unknown". - /// **Please note the compiled contracts are not always consistent with the previous compiled ones, because the building + /// **Please note the compiled contracts are not always consistent with the previous compiled ones, because the building /// process happens in your local changing environment.** - /// + /// /// To install target "wasm32-unknown-unknown", run the following command: /// /// $ rustup target add wasm32-unknown-unknown @@ -66,6 +69,7 @@ enum PchainCompile { /// Available tags: /// - mainnet01 /// - 0.4.2 + /// - 0.4.3 #[clap( long = "use-docker-tag", display_order = 5, @@ -73,6 +77,15 @@ enum PchainCompile { group = "docker-option" )] docker_image_tag: Option, + + /// Set log level, default is Error + /// + /// Available log levels: + /// - Error + /// - Info + /// - Debug (provide compile progress information ) + #[clap(long = "log-level", display_order = 6, verbatim_doc_comment)] + log_level: Option, }, } @@ -86,6 +99,7 @@ async fn main() { locked, dockerless, docker_image_tag, + log_level, } => { if source_path.is_empty() { println!("Please provide at least one source!"); @@ -93,10 +107,8 @@ async fn main() { } println!("Build process started. This could take several minutes for large contracts."); - let build_options = BuildOptions { - locked - }; - + let build_options = BuildOptions { locked }; + let docker_option = if dockerless { DockerOption::Dockerless } else { @@ -104,7 +116,24 @@ async fn main() { tag: docker_image_tag, }) }; - + let log_level_filter = log_level.map_or(log::LevelFilter::Error, |level_str| { + log::LevelFilter::from_str(&level_str).unwrap() + }); + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "{}[{}][{}] {}", + chrono::Local::now().format("[%H:%M:%S]"), + record.target(), + record.level(), + message + )) + }) + .level(log::LevelFilter::Error) + .level_for("pchain_compile", log_level_filter) + .chain(std::io::stdout()) + .apply() + .unwrap(); // Spawn threads to handle each contract code let mut join_handles = vec![]; source_path.into_iter().for_each(|source_path| { diff --git a/src/build.rs b/src/build.rs index b2a68fd..4378940 100644 --- a/src/build.rs +++ b/src/build.rs @@ -31,19 +31,25 @@ use std::{collections::HashSet, path::PathBuf}; use std::fs; use crate::error::Error; -use crate::{DockerConfig, BuildOptions}; +use crate::{BuildOptions, DockerConfig}; /// `build_target` takes the path to the cargo manifest file(s), generates an optimized WASM binary(ies) after building /// the source code and saves the binary(ies) to the designated destination_path. -/// +/// /// This method is equivalent to run the command: -/// +/// /// `pchain_compile` build --source `source_path` --destination `destination_path` pub async fn build_target( source_path: PathBuf, destination_path: Option, ) -> Result { - build_target_with_docker(source_path, destination_path, BuildOptions::default(), DockerConfig::default()).await + build_target_with_docker( + source_path, + destination_path, + BuildOptions::default(), + DockerConfig::default(), + ) + .await } /// Validates inputs and trigger building process that uses docker. @@ -74,7 +80,14 @@ pub(crate) async fn build_target_with_docker( return Err(Error::UnkownDockerImageTag(docker_image_tag)); } - build_target_in_docker(source_path, destination_path, options, docker_image_tag, wasm_file).await + build_target_in_docker( + source_path, + destination_path, + options, + docker_image_tag, + wasm_file, + ) + .await } /// Validates inputs and trigger building process that does not use docker. @@ -152,14 +165,15 @@ async fn compile_contract_in_docker_container( options: BuildOptions, wasm_file: &str, ) -> Result<(), Error> { + log::debug!("Start to copy dependencies to docker..."); // Step 1. create dependency directory and copy source to docker for dependency in dependencies { crate::docker::copy_files(docker, container_name, &dependency).await?; } - + log::debug!("Start to copy source code files to docker..."); // Step 2: create directory paths inside docker and copy file to container crate::docker::copy_files(docker, container_name, source_path.to_str().unwrap()).await?; - + log::debug!("Start to build source code inside docker..."); // Step 3: build the source code inside docker let (result_in_docker, build_log) = crate::docker::build_contracts( docker, @@ -169,21 +183,24 @@ async fn compile_contract_in_docker_container( wasm_file, ) .await?; - + log::debug!( + "Finished build, start to copy {:?} file from docker", + wasm_file + ); // Step 4: copy file from docker to given location crate::docker::copy_files_from( docker, container_name, &result_in_docker, destination_path.clone(), - build_log + build_log, ) .await?; - + log::debug!("Finished copy file {:?} from docker", wasm_file); Ok(()) } -/// Setup filesystem and build contract by cargo. It manages to create a temporary workding folder and +/// Setup filesystem and build contract by cargo. It manages to create a temporary workding folder and /// remove it after call. async fn build_target_by_cargo( source_path: PathBuf, @@ -208,4 +225,4 @@ async fn build_target_by_cargo( let _ = std::fs::remove_dir_all(temp_dir); result.map(|_| wasm_file) -} \ No newline at end of file +} diff --git a/src/docker.rs b/src/docker.rs index a7ae395..8f63524 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -7,9 +7,12 @@ //! copying files to container and executing commands inside docker. use std::{ + ffi::OsStr, + fs, io::{Read, Write}, ops::Not, - path::{Path, PathBuf}, time::Duration, + path::{Path, PathBuf}, + time::Duration, }; use bollard::{ @@ -32,8 +35,8 @@ use std::fs::File; use crate::error::Error; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -/// List of docker image tags that can be used. The first (0-indexed) is the default one. -pub(crate) const PCHAIN_COMPILE_IMAGE_TAGS: [&str; 2] = [env!("CARGO_PKG_VERSION"), "mainnet01"]; +/// List of docker image tags that can be used. The first (0-indexed) is the default one. +pub(crate) const PCHAIN_COMPILE_IMAGE_TAGS: [&str; 4] = ["latest", "0.4.3", "0.4.2", "mainnet01"]; /// The repo name in Parallelchain Lab Dockerhub: https://hub.docker.com/r/parallelchainlab/pchain_compile pub(crate) const PCHAIN_COMPILE_IMAGE: &str = "parallelchainlab/pchain_compile"; const DOCKER_EXEC_TIME_LIMIT: u64 = 15; // secs. It is a time limit to normal docker execution (except cargo build). @@ -122,16 +125,24 @@ pub async fn copy_files( let src_path = Path::new(source_path).to_path_buf(); let dst_path = Path::new( format!( - "{}-{}.tar.gz", + "{}/{}-{}.tar.gz", + std::env::temp_dir().to_str().unwrap(), container_name, src_path.file_name().unwrap().to_str().unwrap() ) .as_str(), ) .to_path_buf(); - - create_tar_gz(src_path, &save_to_path, &dst_path).map_err(|_| Error::DockerDaemonFailure)?; - + log::debug!( + "Creating temporary compression file {:?} ...", + dst_path.to_str().unwrap_or_default() + ); + create_tar_gz(src_path, &save_to_path, &dst_path) + .map_err(|err| Error::BuildFailure(err.to_string()))?; + log::debug!( + "Finished temporary compression file {:?} creation, and start to copy file to docker container...", + dst_path.to_str().unwrap_or_default() + ); // Read Content let file_content = File::open(dst_path.clone()) .map(|mut file| { @@ -152,10 +163,16 @@ pub async fn copy_files( file_content.into(), ) .await; - + log::debug!( + "Finished compression file {:?} copy, and start delete the temp compression file...", + dst_path.to_str().unwrap_or_default() + ); // Remove file let _ = std::fs::remove_file(&dst_path); // remove the compressed file .tar.gz - + log::debug!( + "{:?} has been deleted...", + dst_path.to_str().unwrap_or_default() + ); result.map_err(|_| Error::DockerDaemonFailure) } @@ -208,7 +225,9 @@ pub async fn build_contracts( locked: bool, wasm_file: &str, ) -> Result<(String, String), Error> { - let source_path_str = source_path.to_str().unwrap() + let source_path_str = source_path + .to_str() + .unwrap() .replace(':', "") .replace('\\', "/") .replace(' ', "_"); @@ -245,7 +264,7 @@ pub async fn build_contracts( Some(&working_folder_code), cmd_cargo_build, true, - None + None, ) .await .map_err(|e| Error::BuildFailure(e.to_string()))?; @@ -295,12 +314,10 @@ pub async fn build_contracts( // Save Cargo.lock to output folder if applicable if locked { - cmds.push( - ( - &working_folder_code, - vec!["mv", "Cargo.lock", output_folder] - ) - ); + cmds.push(( + &working_folder_code, + vec!["mv", "Cargo.lock", output_folder], + )); } for (working_dir, cmd) in cmds { @@ -310,7 +327,7 @@ pub async fn build_contracts( Some(working_dir), cmd, false, - Some(DOCKER_EXEC_TIME_LIMIT) + Some(DOCKER_EXEC_TIME_LIMIT), ) .await .map_err(|e| Error::BuildFailure(e.to_string()))?; @@ -341,8 +358,55 @@ fn create_tar_gz( let tar_gz = File::create(dst_path)?; let enc = GzEncoder::new(tar_gz, Compression::default()); let mut tar = tar::Builder::new(enc); - tar.append_dir_all(tar_path, src_path)?; - tar.finish() + if src_path.is_dir() { + // iter the folder and copy all files or sub-directory except "/target" if exists + for entry in fs::read_dir(src_path.clone())? { + let entry = entry?; + let file_type = entry.file_type()?; + if file_type.is_dir() && entry.file_name().eq(&OsStr::new("target")) { + log::debug!("Ignored /target directory"); + continue; + } else { + let full_path_of_entry = src_path.join(entry.file_name()); + let full_path_of_entry_str = full_path_of_entry + .to_str() + .unwrap_or_default() + .trim_start_matches("/") + .to_owned(); + if file_type.is_dir() { + tar.append_dir_all(full_path_of_entry_str, full_path_of_entry)?; + } else if file_type.is_file() { + let mut file = File::open(full_path_of_entry.clone())?; + tar.append_file(full_path_of_entry_str, &mut file)?; + } else if file_type.is_symlink() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Smart Contract directory should not include symbolic or hard link!!!", + )); + } else { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Smart Contract directory contains unrecognized file type!!!", + )); + } + } + } + tar.finish() + } else if src_path.is_file() { + let mut file = File::open(src_path)?; + tar.append_file(tar_path, &mut file)?; + tar.finish() + } else if src_path.is_symlink() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Smart Contract dependencies should not include symbolic or hard link!!!", + )); + } else { + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Smart Contract dependencies contains unrecognized file type!!!", + )); + } } fn files_from_tar_gz(tar_gz_bytes: Vec) -> Result)>, Error> { @@ -376,7 +440,7 @@ async fn execute( working_dir: Option<&str>, cmd: Vec<&str>, log_output: bool, - timeout_secs: Option + timeout_secs: Option, ) -> Result { let create_exec_results = docker .create_exec( @@ -405,13 +469,13 @@ async fn execute( match start_exec_results { bollard::exec::StartExecResults::Attached { output, .. } => { - let log_outputs = - if log_output { - output.try_collect::>() + let log_outputs = if log_output { + output + .try_collect::>() .await .map_err(|e| Error::BuildFailure(e.to_string()))? .into_iter() - .map(|output| output.to_string() ) + .map(|output| output.to_string()) .collect() } else { Vec::new() @@ -422,14 +486,16 @@ async fn execute( if let Some(timeout) = timeout_secs { let is_inspect_ok = tokio::time::timeout(Duration::from_secs(timeout), async { loop { - if let Ok(inspect_result) = docker.inspect_exec(&create_exec_results.id).await { + if let Ok(inspect_result) = + docker.inspect_exec(&create_exec_results.id).await + { if inspect_result.running != Some(true) { - return true + return true; } // Continue to check if the execution finishes. } else { // Fail to inspect. The loop should be terminated. - return false + return false; } // A small delay to avoid hitting docker endpoint immediately. tokio::time::sleep(Duration::from_millis(20)).await; @@ -438,14 +504,16 @@ async fn execute( .await .map_err(|_| Error::BuildTimeout)?; if !is_inspect_ok { - return Err(Error::BuildFailureWithLogs(log_outputs)) + return Err(Error::BuildFailureWithLogs(log_outputs)); } } - return Ok(log_outputs) - }, + return Ok(log_outputs); + } bollard::exec::StartExecResults::Detached => { - return Err(Error::BuildFailure("Execution Result Not Attached".to_string())); + return Err(Error::BuildFailure( + "Execution Result Not Attached".to_string(), + )); } } } diff --git a/src/lib.rs b/src/lib.rs index 9fc6a71..9622c29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,13 +8,13 @@ //! Cargo in a docker environment. //! //! # Example -//! ```no_run +//! ```ignore //! let source_path = Path::new("/home/user/contract").to_path_buf(); //! let result = pchain_compile::build_target(source_path, None).await; //! ``` -//! +//! //! # Example - Run from a configuration -//! ```no_run +//! ```ignore //! let result = pchain_compile::Config { //! source_path: Path::new("/home/user/contract").to_path_buf(), //! docker_option: pchain_compile::DockerOption::Dockerless, diff --git a/tests/test.rs b/tests/test.rs index fb17d0c..3b89a6e 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -5,9 +5,11 @@ //! Basic tests to demonstrate common usage of pchain_compile. +use assert_cmd::assert::OutputAssertExt; +use pchain_compile::{BuildOptions, DockerConfig, DockerOption}; +use std::fs; use std::path::Path; - -use pchain_compile::{DockerOption, BuildOptions, DockerConfig}; +use std::process::Command; #[tokio::test] async fn build_contract() { @@ -94,7 +96,7 @@ async fn build_contract_without_docker() { build_options: BuildOptions { locked: true }, docker_option: DockerOption::Dockerless, } - .run() + .run() .await; let wasm_name = match run_result { @@ -109,3 +111,90 @@ async fn build_contract_without_docker() { let _ = std::fs::remove_file(destination_path.join(&wasm_name)); assert_eq!(wasm_name, "hello_contract.wasm"); } + +#[test] +fn build_from_command_line_with_docker() { + let source_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("contracts") + .join("hello_contract") + .to_path_buf(); + Command::new("pchain_compile") + .args(&["build", "--source", source_path.to_str().unwrap()]) + .assert() + .success(); + let ret = fs::read_dir(env!("CARGO_MANIFEST_DIR")) + .unwrap() + .filter(|element| { + let entry = element.as_ref().unwrap(); + let file_type = entry.file_type().unwrap(); + let file_name = entry.file_name(); + file_type.is_file() && file_name.eq_ignore_ascii_case("hello_contract.wasm") + }) + .count(); + assert_eq!(ret, 1); + let wasm_file_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("hello_contract.wasm"); + let _ = std::fs::remove_file(wasm_file_path); +} + +#[test] +fn build_from_command_line_without_docker() { + let source_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("contracts") + .join("hello_contract") + .to_path_buf(); + Command::new("pchain_compile") + .args(&[ + "build", + "--source", + source_path.to_str().unwrap(), + "--dockerless", + ]) + .assert() + .success(); + let ret = fs::read_dir(env!("CARGO_MANIFEST_DIR")) + .unwrap() + .filter(|element| { + let entry = element.as_ref().unwrap(); + let file_type = entry.file_type().unwrap(); + let file_name = entry.file_name(); + file_type.is_file() && file_name.eq_ignore_ascii_case("hello_contract.wasm") + }) + .count(); + assert_eq!(ret, 1); + let wasm_file_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("hello_contract.wasm"); + let _ = std::fs::remove_file(wasm_file_path); +} + +#[test] +fn build_inside_contract_dir_with_docker() { + let source_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("contracts") + .join("hello_contract") + .to_path_buf(); + Command::new("pchain_compile") + .args(&[ + "build", + "--source", + source_path.to_str().unwrap(), + "--log-level", + "Debug", + ]) + .current_dir(source_path.to_str().unwrap()) + .assert() + .success(); + let ret = fs::read_dir(&source_path) + .unwrap() + .filter(|element| { + let entry = element.as_ref().unwrap(); + let file_type = entry.file_type().unwrap(); + let file_name = entry.file_name(); + file_type.is_file() && file_name.eq_ignore_ascii_case("hello_contract.wasm") + }) + .count(); + assert_eq!(ret, 1); + let wasm_file_path = source_path.join("hello_contract.wasm").to_path_buf(); + let _ = std::fs::remove_file(wasm_file_path); +}