Skip to content

Commit

Permalink
Merge pull request #8 from parallelchain-io/feature/locked
Browse files Browse the repository at this point in the history
Feature/locked
  • Loading branch information
cy6581 authored Aug 31, 2023
2 parents 8ed4bdf + 7e47451 commit f54b4df
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 60 deletions.
26 changes: 21 additions & 5 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! in a docker environment.

use clap::Parser;
use pchain_compile::{config::Config, DockerConfig, DockerOption};
use pchain_compile::{config::Config, DockerConfig, DockerOption, BuildOptions};
use std::path::{Path, PathBuf};

#[derive(Debug, Parser)]
Expand All @@ -21,7 +21,7 @@ use std::path::{Path, PathBuf};
long_about = None
)]
enum PchainCompile {
/// Build the source code. Please make sure:
/// Build the source code. By default, it uses docker for building the contract. Please make sure:
/// 1. Docker is installed and its execution permission under current user is granted.
/// 2. Internet is reachable. (for pulling the docker image from docker hub)
#[clap(arg_required_else_help = true, display_order = 1, verbatim_doc_comment)]
Expand All @@ -35,16 +35,26 @@ enum PchainCompile {
#[clap(long = "destination", display_order = 2, verbatim_doc_comment)]
destination_path: Option<PathBuf>,

/// Build with the version-locked dependencies. If the source code directory contains the file "Cargo.lock",
/// 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
/// process happens in your local changing environment.**
///
/// To install target "wasm32-unknown-unkown", run the following command:
/// To install target "wasm32-unknown-unknown", run the following command:
///
/// $ rustup target add wasm32-unknown-unknown
#[clap(
long = "dockerless",
display_order = 3,
display_order = 4,
verbatim_doc_comment,
group = "docker-option"
)]
Expand All @@ -58,7 +68,7 @@ enum PchainCompile {
/// - 0.4.2
#[clap(
long = "use-docker-tag",
display_order = 4,
display_order = 5,
verbatim_doc_comment,
group = "docker-option"
)]
Expand All @@ -73,6 +83,7 @@ async fn main() {
PchainCompile::Build {
source_path,
destination_path,
locked,
dockerless,
docker_image_tag,
} => {
Expand All @@ -82,6 +93,10 @@ async fn main() {
}
println!("Build process started. This could take several minutes for large contracts.");

let build_options = BuildOptions {
locked
};

let docker_option = if dockerless {
DockerOption::Dockerless
} else {
Expand All @@ -96,6 +111,7 @@ async fn main() {
let config = Config {
source_path,
destination_path: destination_path.clone(),
build_options: build_options.clone(),
docker_option: docker_option.clone(),
};

Expand Down
27 changes: 20 additions & 7 deletions src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,26 @@ use std::{collections::HashSet, path::PathBuf};
use std::fs;

use crate::error::Error;
use crate::DockerConfig;
use crate::{DockerConfig, BuildOptions};

/// `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<PathBuf>,
) -> Result<String, Error> {
build_target_with_docker(source_path, destination_path, 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.
pub(crate) async fn build_target_with_docker(
source_path: PathBuf,
destination_path: Option<PathBuf>,
options: BuildOptions,
docker_config: DockerConfig,
) -> Result<String, Error> {
// create destination directory if it does not exist.
Expand All @@ -69,13 +74,14 @@ pub(crate) async fn build_target_with_docker(
return Err(Error::UnkownDockerImageTag(docker_image_tag));
}

build_target_in_docker(source_path, destination_path, 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.
pub(crate) async fn build_target_without_docker(
source_path: PathBuf,
destination_path: Option<PathBuf>,
options: BuildOptions,
) -> Result<String, Error> {
// create destination directory if it does not exist.
if let Some(dst_path) = &destination_path {
Expand All @@ -90,7 +96,7 @@ pub(crate) async fn build_target_without_docker(
crate::manifests::package_name(&source_path).map_err(|_| Error::InvalidSourcePath)?;
let wasm_file = format!("{package_name}.wasm").replace('-', "_");

build_target_by_cargo(source_path, destination_path, wasm_file).await
build_target_by_cargo(source_path, destination_path, options, wasm_file).await
}

fn validated_source_path(source_path: PathBuf) -> Result<PathBuf, Error> {
Expand All @@ -104,6 +110,7 @@ fn validated_source_path(source_path: PathBuf) -> Result<PathBuf, Error> {
async fn build_target_in_docker(
source_path: PathBuf,
destination_path: Option<PathBuf>,
options: BuildOptions,
docker_image_tag: String,
wasm_file: String,
) -> Result<String, Error> {
Expand All @@ -124,6 +131,7 @@ async fn build_target_in_docker(
dependencies,
source_path,
destination_path,
options,
&wasm_file,
)
.await;
Expand All @@ -141,6 +149,7 @@ async fn compile_contract_in_docker_container(
dependencies: impl IntoIterator<Item = String>,
source_path: PathBuf,
destination_path: Option<PathBuf>,
options: BuildOptions,
wasm_file: &str,
) -> Result<(), Error> {
// Step 1. create dependency directory and copy source to docker
Expand All @@ -152,10 +161,11 @@ async fn compile_contract_in_docker_container(
crate::docker::copy_files(docker, container_name, source_path.to_str().unwrap()).await?;

// Step 3: build the source code inside docker
let result_in_docker = crate::docker::build_contracts(
let (result_in_docker, build_log) = crate::docker::build_contracts(
docker,
container_name,
source_path.to_str().unwrap(),
source_path,
options.locked,
wasm_file,
)
.await?;
Expand All @@ -166,6 +176,7 @@ async fn compile_contract_in_docker_container(
container_name,
&result_in_docker,
destination_path.clone(),
build_log
)
.await?;

Expand All @@ -177,6 +188,7 @@ async fn compile_contract_in_docker_container(
async fn build_target_by_cargo(
source_path: PathBuf,
destination_path: Option<PathBuf>,
options: BuildOptions,
wasm_file: String,
) -> Result<String, Error> {
// 1. Create temporary folder as a working directory for cargo build
Expand All @@ -188,11 +200,12 @@ async fn build_target_by_cargo(
&temp_dir,
source_path.as_path(),
destination_path,
options.locked,
&wasm_file,
);

// 3. Remove temporary files after building
let _ = std::fs::remove_dir_all(temp_dir);

result.map(|_| wasm_file)
}
}
86 changes: 80 additions & 6 deletions src/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

//! Implements the compilation process of smart contract by utilizing crates `cargo`, `wasm-opt` and `wasm-snip`.

use std::path::{Path, PathBuf};
use std::{path::{Path, PathBuf}, io::Write, sync::{Arc, Mutex}};

use cargo::{
core::compiler::{CompileKind, CompileTarget},
Expand All @@ -31,7 +31,7 @@ pub(crate) fn random_temp_dir_name() -> PathBuf {
.to_path_buf()
}

/// Equivalent to running following commands:
/// Equivalent to run following commands:
/// 1. cargo build --target wasm32-unknown-unknown --release --quiet
/// 2. wasm-opt -Oz <wasm_file> --output temp.wasm
/// 3. wasm-snip temp.wasm --output temp2.wasm --snip-rust-fmt-code --snip-rust-panicking-code
Expand All @@ -40,14 +40,17 @@ pub(crate) fn build_contract(
working_folder: &Path,
source_path: &Path,
destination_path: Option<PathBuf>,
locked: bool,
wasm_file: &str,
) -> Result<(), Error> {
let output_path = destination_path.unwrap_or(Path::new(".").to_path_buf());

// 1. cargo build --target wasm32-unknown-unknown --release --quiet
let mut config = Config::default().unwrap();
// Does not set "--locked" if the Cargo.lock file does not exist.
let use_cargo_lock = locked && source_path.join("Cargo.lock").exists();
let mut config = CargoConfig::new();
config
.configure(0, true, None, false, false, false, &None, &[], &[])
.configure(0, false, None, false, use_cargo_lock, false, &None, &[], &[])
.unwrap();
let mut compile_configs =
CompileOptions::new(&config, cargo::util::command_prelude::CompileMode::Build).unwrap();
Expand All @@ -61,8 +64,16 @@ pub(crate) fn build_contract(
format!("Error in preparing workspace according to the manifest file in source path:\n\n{:?}\n", e),
)
})?;
cargo::ops::compile(&ws, &compile_configs)
.map_err(|e| Error::BuildFailure(format!("Error in cargo build:\n\n{:?}\n", e)))?;
if let Err(_) = cargo::ops::compile(&ws, &compile_configs) {
return Err(Error::BuildFailureWithLogs(config.logs()))
}

// Save Cargo.lock to output folder: If option '--locked' is enabled, the Cargo.lock file
// is the file provided by user, otherwise, the Cargo.lock file is the one generated during
// "cargo build".
if locked {
let _ = std::fs::copy(source_path.join("Cargo.lock"), output_path.join("Cargo.lock"));
}

// 2. wasm-opt -Oz wasm_file --output temp.wasm
let temp_wasm = working_folder.join("temp.wasm");
Expand Down Expand Up @@ -102,3 +113,66 @@ pub(crate) fn build_contract(

Ok(())
}

/// Captures the [cargo::util::Config] with custom instantiation.
pub struct CargoConfig {
/// The logs from the shell which is used by the cargo
logs: Arc<Mutex<Vec<String>>>,
/// Cargo configuration
config: Config,
}

impl CargoConfig {
pub fn new() -> Self {
// Setup a shell that stores logs in memory.
let logs = Arc::new(Mutex::new(Vec::<String>::new()));
let log_writer = BuildLogWriter { buffer: logs.clone() };
let shell = cargo::core::Shell::from_write(Box::new(log_writer));

// Setup Cargo configuration with the custom shell.
let current_dir = std::env::current_dir().unwrap();
let home_dir = cargo::util::homedir(&current_dir).unwrap();
let config = Config::new(shell, current_dir, home_dir);
Self {
logs,
config
}
}

/// Return logs as a String
pub fn logs(&self) -> String{
self.logs.lock().unwrap().join("")
}
}

impl std::ops::Deref for CargoConfig {
type Target = Config;
fn deref(&self) -> &Self::Target {
&self.config
}
}

impl std::ops::DerefMut for CargoConfig {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.config
}
}

/// Implements [std::io::Write] and be used by Cargo. It stores the
/// output logs during cargo building process.
#[derive(Default)]
pub struct BuildLogWriter {
pub buffer: Arc<Mutex<Vec<String>>>
}

impl Write for BuildLogWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if let Ok(ref mut mutex) = self.buffer.try_lock() {
mutex.push(String::from_utf8_lossy(buf).to_string());
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
13 changes: 12 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@ pub struct Config {
pub source_path: PathBuf,
/// Path to destination folder. None if current folder should be used.
pub destination_path: Option<PathBuf>,
/// Options for building rust code.
pub build_options: BuildOptions,
/// Compilation option regards to use of docker.
pub docker_option: DockerOption,
}

/// Options for building rust code.
#[derive(Clone, Default)]
pub struct BuildOptions {
/// Use of the Cargo.lock. It is equivalent to run Cargo build with
/// flag "--locked".
pub locked: bool
}

/// Compilation option regards to docker.
#[derive(Clone)]
pub enum DockerOption {
Expand Down Expand Up @@ -49,12 +59,13 @@ impl Config {
crate::build::build_target_with_docker(
self.source_path,
self.destination_path,
self.build_options,
docker_config,
)
.await
}
DockerOption::Dockerless => {
crate::build::build_target_without_docker(self.source_path, self.destination_path)
crate::build::build_target_without_docker(self.source_path, self.destination_path, self.build_options)
.await
}
}
Expand Down
Loading

0 comments on commit f54b4df

Please sign in to comment.