diff --git a/.gitignore b/.gitignore index da5babf0..57f7a156 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,8 @@ pkg *.state **/.idea -*.nix -flake.lock +**/concordium-out + +**/*.nix +**/flake.lock +**/result diff --git a/cargo-concordium/CHANGELOG.md b/cargo-concordium/CHANGELOG.md index fc956f66..27a6e138 100644 --- a/cargo-concordium/CHANGELOG.md +++ b/cargo-concordium/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- Support specifying a profile using the `--profile` flag. +- Building/testing a single package in a workspace is now possible by calling + `cargo concordium` from the smart contract directory in the workspace. + ## 4.2.0 - Unit tests now run in parallel and the results are printed sequentially. Pass diff --git a/cargo-concordium/Cargo.lock b/cargo-concordium/Cargo.lock index a8fa12eb..193cea46 100644 --- a/cargo-concordium/Cargo.lock +++ b/cargo-concordium/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -37,7 +37,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -115,7 +115,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.5", "num-traits 0.2.19", "zeroize", ] @@ -132,7 +132,7 @@ dependencies = [ "ark-std", "derivative", "digest", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits 0.2.19", "paste", @@ -460,7 +460,7 @@ dependencies = [ "semver", "serde 1.0.218", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -512,6 +512,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "2.34.0" @@ -580,7 +596,7 @@ dependencies = [ "rust_decimal", "serde 1.0.218", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -611,7 +627,7 @@ dependencies = [ "sha2", "sha3", "slab", - "thiserror", + "thiserror 1.0.69", "tinyvec", ] @@ -628,7 +644,7 @@ dependencies = [ [[package]] name = "concordium_base" -version = "7.0.0" +version = "9.0.0" dependencies = [ "anyhow", "ark-bls12-381", @@ -639,6 +655,8 @@ dependencies = [ "bs58", "byteorder", "chrono", + "ciborium-io", + "ciborium-ll", "concordium-contracts-common", "concordium_base_derive", "curve25519-dalek", @@ -646,8 +664,9 @@ dependencies = [ "ed25519-dalek", "either", "ff", + "generic-array", "hex", - "itertools", + "itertools 0.14.0", "leb128", "libc", "nom 7.1.3", @@ -663,14 +682,17 @@ dependencies = [ "sha2", "sha3", "subtle", - "thiserror", + "thiserror 2.0.17", "zeroize", ] [[package]] name = "concordium_base_derive" -version = "1.0.0" +version = "1.1.0-alpha.3" dependencies = [ + "convert_case 0.8.0", + "darling", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", "syn 2.0.99", @@ -704,6 +726,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -763,6 +794,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -863,9 +900,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -873,9 +910,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", @@ -887,9 +924,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -933,7 +970,7 @@ version = "0.99.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", @@ -1051,7 +1088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1262,6 +1299,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy 0.8.27", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -1643,6 +1691,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2012,7 +2069,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -2185,7 +2242,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2320,9 +2377,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.36.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" dependencies = [ "arrayvec 0.7.6", "borsh", @@ -2359,7 +2416,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2372,7 +2429,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2822,7 +2879,7 @@ dependencies = [ "getrandom 0.3.1", "once_cell", "rustix 1.0.0", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2849,7 +2906,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -2863,6 +2929,17 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "time" version = "0.3.39" @@ -3221,7 +3298,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror", + "thiserror 1.0.69", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -3300,7 +3377,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3580,7 +3657,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive 0.8.27", ] [[package]] @@ -3594,6 +3680,17 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/cargo-concordium/Cargo.toml b/cargo-concordium/Cargo.toml index 7df9d37d..989634a0 100644 --- a/cargo-concordium/Cargo.toml +++ b/cargo-concordium/Cargo.toml @@ -48,5 +48,5 @@ path = "../concordium-base/smart-contracts/wasm-chain-integration/" features = ["display-state"] [dependencies.concordium_base] -version = "7.0" +version = "9.0" path = "../concordium-base/rust-src/concordium_base" diff --git a/cargo-concordium/src/build.rs b/cargo-concordium/src/build.rs index febbe602..5a999f62 100644 --- a/cargo-concordium/src/build.rs +++ b/cargo-concordium/src/build.rs @@ -1,8 +1,8 @@ -use crate::BuildOptions; +use crate::{get_packages, BuildOptions}; use ansi_term::{Color, Style}; use anyhow::Context; use base64::{engine::general_purpose, Engine as _}; -use cargo_metadata::{Metadata, MetadataCommand}; +use cargo_metadata::{camino::Utf8Path, Metadata, MetadataCommand, Package}; use concordium_base::{ contracts_common::{ self, @@ -55,18 +55,12 @@ fn to_snake_case(string: &str) -> String { /// Get the crate's metadata either by looking for the `Cargo.toml` file at the /// `--manifest-path` or at the ancestors of the current directory. -/// -/// If successful, the return value is a pair of metadata and all of the -/// `cargo_args` except the `--manifest-path` and the path to the manifest file. -/// This last part is used for reproducible builds. There we want to keep the -/// remaining `cargo` arguments, but the manifest path does not make sense since -/// the project is built from a specific location inside the container. -fn get_crate_metadata( - cargo_args: &[String], -) -> anyhow::Result<(Metadata, impl Iterator)> { - let pred = |val: &&String| !val.starts_with("--manifest-path"); - let mut args = cargo_args.iter().skip_while(pred); +pub fn get_crate_metadata(cargo_args: &[String]) -> anyhow::Result { let mut cmd = MetadataCommand::new(); + let mut args = cargo_args + .iter() + .skip_while(|val| !val.starts_with("--manifest-path")); + match args.next() { Some(p) if *p == "--manifest-path" => { // If a `--manifest-path` is provided, look for the `Cargo.toml` file there. @@ -90,9 +84,7 @@ fn get_crate_metadata( }; let metadata = cmd.exec().context("Could not access cargo metadata.")?; - - let init_args = cargo_args.iter().take_while(pred); - Ok((metadata, init_args.chain(args))) + Ok(metadata) } #[derive(Debug, Clone, Copy)] @@ -151,15 +143,16 @@ pub struct TarArchiveData { /// All those paths are expected to be relative to the same root as the /// `package_root_path`. fn create_archive( - package_root_path: &Path, - package_version_string: &str, + package: &Package, + workspace_root: PathBuf, omit_files: &[&Path], ) -> anyhow::Result { - let in_package_root_dir = std::path::Path::new(package_version_string); + let package_version_string = format!("{}-{}", package.name, package.version); + let in_package_root_dir = std::path::Path::new(&package_version_string); let mut tar = tar::Builder::new(Vec::new()); tar.mode(tar::HeaderMode::Deterministic); // Ignore files that are ignored by Git. - let files = ignore::WalkBuilder::new(package_root_path) + let files = ignore::WalkBuilder::new(&workspace_root) .git_global(true) .git_ignore(true) .parents(true) @@ -171,12 +164,12 @@ fn create_archive( for file in files { let file = file?; let file_path = file.path(); - if file_path == package_root_path || omit_files.iter().any(|f| file_path.starts_with(f)) { + if file_path == workspace_root || omit_files.iter().any(|f| file_path.starts_with(f)) { // We don't want to add the root path since we are adding all // relative paths under it. continue; } - let relative_path = file.path().strip_prefix(package_root_path)?; + let relative_path = file.path().strip_prefix(&workspace_root)?; if relative_path == std::path::Path::new("Cargo.lock") { lock_file_found = true; } @@ -185,7 +178,7 @@ fn create_archive( // directory. This then matches the behaviour of cargo package. tar.append_path_with_name(file.path(), in_package_root_dir.join(relative_path))?; } - let cargo_lock_exists = package_root_path.join("Cargo.lock").is_file(); + let cargo_lock_exists = workspace_root.join("Cargo.lock").is_file(); if !lock_file_found && cargo_lock_exists { anyhow::bail!( "Unable to proceed with a verifiable build. Cargo.lock seem to be included in \ @@ -264,66 +257,59 @@ struct ContainerBuildOutput { tar_archive: TarArchiveData, } -/// Package data for building inside a container. -struct PackageData<'a> { - /// The target directory of the package that is being - /// built. This is used to exclude it from bundling. - package_target_dir: &'a Path, - /// The root of the package to build. - package_root_path: &'a Path, - /// The package-name-version pair to mimics the behaviour or cargo package. - /// The paths in the tar archive are - package_version_string: &'a str, -} - /// Build the provided directory in the container. Return the built module and /// tar archive. The arguments are /// /// - `image`, the docker image that will be used to build. -/// - `package_target_dir`, -/// - `package_root_path`, +/// - `package`, the package to build. +/// - `metadata`, the cargo metadata object. /// - `extra_args`, the extra arguments to pass to the cargo build command. /// - `container_runtime`, the container runtime to use, e.g. `docker` or /// `podman` -/// - `out_path`, - the path to the out file for the wasm artifact. This should +/// - `out_path`, the path to the out file for the wasm artifact. This should /// be a fully expanded, canonical path. -/// - `tar_path`, - the path to the tar archive. This should be a fully +/// - `tar_path`, the path to the tar archive. This should be a fully /// expanded, canonical path. -fn build_in_container<'a>( +/// - `source_link`, the link to where the source code will be located +#[allow(clippy::too_many_arguments)] +fn build_in_container( image: String, - PackageData { - package_target_dir, - package_root_path, - package_version_string, - }: PackageData, - extra_args: impl Iterator, + package: &Package, + metadata: &Metadata, + extra_args: &[String], container_runtime: &str, out_path: &Path, tar_path: &Path, source_link: Option, ) -> anyhow::Result { + // NOTE: Also works without workspaces. + let workspace_root = metadata.workspace_root.canonicalize()?; + let mut target_dir = metadata.target_directory.as_std_path().to_path_buf(); + + if target_dir + .try_exists() + .context("Unable to check if target directory exists.")? + { + target_dir = target_dir.canonicalize()?; + }; + let toolchain_file = workspace_root.join("rust-toolchain.toml"); let tar_archive = create_archive( - package_root_path, - package_version_string, - &[out_path, tar_path, package_target_dir], + package, + workspace_root, + &[out_path, tar_path, &target_dir, &toolchain_file], )?; let archive_hash = sha2::Sha256::digest(&tar_archive.tar_archive); - let build_command = [ - "cargo", - "--locked", - "build", - "--target", - "wasm32-unknown-unknown", - "--release", - "--target-dir", - "/b/t", - ] - .into_iter() - .map(String::from) - .chain(extra_args.cloned()) - .collect::>(); + let build_command = CargoBuildParameters { + target_dir: Path::new("/b/t"), + profile: "release", + locked: true, + features: &[], + package: Some(&package.name), + extra_args, + } + .get_cargo_cmd_as_strings()?; // If both the potential output files exist check if there is no point // rebuilding. @@ -390,21 +376,31 @@ fn build_in_container<'a>( /// /// Note that even if a verifiable build is requested the schemas are built on /// the host machine. -#[allow(clippy::too_many_arguments)] pub(crate) fn build_contract( - version: WasmVersion, - build_schema: SchemaBuildOptions, - enable_debug: bool, - image: Option, - source_link: Option, - container_runtime: String, - out: Option, - skip_wasm_opt: bool, + options: BuildOptions, cargo_args: &[String], + package: &Package, + metadata: &Metadata, ) -> anyhow::Result { // Check that the wasm target is installed check_wasm_target()?; + let build_schema = options.schema_build_options(); + let image = options.image; + let container_runtime = options.container_runtime; + let out = options.out; + let version = options.version; + let skip_wasm_opt = options.skip_wasm_opt; + let source_link = options.source_link; + let enable_debug = options.allow_debug; + let profile = options.profile; + let target_dir = metadata.target_directory.join("concordium"); + let args_without_manifest: Vec = cargo_args + .iter() + .take_while(|val| !val.starts_with("--manifest-path")) + .cloned() + .collect(); + // Check immediately if reproducible build is requested that we can execute the // container runtime. if let Some(image) = &image { @@ -417,23 +413,14 @@ pub(crate) fn build_contract( } } - let (metadata, args_without_manifest) = get_crate_metadata(cargo_args)?; - - let package = metadata - .root_package() - .context("Unable to determine package.")?; - - let package_root = package + let package_root_path = package .manifest_path .parent() - .context("Unable to get package root path.")?; + .context("Unable to get package root path.")? + .canonicalize()?; let wasm_file_name = format!("{}.wasm", to_snake_case(package.name.as_str())); - let package_root_path = package_root.canonicalize()?; - - let package_version_string = format!("{}-{}", package.name, package.version); - // Make sure up-front before building anything that the output path points to a // sensible location let mut out_filename = match out { @@ -475,6 +462,8 @@ pub(crate) fn build_contract( let schema = build_contract_schema( cargo_args, skip_wasm_opt, + package, + &target_dir, utils::generate_contract_schema_v0, ) .context("Could not build module schema.")?; @@ -497,6 +486,8 @@ pub(crate) fn build_contract( let schema = build_contract_schema( cargo_args, skip_wasm_opt, + package, + &target_dir, utils::generate_contract_schema_v3, ) .context("Could not build module schema.")?; @@ -521,35 +512,21 @@ pub(crate) fn build_contract( .context("Unable to get working directory. Does it exist?")? .canonicalize()?; out_filename = cwd.join(out_filename); + // The archive will be named after the module output path, by appending `.tar` // to it. - let tar_filename: PathBuf = { - // Rust 1.70 has as_mut_os_string, but to support older versions we don't use it - // here for now, and instead convert from and to OsString to append an - // extension. - let mut tar_filename = out_filename.clone().into_os_string(); - tar_filename.push(".tar"); - tar_filename.into() - }; - let mut package_target_dir = metadata.target_directory.as_std_path().to_path_buf(); - if package_target_dir - .try_exists() - .context("Unable to check if target directory exists.")? - { - package_target_dir = package_target_dir.canonicalize()?; - }; + let mut tar_filename = out_filename.to_owned(); + tar_filename.as_mut_os_string().push(".tar"); + let ContainerBuildOutput { output_wasm, build_info, tar_archive, } = build_in_container( image, - PackageData { - package_target_dir: package_target_dir.as_path(), - package_root_path: package_root_path.as_path(), - package_version_string: &package_version_string, - }, - args_without_manifest, + package, + metadata, + &args_without_manifest, &container_runtime, &out_filename, &tar_filename, @@ -568,19 +545,16 @@ pub(crate) fn build_contract( .join("wasm32-unknown-unknown/release") .join(wasm_file_name); - let mut cmd = Command::new("cargo"); - cmd.arg("build") - .args(["--target", "wasm32-unknown-unknown"]) - .args(["--release"]) - .arg("--target-dir") - .arg(target_dir) - .args(cargo_args); - - let result = cmd - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output() - .context("Could not use cargo build.")?; + let result = CargoBuildParameters { + target_dir: &target_dir, + profile: &profile, + locked: false, + features: &[], + package: Some(&package.name), + extra_args: &[], + } + .run_cargo_cmd() + .context("Could not use cargo build.")?; if !result.status.success() { anyhow::bail!("Compilation failed.") @@ -676,7 +650,7 @@ pub(crate) fn build_contract( total_module_len, schema: return_schema, stored_build_info: stored_build_info.map(|(bi, a)| (bi, a.archived_files)), - metadata, + metadata: metadata.clone(), out_filename, }) } @@ -779,16 +753,10 @@ fn find_closest<'a>( pub fn build_contract_schema( cargo_args: &[String], skip_wasm_opt: bool, + package: &Package, + target_dir: &Utf8Path, generate_schema: impl FnOnce(&[u8]) -> ExecResult, ) -> anyhow::Result { - let (metadata, _) = get_crate_metadata(cargo_args)?; - - let target_dir = format!("{}/concordium", metadata.target_directory); - - let package = metadata - .root_package() - .context("Unable to determine package.")?; - let result = Command::new("cargo") .arg("build") .args(["--target", "wasm32-unknown-unknown"]) @@ -805,11 +773,10 @@ pub fn build_contract_schema( anyhow::bail!("Compilation failed."); } - let filename = format!( - "{}/wasm32-unknown-unknown/release/{}.wasm", - target_dir, - to_snake_case(package.name.as_str()) - ); + let filename = target_dir + .join("wasm32-unknown-unknown") + .join("release") + .join(format!("{}.wasm", to_snake_case(package.name.as_str()))); if !skip_wasm_opt { wasm_opt::OptimizationOptions::new_opt_level_0() @@ -1177,12 +1144,28 @@ pub(crate) fn build_and_run_integration_tests( build_options: BuildOptions, test_targets: Vec, ) -> anyhow::Result<()> { - let cargo_args = build_options.cargo_args.clone(); let allow_debug = build_options.allow_debug; // Build the module in the same way as `cargo concordium build`, except that // schema information shouldn't be printed. - let build_info = crate::handle_build(build_options, false)?; + let cargo_args = if build_options.allow_debug { + // prepend the features at the beginning of the build_options + // since the user might have added some build_options after `--`. + let mut args = Vec::with_capacity(build_options.cargo_args.len() + 1); + args.push("--features=concordium-std/debug".into()); + args.extend(build_options.cargo_args.clone()); + args + } else { + build_options.cargo_args.clone() + }; + crate::handle_build(build_options.clone(), false)?; + let metadata = get_crate_metadata(&cargo_args)?; + let package = metadata + .root_package() + .context("Unable to determine root package")?; + let build_info = build_contract(build_options.clone(), &cargo_args, package, &metadata)?; + + let cargo_args = build_options.cargo_args.clone(); let mut cargo_test_args = vec!["test"]; @@ -1365,35 +1348,32 @@ pub fn build_and_run_wasm_test( // Check that the wasm target is installed check_wasm_target()?; - let (metadata, _) = get_crate_metadata(extra_args)?; - - let target_dir = format!("{}/concordium", metadata.target_directory); - - let package = metadata - .root_package() - .context("Unable to determine package.")?; - - let cargo_args = [ - "build", - "--release", - "--target", - "wasm32-unknown-unknown", - "--features", - if enable_debug { - "concordium-std/wasm-test,concordium-std/debug" + let metadata = get_crate_metadata(extra_args)?; + let pkgs = get_packages(&metadata)?; + let target_dir = metadata + .target_directory + .into_std_path_buf() + .join("concordium"); + + let cargo_cmd = CargoBuildParameters { + target_dir: &target_dir, + profile: "release", + locked: false, + package: None, + features: if enable_debug { + &["concordium-std/wasm-test", "concordium-std/debug"] } else { - "concordium-std/wasm-test" + &["concordium-std/wasm-test"] }, - "--target-dir", - target_dir.as_str(), - ]; + extra_args, + }; // Output what we are doing so that it is easier to debug if the user // has their own features or options. eprint!( "{} cargo {}", Color::Green.bold().paint("Running"), - cargo_args.join(" ") + cargo_cmd.get_cargo_cmd_as_strings()?.join(" ") ); if extra_args.is_empty() { // This branch is just to avoid the extra trailing space in the case when @@ -1402,12 +1382,8 @@ pub fn build_and_run_wasm_test( } else { eprintln!(" {}", extra_args.join(" ")); } - let result = Command::new("cargo") - .args(cargo_args) - .args(extra_args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output() + let result = cargo_cmd + .run_cargo_cmd() .context("Failed building contract tests.")?; // Make sure that compilation succeeded before proceeding. anyhow::ensure!( @@ -1415,21 +1391,6 @@ pub fn build_and_run_wasm_test( Color::Red.bold().paint("Could not build contract tests.") ); - // If we compiled successfully the artifact is in the place listed below. - // So we load it, and try to run it.s - let filename = format!( - "{}/wasm32-unknown-unknown/release/{}.wasm", - target_dir, - to_snake_case(package.name.as_str()) - ); - if !skip_wasm_opt { - wasm_opt::OptimizationOptions::new_opt_level_0() - .run(&filename, &filename) - .context("Failed running wasm_opt")?; - } - - let wasm = std::fs::read(filename).context("Failed reading contract test output artifact.")?; - eprintln!("\n{}", Color::Green.bold().paint("Running unit tests ...")); let seed_u64 = match seed { @@ -1442,18 +1403,43 @@ pub fn build_and_run_wasm_test( } }; - let artifact = concordium_wasm::utils::instantiate::( - ValidationConfig::V1, - &NoDuplicateImport, - &wasm, - )? - .artifact; - let artifact_keys: Vec<_> = artifact.export.keys().collect(); + let mut artifacts = Vec::new(); + for pkg in pkgs { + // If we compiled successfully the artifact is in the place listed below. + // So we load it, and try to run it. + let file_path = target_dir + .join("wasm32-unknown-unknown") + .join("release") + .join(format!("{}.wasm", to_snake_case(pkg.name.as_str()))); + if !skip_wasm_opt { + wasm_opt::OptimizationOptions::new_opt_level_0() + .run(&file_path, &file_path) + .context("Failed running wasm_opt")?; + } + + let wasm = + std::fs::read(file_path).context("Failed reading contract test output artifact.")?; + + let artifact = concordium_wasm::utils::instantiate::( + ValidationConfig::V1, + &NoDuplicateImport, + &wasm, + )? + .artifact; + + artifacts.push(artifact); + } - let num_failed = artifact_keys + let num_failed = artifacts .into_par_iter() - .filter_map(|name| get_test_result(name, seed_u64, &artifact, enable_debug)) - .count(); + .map(|artifact| { + let artifact_keys: Vec<_> = artifact.export.keys().collect(); + artifact_keys + .into_par_iter() + .filter_map(|name| get_test_result(name, seed_u64, &artifact, enable_debug)) + .count() + }) + .sum::(); if num_failed == 0 { eprintln!("Unit test result: {}", Color::Green.bold().paint("ok")); @@ -1464,6 +1450,10 @@ pub fn build_and_run_wasm_test( } } +// NOTE: This seems to not work if there's a rust-toolchain file, reproducible +// builds still fail without proper error messages if a toolchain file is +// provided without the wasm target. +// /// Checks if the `wasm32-unknown-unknown` target is installed, and returns an /// error if not. fn check_wasm_target() -> anyhow::Result<()> { @@ -1494,3 +1484,62 @@ fn check_wasm_target() -> anyhow::Result<()> { ); Ok(()) } + +/// The parameters to pass to CargoBuildParameters +struct CargoBuildParameters<'a> { + target_dir: &'a Path, + profile: &'a str, + locked: bool, + features: &'a [&'a str], + package: Option<&'a str>, + extra_args: &'a [String], +} + +impl CargoBuildParameters<'_> { + /// Get the cargo arguments as a list of strings, i.e. `Vec!["cargo", "build", ...]`. + fn get_cargo_cmd_as_strings(&self) -> anyhow::Result> { + let mut args = vec![ + "cargo", + "build", + "--target", + "wasm32-unknown-unknown", + "--profile", + self.profile, + "--target-dir", + self.target_dir + .to_str() + .context("target_dir is not valid UTF-8")?, + ]; + if let Some(pkg) = self.package { + args.push("--package"); + args.push(pkg); + } + if self.locked { + args.push("--locked"); + } + args.extend(self.extra_args.iter().map(|x| x.as_str())); + + let mut args: Vec<_> = args.into_iter().map(|x| x.to_string()).collect(); + if !self.features.is_empty() { + args.push("--features".to_string()); + args.push(self.features.join(",")); + } + + Ok(args) + } + + /// Run the `cargo build` command with the specified parameters. + fn run_cargo_cmd(&self) -> anyhow::Result { + let mut args = self.get_cargo_cmd_as_strings()?; + let executable = args.remove(0); // "cargo" + let mut cmd = Command::new(&executable); + cmd.args(&args); + + let output = cmd + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output()?; + + Ok(output) + } +} diff --git a/cargo-concordium/src/context.rs b/cargo-concordium/src/context.rs index ec7714a8..44f8b431 100644 --- a/cargo-concordium/src/context.rs +++ b/cargo-concordium/src/context.rs @@ -195,7 +195,7 @@ impl v0::HasReceiveContext for ReceiveContextV1Opt { } impl v1::HasReceiveContext for ReceiveContextV1Opt { - fn entrypoint(&self) -> ExecResult { + fn entrypoint(&self) -> ExecResult> { let ep = unwrap_ctx_field(self.entrypoint.as_ref(), "entrypoint")?; Ok(ep.as_entrypoint_name()) } diff --git a/cargo-concordium/src/main.rs b/cargo-concordium/src/main.rs index b59520f5..950b2df8 100644 --- a/cargo-concordium/src/main.rs +++ b/cargo-concordium/src/main.rs @@ -4,6 +4,7 @@ use crate::{ }; use ansi_term::Color; use anyhow::{bail, ensure, Context}; +use cargo_metadata::{Metadata, Package}; use clap::AppSettings; use concordium_base::{ contracts_common::{ @@ -28,6 +29,7 @@ use concordium_wasm::{ use ptree::{print_tree_with, PrintConfig, TreeBuilder}; use sha2::Digest; use std::{ + fmt::Debug, fs::{self, File}, io::Read, path::{Path, PathBuf}, @@ -372,7 +374,7 @@ struct EditOptions { // The issue is known (https://github.com/TeXitoi/structopt/issues/391) but won't // be fixed in `structopt` as it is in maintenance mode and is now integrated // in `clap` v3+. Once we migrate to `clap` v3+, this can become a doc comment. -#[derive(Debug, StructOpt)] +#[derive(Clone, Debug, StructOpt)] struct BuildOptions { #[structopt( name = "schema-embed", @@ -424,6 +426,13 @@ struct BuildOptions { (expected input: `./my/path/base64_schema.b64` or `-`)." )] schema_base64_out: Option, + #[structopt( + name = "profile", + long = "profile", + help = "The build profile to use for the smart contract.", + default_value = "release" + )] + profile: String, #[structopt( name = "out", long = "out", @@ -1006,39 +1015,16 @@ fn handle_print_build_info(source: PathBuf) -> anyhow::Result<()> { Ok(()) } -/// Build the smart contract module using the provided options. -/// -/// This method is used by both the build and test command. -/// When building, i.e. when running `cargo concordium build`, the schema -/// information is outputted, but that is not the case when testing. -/// This behaviour is configurable via the parameter `print_schema_info`. -fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result { +/// Handles the build of a single package. +fn handle_package( + build_info: &BuildInfo, + options: &BuildOptions, + print_extra_info: bool, +) -> anyhow::Result<()> { let success_style = ansi_term::Color::Green.bold(); let bold_style = ansi_term::Style::new().bold(); - let build_schema = options.schema_build_options(); let is_verifiable_build = options.image.is_some(); - let cargo_args = if options.allow_debug { - // prepend the features at the beginning of the options - // since the user might have added some options after `--`. - let mut args = Vec::with_capacity(options.cargo_args.len() + 1); - args.push("--features=concordium-std/debug".into()); - args.extend(options.cargo_args); - args - } else { - options.cargo_args - }; - let build_info = build_contract( - options.version, - build_schema, - options.allow_debug, - options.image, - options.source_link, - options.container_runtime, - options.out, - options.skip_wasm_opt, - &cargo_args, - ) - .context("Could not build smart contract.")?; + if let Some(module_schema) = &build_info.schema { let module_schema_bytes = to_bytes(module_schema); if print_extra_info { @@ -1075,7 +1061,7 @@ fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result ); } - if let Some(schema_out) = options.schema_out { + if let Some(schema_out) = &options.schema_out { // A path and a filename need to be provided when using the `--schema-out` // flag. if schema_out.file_name().is_none() || schema_out.is_dir() { @@ -1091,11 +1077,11 @@ fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result } fs::write(schema_out, &module_schema_bytes).context("Could not write schema file.")?; } - if let Some(schema_json_out) = options.schema_json_out { - write_json_schema(&schema_json_out, module_schema) + if let Some(schema_json_out) = &options.schema_json_out { + write_json_schema(schema_json_out, module_schema) .context("Could not write JSON schema files.")?; } - if let Some(schema_template_out) = options.schema_template_out { + if let Some(schema_template_out) = &options.schema_template_out { if schema_template_out.as_path() == Path::new("-") { write_schema_template(None, module_schema) .context("Could not print the template of the schema.")?; @@ -1107,11 +1093,11 @@ fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result ); } - write_schema_template(Some(schema_template_out), module_schema) + write_schema_template(Some(schema_template_out.to_path_buf()), module_schema) .context("Could not write template schema files.")?; } } - if let Some(schema_base64_out) = options.schema_base64_out { + if let Some(schema_base64_out) = &options.schema_base64_out { if schema_base64_out.as_path() == Path::new("-") { write_schema_base64(None, module_schema) .context("Could not print base64 schema.")?; @@ -1123,7 +1109,7 @@ fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result ); } - write_schema_base64(Some(schema_base64_out), module_schema) + write_schema_base64(Some(schema_base64_out.to_path_buf()), module_schema) .context("Could not write base64 schema file.")?; } } @@ -1174,7 +1160,54 @@ fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result ) ) } - Ok(build_info) + + Ok(()) +} + +/// Gets the `root_package` from the workspace i.e. the package of the current dir. +/// +/// It returns a `Vec` to support getting all the packages in the +/// workspace if there is no `root_package`. +fn get_packages(metadata: &Metadata) -> anyhow::Result> { + let root_package = metadata.root_package(); + if let Some(package) = root_package { + Ok(vec![package.clone()]) + } else { + bail!("Error: No package found!"); + } +} + +/// Build the smart contract module using the provided options. +/// If it's in a workspace context it will build the smart contract in the +// current directory, if one exists otherwise it will build all smart contracts +// in the workspace. +/// +/// This method is used by both the build and test command. +/// When building, i.e. when running `cargo concordium build`, the schema +/// information is outputted, but that is not the case when testing. +/// This behaviour is configurable via the parameter `print_schema_info`. +fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result<()> { + let cargo_args = if options.allow_debug { + // prepend the features at the beginning of the options + // since the user might have added some options after `--`. + let mut args = Vec::with_capacity(options.cargo_args.len() + 1); + args.push("--features=concordium-std/debug".into()); + args.extend(options.cargo_args.clone()); + args + } else { + options.cargo_args.clone() + }; + + let metadata = get_crate_metadata(&cargo_args)?; + let pkgs = get_packages(&metadata)?; + + for package in pkgs { + let build_info = build_contract(options.clone(), &cargo_args, &package, &metadata) + .context("Could not build smart contract.")?; + handle_package(&build_info, &options, print_extra_info)?; + } + + Ok(()) } /// Loads the contract state from file and displays it as a tree by printing to diff --git a/concordium-base b/concordium-base index 7f7fa47a..286105a2 160000 --- a/concordium-base +++ b/concordium-base @@ -1 +1 @@ -Subproject commit 7f7fa47abc74775fbaa60afa35ed5d08fabbbbca +Subproject commit 286105a2713d6a5b27d1b2189b7d66bbd8065679 diff --git a/reproducible/example-contract/Cargo.lock b/reproducible/example-contract/Cargo.lock index 7029f963..86e83df4 100644 --- a/reproducible/example-contract/Cargo.lock +++ b/reproducible/example-contract/Cargo.lock @@ -52,13 +52,6 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -[[package]] -name = "piggy-bank-part1" -version = "0.1.1" -dependencies = [ - "concordium-std", -] - [[package]] name = "proc-macro2" version = "1.0.95" @@ -88,6 +81,13 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "test-contract" +version = "0.1.0" +dependencies = [ + "concordium-std", +] + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/reproducible/example-contract/Cargo.toml b/reproducible/example-contract/Cargo.toml index 96e82266..fbd9cf3a 100644 --- a/reproducible/example-contract/Cargo.toml +++ b/reproducible/example-contract/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +resolver = "3" + [package] name = "test-contract" version = "0.1.0"