From a91d1d74859879bacdc392ae65bf183d9a972d3e Mon Sep 17 00:00:00 2001 From: rasmus-kirk Date: Sun, 23 Nov 2025 09:00:33 +0100 Subject: [PATCH 1/5] Implemented workspace support, `cargo test` will not work yet --- cargo-concordium/Cargo.lock | 157 +++++-- cargo-concordium/Cargo.toml | 2 +- cargo-concordium/src/build.rs | 386 ++++++++++-------- cargo-concordium/src/main.rs | 107 +++-- concordium-base | 2 +- reproducible/.gitignore | 1 + reproducible/example-contract/Cargo.lock | 14 +- reproducible/example-contract/Cargo.toml | 3 + .../concordium-out/module.wasm.v1 | Bin 0 -> 20220 bytes .../concordium-out/module.wasm.v1.tar | Bin 0 -> 8192 bytes reproducible/run-copy.sh | 1 + 11 files changed, 425 insertions(+), 248 deletions(-) create mode 100644 reproducible/.gitignore create mode 100644 reproducible/example-contract/concordium-out/module.wasm.v1 create mode 100644 reproducible/example-contract/concordium-out/module.wasm.v1.tar 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..f42e2ae2 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, @@ -61,12 +61,12 @@ fn to_snake_case(string: &str) -> String { /// 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 +90,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 +149,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 +170,13 @@ 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)) { + println!("{file_path:?}"); + 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 +185,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,18 +264,6 @@ 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 /// @@ -291,39 +279,37 @@ struct PackageData<'a> { /// expanded, canonical path. fn build_in_container<'a>( 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 { - let tar_archive = create_archive( - package_root_path, - package_version_string, - &[out_path, tar_path, package_target_dir], - )?; + // 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 tar_archive = create_archive(package, workspace_root, &[out_path, tar_path, &target_dir])?; 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: &extra_args, + } + .get_cargo_cmd_as_strings()?; // If both the potential output files exist check if there is no point // rebuilding. @@ -392,19 +378,30 @@ fn build_in_container<'a>( /// 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 +414,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 +463,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 +487,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 +513,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 +546,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 +651,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 +754,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"]) @@ -1177,12 +1146,26 @@ 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().unwrap(); + 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; - let num_failed = artifact_keys + artifacts.push(artifact); + } + + 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,7 @@ pub fn build_and_run_wasm_test( } } +// NOTE: This seems to not work if there's a rust-toolchain file /// Checks if the `wasm32-unknown-unknown` target is installed, and returns an /// error if not. fn check_wasm_target() -> anyhow::Result<()> { @@ -1494,3 +1481,60 @@ 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<'a> CargoBuildParameters<'a> { + 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) + } + + 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/main.rs b/cargo-concordium/src/main.rs index b59520f5..e3003082 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,15 @@ 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 { +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 +1060,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 +1076,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 { + 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 +1092,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 +1108,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 +1159,53 @@ fn handle_build(options: BuildOptions, print_extra_info: bool) -> anyhow::Result ) ) } - Ok(build_info) + + Ok(()) +} + +fn get_packages(metadata: &Metadata) -> anyhow::Result> { + let root_package = metadata.root_package(); + if let Some(package) = root_package { + Ok(vec![package.clone()]) + } else { + let smart_contract_pkgs: Vec = + metadata.workspace_packages().into_iter().cloned().collect(); + if smart_contract_pkgs.len() != 0 { + Ok(smart_contract_pkgs) + } else { + bail!("Error: No package found!"); + } + } +} + +/// 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<()> { + 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/.gitignore b/reproducible/.gitignore new file mode 100644 index 00000000..41fbeb02 --- /dev/null +++ b/reproducible/.gitignore @@ -0,0 +1 @@ +**/result 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" diff --git a/reproducible/example-contract/concordium-out/module.wasm.v1 b/reproducible/example-contract/concordium-out/module.wasm.v1 new file mode 100644 index 0000000000000000000000000000000000000000..8beec2ed9458d9525b61ffc6ed5762f134caee0b GIT binary patch literal 20220 zcmdU%eQaIleczvR&V3{ArFG=ciDD|%xi?-bHKzD}NjhoFqs?+v#fg(OZMJriDUy=- zBGP;jEw6zoCr;)$R^dNVzyVyq1B}He;?)?k#tYJp1GLT<+(iRC%*FhNy9SsGWScj* zsJnXbKHuMS&b^1Ulxzz&U=l9RbDo#q^LzjOo|k)_bAfZ8e%sA%UZmiHhd=N@xU=K* zC)jx)+ObDKg^KQh5`TFFkI-0FS#^)_;9Wx=@jZBR^ir*(E_|c|jF%k^uGnBzdoaOH z_)o&#-nq5axwVb?#Y-0to?qBHv#~I@u(-W&X6>QXg$)-Knr|&OZ!WCP?`uD_w|(W@ znX|Jiv#WCpuCD+k-rSnqS~#j6 z9xRT2Xk&3}VNY`ge2#HB2CkgK}6mOF5uTeW#{cJqRZd(WJipWT{;#`!3ypE+}3VfI0!wYd<5 zJs%2O8q^Q|&557=`D1^r_0bb|&!j*1$xr;k(cbSr8GQcHAc%It8U0U!qhYC4(xcWY zFSja5l<+w<(2DNqbG($7RClT`pt^LrFHAxz>FsDqqV&q6yDt8jgw}Rgt0#K{$*De0RM8ItFMMI=8=!OG+u6nchJ8X37u#L_(2k;HGnUrwIo`O zdy~*`yayag)V89udS4&#qO^7|IJzV}7S$%B+6mVzBdZfZvn))?lMzj6ymW`F54dn9 zabS%su3Wi%Qp4gVpQK*D6a+gUk{BY!6F2A_J;K!8<&GE#!|BLU8pMZ$O`N)W_3XcA zqv@EEK7sEF-a^0IwU%{lsH-|A;FGxezY2m-)9$+ER&a;Yyh8DQ=9R*LWIIgKCsk;| z5?^YEcT!B2;s%q(1C+tAYE}HX8~joie^}xgvBEed^L;h$R}EVRXCER>Ufs z|7KV*^d>lYgg%r6UPL^C^u1ucg#hmBgC}4^ml!1h-zZLa$xuZO>5;AaUzLNhaJ?Sv z;rj{qxjtuzUw6X%2Z3ki?PBH^a%PHIamGyg6}d^ZWS^>fFEj6-1P}4i2FHebtq+7# zzzw<{;eK);g_{^%8lJ?)f+V`j4bgviDHf~VaZ7O^CVEB=y34K7Eujl45PKq-46h+& zy8WeFYKOb%Kr_OSRI?js)@S*PH6`AfAgI(T-_uvYI?bAnVAc&+7w1mI`+;S{4OC2z zqv5kbAO7N4^nB11ZUX+kR^L7uJpdM{+ZSXbQJUP_7t#06jKePlOUIm>bb|3GR`X(^ z=8vuB`ECsm-Lt`@`z$ylrMukyIWxp@K)|Vw$YX`LiMp3pQ=Xe9OmirNAL(=RG4mGf@YgAVTHMgE>wye4@`ic zxQ$Q2cahJQW?1F@F%U%(}6cm zg9CL_Qt-h^r$IB4M~_Qsa4%j27u;_RnztSH*FAMn{cKQ@Rc6tEQC`ylo`mTEY*Cum z-zvrBR#^-a+U2B#zV|0(U~8H|cc2--965hv&TKLx0k9bGIUFwdlyb!pJXt_eJ|%EA zl>%N|kxan0Zj81V$TVPD>IS%^M1ZS58-!sejFmcGTpX;nDkpY63*b^(QlNwtM2{m$ zi9p)~bBqwP$JQw|jOSm3#}+vIg?Jlh3+ine31tLV9u*w7aLS^XRVo5txh12~BCY_Y z?p+;J^efKmHTlMX=riVE7^(U@oy=Jq&rKugkj(; zil8YLu7`cNZhQn`>)(ae zM{GixmY56VqUH4$lHi3$T4mvYvXsBoDtYHc+vz?*EKzOxAgUs$CbcRxLM&&{J^I9q zkFB8;4Xr!a45c6{_Jb9}@rN3ZB1wa=xst#zo;P$@l}gzl*Sw_%ueVIsUHvemhmcM7 z)Cvq5x(nq&d~3aq5P{%MeDAM*>%0H`+RHOa9NdY2`P?7;_W$_Nx1auRl1{y)4*%(U zFZ|vgef8Bp|5K{e<()5o^Y?%2KmXys_@_JS@)go%Z%CwJ<&Gf*NoX0$XONg{Rv=9e zklD+^w3gzo6h&Lr49_&!YSmB(KBosra;o;M!s;He8ZyMEo^Q2kMRo&S5;iK!$g;H3 z4iDpJpdDWICy^vw7BdgwbS3{q1dLQdmKfMyj z`GxAy$g5(iLbw3HlC@I2d!4Qucew+ywe;5Q^ew%~aRq5*yVHD&RW!O4eQ61wEc++` z>GXQzhCuq#$o%-8F@5<|yEX;`4y4}FO8u#|qU9t&PZD^_R8r47tid!{48hZfX#<E^?M$aJS@ro&_tKZv|LV8oIFmXi8kFrutsn)_7s0U=qUP~P(AP&<(PC&3fU>08J*>x8t z3H(GPvTcpmaSnTzZk=lR3>k~5kwRcfjjR72rd*7=ULjH z644ZN!+|C9C4GVH&0LXa193$=6vc~$iTaoLL#h=HG)ob#kiZ$VLRE|ZwgPJfwDBtF zK@J$FRQ5_Q5P-5GfqH1Ytv7J@6q;-V4^%u6@~sDukH)=dK_43R`If<4V4TtXObea+zHYCzx8I~P!oqD_1e`LB{f!NCS|rz znqFRKL3XFG0HIb6RupJvWTCN!Sr-}4q;x+nok3!KN8_;S;|w6XCXPSn8)S%20bP6{t6x1DU~KYa(oCMJhDa~i?=xKVv_qBXl<{j1 zIZ{M;s}ZcuBz&g#MGr-c_fS+SK*`yI$X}MV$y~Zbr$-1ftbX5nuS%vsslunWeZCbg zZ=(vxAlO}Sz3E}Jt5i07JM=ykwmiilryto)OY2%pwFs83h22S6 z0y-q2tXevnK_jc@)tt09FkvITQ7rCA37f*|6tO)4SfN^Z%L(us01Uj0+<1J+d{HP3 zMQ30Kq=2heXm3gK7Nig~I~rjVI|m(og-LrYiMb@k$k%)A)kNNsBEw9Yikt2ll1(c> zcsDbqNE2knWaZ45V2n^@tRR3BM(rUp2i{U-caf7Zd3=~rL-(W886>wZ=Z!`sEKMTk;$oALl8|F>PAZOS9Qd z$yyMb-b+iT`Uq*1uzlMD5#na1o_w=N!%?LUjw*R>!&p~vI;KEE0ndUgH9$_sd8dQG zGN%*yeM3ZE(>eoWOr7;;031Em1Ikh7a7F;daV4grF)|&+vJLZAka0W|$Mq-UxK_wo zgu8U7Eq>MO|K_7%PiMgzOhE|XErd`5gnX)BbJ+P(HtR_94S5~e-19E0mKi$;7lGM8H$8flJ zKn(-TO&-71tR)rPk?DjNfFxQ`4g(|7ccOI_v`>zJTj4f#7!h~joN;4k(_HWp>x*cM zJ{FQ%BI`vI()%14OI1k&ix%&oSQIEBOwc=(hq~J0u|+X}mO`qap`i zv8>CO>U`=|wVci}1*#!$A7vV{#2=D3H_)s_GfA!4lT=||p6Y>af2&Fx>{U)z%>iR2 zJ#q!g?a({8%nXH`4s^jt>?~VOr&Sp5NZ#XOZy+Rtr>~(<#O6Fa`>2 z@pwu&VZP`M1vrVOYRlA%o5-1x^kC;HpqDq+FSL3j_b`(?K)P}Vc&$|vqBX4o8=zF% zBln(;+@&vG;FQoMX~1%;nr9iTj%^+3^%KdrP+5uPawi88iG&Ux*z_VT?nybXF~3yt zehG8vBtu$dTd39AmHFhCH~|u(L=gYT4tq>1p&0a;g97A$g?S6@St-4RvvI2`w5%C{ zN*;$YTkJOU=M9;jk>Ja)nK|PCC8>Pa38WCOY+Xc&^qP|1T9@={PP&<7k#tNFmt4z9 zuVtiHo3cHreXWVVGdyd^sB4?bAu>^c7`##R_3}X@c=3wk4bsdQwt3EgDM&)K@vKon zam0`f^HEilr`%DVq(mx0FxzLX3KLXw7HK1M9o6ZaxxQ1avQ|R8p2B$Wg$eIHnnn`9 z!>Z6Pp;lR?)CTa7A>Am*UBYPvkwgONsuNl#AxQ6|<uD9trIGquS;36- ztx*4>m0z;@YlS{9S$Ws$Un%t2werhW|7xMn%T~T_^{*HDT(|OTR{z67pVzGXhSmS5 z(B};+ziIVv7y7(esDI1KZ&{yr3*~pL{EpQ>_M~X{sEfv>VH`1^O}|4u=*br`n+M~H?98dLZ3IS{Fc?f zTj=wamEW=Y$IKRdpLeYO3f2YBt={*!V&%uJ{u@^IeIB>+Q&#^>q0duRe%k7vEA)BV z%FkN;w+nrqRT;O<8I+cYS~lX#F+jAz_kP~@Anuc(d;ufYDPW6Gs67Fahb&-Wp&UeL ztDTkBl`ACrWaJ8!xL@rTYKPi8>4qXa(MdzMlQMfKvd!FfF?B%6C1A7^dzPBj%nx`6 z5t++(WD9t}pz8%fo$vzXec*8dPov{aJX(a4Q6G5{Lr2LE?Ri{7%=8?wS!^?_3;u3b}fZgxOPnbr`b!4=d7JK7DSVw zlBnKE?MwU^m&K48+pG+rbeK$9EJ~3Ovv4}cWDo~)$gfWJk&Pua<$r#yY)1&%I`gPo zIsl^>==VBtv+;4iKI(2gRV4{Y%&!>&dKXrKkl#r;>=;7fQQ;k)E8Bu{rCG4vKhXG^ z@_X~tVvGcW+_f{8DW+If?W9UIudSx509Bf@+*InsKtyDlaw?OWoU}|@8!fDD*bcOf z8#GeNUWxBSmJz!m(oyuDUOp*gb&~B6Mj2m-d7Os8(QpPdvTb1Y3XhZOP|;-#!Fn%i z$nVI;BA<;hT_+K>^C<24Xa^rQi#4`=(triVAtn`TY+Xs2LqyXwZ!8dsV4)o@QWkT& zxaL{4&&F!#vQLu@JHT6(eE@qDtj>0T-`N3`Y&a#;RB1~g>0?52Jv7oXMB*lVhLO1= z&?rmW)$SodpU1?cO!g+(7RboF53BE-9H0Y#S$NB~$Q?p+JIto{DiZVxORpQa9eEUS z)C@KUqWszqhXF-`x*V`nTTG=RG>TK5c`^k+jXf=zqY3-`b_}%M$99Y{<^NbvazOdQ z0;3_V^$QEAX00cJSXj`|Uufvh8>D#hDavTOw&KmBpc~n;!usZV9HPg}9TxOB+ONl< z>9IENAd=NVrpJ-r1htJGK$rq+9S5$`WB8bT1UZ<=@35BZmMwfNR0tK}F5NPBi!7OBbBNbP4uz=I$ z#I|EMY*cZbgRn!~^PQw8?1}H`L`^)Htxlm=U_3N+#r4#gRWq?W>!*e?wn|aac**#n zZMMz~LIXtxd0BhD%4A+lN$cM{Sg{!w52ANcYUw~da$Meo1@XA6|Kq^leB(@&-89Ya z`y_BQ;{Fjd69$0&Yg>JM)wG=JTW6fV(-}^PWt>;cbOG6JrPWD z>I@u`h&Db78k&< z5p9;n!}SL^zRL8?Z@=3C5f^1rJUp^%MOYk^I%ic3pZr z{SJ@#)1)HT{QKM4`+uke6`Mh`07TpTI-79p3M;VLOl8ef&1SL*nl<{`mIS+lnk^e1 zdJ<5Sc%)^gf!fPP1Echn_kQCvQjIBk1LJvWpc&Y-Rncw1P`77;w6x*x(FV)uqgQII zz7pM$wVaYY7V}(}d02GTyC+ z(9MhV8h@vIvwWbxqy5U8bmk;}n@w_$;YNyY7=nu+5f3psFfIZ6eb5*l*g`iC2;z~C z7huLERq{vAl@2ty26lMY*#|MtAf&2$!N^wl=vtQ%q3+$z83lQ2##m$`3&geY)Qq4*}vQo5D{rl^b(4 zLWgug)|2lUNM7L?sBFsBXPim1Xx?5-Sh&BNVb^FBKL#BEO`GG`fCKYiza5WNxBGaA z*5^6jc!DhryInyEVne$Hkp^lXbZ=apvAD`*H;R75>lEbo^$c}#8`SR%`RYV=tK9tY zu~23jWIi+rCy74T;Zi*2>Mw=)9rRu9U~>tbS=oqj{vM6?w0OglgUVsct;onsw@%18 zb5T|YsYlh~&psnc%xCw}RL>r_B2QkGL{>WL!Cm{xuKexMrTG5adHUPK=Am-^Jr*8r zafLm*I&8J*D=Qud6LaGb5C&5r4{} z2RG@H$1dm^FEH;S@nxoY>`kj-JKRbmp0@7Jm?B!pTfcb_h(qU^fWr%s%o z>wyPEYFu&aNUfGjO|rp#cwxP@M_Ba_NPzq58q}Hg=+2#PmWu%Dm@EF(`VRtsL9BZP zFAd_m?0S7AWo1lSi@Ve|yWM?bIGe!-9qEv~;^P=_CEfXy&wtG_sRDD_p7i-eS+kR! zGe!~QF82{0T+!7PJ2DUM)Fow8M802PgP2Hb{iESiPt5pn*-YfAtVD4pWd6(|hhUx| zztI%Le}f&0UL!X?dAqCMj*LaF#y@7Rh7d7e$VU(`t!B({dj@W+m$Dxo)M*nJL*6;b zy(O~+Il(mEVmN%Sw>!!cAVi#%=)i9(kqXjRzUC9D;6zFHFU-6wMf$_QUwF=y&IExa zaPOFDnIh2bT?{MYEV8A$62v-qmXt~Cl_&ABlKXUVK#E_`Aj{^Y82Bg?uP!hL`d$of zKp2MDT&lc)n^Q~iX~vL!+WkdnMyd4&e1(L)iz{~LnFnAs&h9MU<`}Be!Z7|tx(aVR z%Kvwl*?K^nz;qi&Af_{r^99tpBijhsMv|=(J!FeOBvEZh@OGGo$(<8f9GlFMsG&^w z=RE{}PCK$GQs5O(|||fPs9Bi!@r>Ve)5|1hZy6 zYI>>V^3m{403Hmv0YU(h*NE&gr5|uG;6Rnpc1-P~kR1`TYW510-DC^WvQE~63hrweokZ3p3|hum|;r?aA0JTENNL`%n~Z2J( zRS3a+Nsv4b^S4f`tW4;;qND2f-eXtshi;W^++ZvHw_hWJ7;^68PQm06_QBYZ{UJSl zzkNMQz(hYem-^@bZs;_sTW&H;v>K1g>7UwN9%{8b@R$I!#TPELD(Xz_ZaRer1dUpv z?g8NTp<9?idnC}B4s!!q+Ks^Q`#C4l-lVh~e-2EQ{oMge>Q7dJs$cMub!p9EW=KF| z=YG&mozssIXRgs#dso{1*R@t%LM$uJ8MUklY;VbS2&@M9LR-ty&zl@EVD=OwO^W*HVkS9AUy@L+2E7}4)&PJvM%-nnxC-+KP)&bZ$*;SYOo*_dA|ZseX|HG zwf4jg<%#mEhY(0_wtbRHc7k#v00;>H>`mskV`DrHLX;7cbR9w#*flj=_2$l?uxNNJ zICAh<*zLH=vZx5c8IdPSa8Dv_IO)ap;t0VN!xC~y!_cK(=;k!A`)D)hXq7}~KUK}_ zYe$vk+?k|s0|cRujV-?85Zn@lPWwp}EspGK5z5TA;V=)v`nGoIp*OpdXN1B|STC}g zz58(Qm6O|xyRo7d{Bz1(f?9W3@0H2CG@AojD{(4yN~HK%_klD<`406uok0UWlPi3- z8*l%XtN&RgsAYD79QelKm$ZGy;>>O#;MSH&ee5djxjj;IueZ#U2#j6D!*ufT^b$A{ zcibHpc1+3f1e`g6B=?py47a9DfozQ2n!X7p7E%|O^;xA)xm6ivmH7BH+{Q7$bdF`9 zMyUWdUo_utV1GC<)7C53J4o8Mj7cGLvwo1X(1y!f)%B%j5A5K}XU#w?u~Z@D&RTUF z##gpC(nr7iSdHCcWe(&EH)<%|tU;@x_m2pd_@@O8P7zJ+@!kSTmLUhM;1H~I?bS3tIx6{FQt|i7^2D&T^Dq zf7~yKq7&@JD1EgpWn18sY*U$Bm&qc(zpQ-P?=MFrJU*rVU!3T%zyH%t#7ZC#W8z

PY*kTFH4Wv2mmY4)5a+WEr3A! zvW=>SVwt3ZREBAFUEf~cLx7RU_ksaqFiQV7NzAfsS3k|!k4#xufsjR20yW=)NCG|J z1qvkoi*)%BBg4MtU^ggG^fCwYq^|XUSk6P=|DN2FK&J+|?Af!By&H4!mLMU~;08Hc z$TguRHu=C*(1g6N)Wrs5V=-L4hki9BbwUZM{OPo#9;Bw-cNC|Zmb>q$RFySCr$5S8 z<(DNoxUpmuZUybhHHYZUSq<82_CV@~E}$G}1InUhPPG}B7U7!A%V=j8CKvR{uRpE3 z_+VFZ53Lh3tAee7t`v$?A*Gh21wat<|BDX|19c_#&W;_)IC@Z=$t={&pBAOG7%HciG@R!6-y@+GV%<1jF*X+b&z* zCm8+YM+Y|qL&~*BFxcl4y@4Za?7I4u-l0pI8$&B=bF(W$bF&-g*M>G0&M$8AlR!h8 z8*@X8{I<~L<8vGQOwZ==#kIka>GA2gb5nEevlC}0CMM4fb$_#HkmAdlqKxdqU93S3s0^B?fBxz&GeXncNpWPV|MVSH+2dTM@lbYX04d}M6l-2B}9^xXXD z+}!Zoc)L9{btA}I8;h&wMQJ05-D%)nh0X!d^;5&%n0@HX_QD*%F97>fz(lj1wm&02 z4L+sp!<4SV$J0N*@Zjdq{K`e6e{dL~Od1#RDge%I>i3Bj*H)8rvx_SW^LHf=>Ia{a zPyRw;&yOV+7gx`$Z7*!_`$Y5Zb>=??{O6glhv&_Pf1*2l_HuW4XZ$~6d>`ZePbejZ z;H;ssb}mWIK8(_32s!sy7`xl(`%Ru2lS_4TYu>1_!I1J2?T2We3p{Q`-c+gAzZxI$ zN>BaE-$hF=pE@7$Q}e`p)b|!X2eNnZd~Wu^*}287hZAw-+{)TR)J;XP$>Z4Y@bJj+ z=rkCr777 zr^klJM#e_R(7CaRvG&;H*wonc`0)71_~`i9`1tt5czb+ud}@4pVt8U?Vsv6`Vtis^ zqCGJ=F*PyW9&V4cN84lV@%BW!-JWbuwWlYCCr2hnC&wnoCnqM`larHElhaefQzKKO zQ)5%(Qxj9|smZCSsp)A@oMw6~96-}_nx0P9E^Q^qVqiMk; zSqr~jm6)qY+})+vc=i#VEqLMw_(pydY-4LPncYIT0A`fExCiL_LHcg<6wl|@9(?%B zxsA1pXKeIc$>!Rnjk$$v@@(d#$CCLq6l8U6E4etkHFqJIU)bDQT=fd%r_H+p^Bcha zS)SkH`7xfqjI2-*4F6!khi%SXShzULrjuIS9|yM;+Rk2DT$vwSTs^nuZoBRDAAI*e z{{DBL`R0-Fcdvb7sB-<^|K5v}kAD5p!SK1i_~KuzxsNj1oa%)&JkZ652T_v6bBnWQ zR~8242<}>uN8OUb$ym+vN(%=E8MVAHuS%QGSHd@b(oHOSh~KIk8|}Eh%moV0;NZr> h$^yR}Ce0lj^g{;c7dNE6LuZF?j|KpHzrv zp~cUBD>9lqV^pM!CN$w;oC%)b{G;stnML}x(+we?nf3YM6}CHYpMT^3V#agwZdSC! z7bOsEb>G!<-4#}s<&yk5g$f$ofg@%~R&EhJC$G9NwW_hHI;b2D`??`>UG=&o;EJxO zrg3xwO@cZ;Y0-Ag z@=vp_TeRnAXVapa^(MCU{H#IaO-fx&&uoygjf;9xpgbQ(k)46PZu@!ICwEDnIWR0w zq*1v~thJ20EalQ`nla&7VpXP{1!gnJWomeCb#5}2DVd@;2gS#zv328$etsK3Ek4cX zb+v;jkCT`l8Im&Edah}za>uo0Qcxv9`dxwcK6 z4669Oue+6U+U1w2qwU;|zpx<-9~rw+SD>e3OAq{6L zqq#^kMIB3x^^oDuLF^BB$^DKHd}iCyXy>CxJoh<(MTn03M9?&MU`?()lTujiGOHAY z#OXw6>~#u_t}%g2&a_d9W%oq(o2yFuRx_<8Hck|xCgqO~n|Ufzonwf3%*rX+RAJyB$iv%4x;LMO?6M6H&zP7| z^xUH!oytODGH-Li499R`TxqH^8=Orl<1GS0C(QB;`_a(SW+}y^mVy)9YX1fJ$mlL* zexY&ksJ|nvJ6c$$&I3x_%2grZ-mBML5wTUszjOZo{zKhdx5&-mGHRf$OJwbVGb2Mp9=GA_x0HRyT@FPaULa<5Gcd~0 z#VtW5zc{#ovR>eo;McoI3?%;L=rS6v48u)fgf~FUVP#>VY6t%R&)5H$4E)(aYnx)x zt*#WKH-Kr<^|mm0b1{8Ux|Rg#NITa}M{twjN*jlRuq@1FG+YrT-Et9@eLu2piQS`9 zGHOB_SnGDy!1=e*PcUHwoiIE-P$x|tZcA|kCEx|-GO8DN2?_ARoc3D-@{jcO18eUX zbRGX&$m_m4@X{l@Li}e^?%(@2-^1{J{7X*7Gx^AUzH0h+9{-2z-!}HERC(V^UtxdF zu>SL8|NAt0y#9Oae}*|RZrbDQ6YT%M`JZqp_TwMxfBfnEKaTnzT;=TS2lBRiwekxqAXVj zHPMwL$TJGRBzoOdd(wqM{@B)JRr}IaU=1xemxf`Zh>v zLq$7O>DC3iIO>sI+UEvlj$qd)^`2}I)d{SmI{E7T*dcQs7AvfamX^5ZLf5i&rr8`e;$NDeT+{Yi5*?Iogs9z zz&`Y0XuLAt9%iz}8|-!n^Ml|@=m=I{2kMTi$HVjZWQ8FNwxQM=h3ZtiLJv3{fPd7`P{5jb0!9@5p_@sd+4LoV!JJZ1b02%+KqyPW_ literal 0 HcmV?d00001 diff --git a/reproducible/run-copy.sh b/reproducible/run-copy.sh index d0686083..0305efa9 100755 --- a/reproducible/run-copy.sh +++ b/reproducible/run-copy.sh @@ -22,5 +22,6 @@ shift 2 # execute the supplied command which consists of everything apart from the first # 2 arguments to the `run-copy` script. "$@" +ls "$BUILD_DIR" mv "$BUILD_DIR"/*.wasm "$TMP"/out.wasm wasm-opt -O0 -o /artifacts/out.wasm "$TMP"/out.wasm From b1c3af247c8a6856fd68b28de7ab07d2ce4b6105 Mon Sep 17 00:00:00 2001 From: rasmus-kirk Date: Sun, 23 Nov 2025 10:09:38 +0100 Subject: [PATCH 2/5] Self review and changelog --- .gitignore | 5 +++-- cargo-concordium/CHANGELOG.md | 5 +++++ cargo-concordium/src/build.rs | 35 ++++++++++++++------------------- cargo-concordium/src/context.rs | 2 +- cargo-concordium/src/main.rs | 6 ++++-- reproducible/.gitignore | 1 - reproducible/run-copy.sh | 1 - 7 files changed, 28 insertions(+), 27 deletions(-) delete mode 100644 reproducible/.gitignore diff --git a/.gitignore b/.gitignore index da5babf0..f91f51bd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,6 @@ pkg *.state **/.idea -*.nix -flake.lock +**/*.nix +**/flake.lock +**/result diff --git a/cargo-concordium/CHANGELOG.md b/cargo-concordium/CHANGELOG.md index fc956f66..d2d4f18e 100644 --- a/cargo-concordium/CHANGELOG.md +++ b/cargo-concordium/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- Support specifying a profile using the `--profile` flag. +- Support for workspaces. + ## 4.2.0 - Unit tests now run in parallel and the results are printed sequentially. Pass diff --git a/cargo-concordium/src/build.rs b/cargo-concordium/src/build.rs index f42e2ae2..8295a99a 100644 --- a/cargo-concordium/src/build.rs +++ b/cargo-concordium/src/build.rs @@ -55,12 +55,6 @@ 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. pub fn get_crate_metadata(cargo_args: &[String]) -> anyhow::Result { let mut cmd = MetadataCommand::new(); let mut args = cargo_args @@ -170,7 +164,6 @@ fn create_archive( for file in files { let file = file?; let file_path = file.path(); - println!("{file_path:?}"); 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. @@ -268,16 +261,18 @@ struct ContainerBuildOutput { /// 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. +/// - `package_root_path`, 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, package: &Package, metadata: &Metadata, @@ -307,7 +302,7 @@ fn build_in_container<'a>( locked: true, features: &[], package: Some(&package.name), - extra_args: &extra_args, + extra_args, } .get_cargo_cmd_as_strings()?; @@ -376,7 +371,6 @@ 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( options: BuildOptions, cargo_args: &[String], @@ -526,7 +520,7 @@ pub(crate) fn build_contract( } = build_in_container( image, package, - &metadata, + metadata, &args_without_manifest, &container_runtime, &out_filename, @@ -774,11 +768,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() @@ -1493,6 +1486,7 @@ struct CargoBuildParameters<'a> { } impl<'a> CargoBuildParameters<'a> { + /// 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", @@ -1524,6 +1518,7 @@ impl<'a> CargoBuildParameters<'a> { 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" 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 e3003082..64ac3fff 100644 --- a/cargo-concordium/src/main.rs +++ b/cargo-concordium/src/main.rs @@ -1077,7 +1077,7 @@ fn handle_package( 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) + 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 { @@ -1163,6 +1163,8 @@ fn handle_package( Ok(()) } +/// Get a list of packages in the workspace from the metadata. +/// If it's a non-workspace package, get the single package. fn get_packages(metadata: &Metadata) -> anyhow::Result> { let root_package = metadata.root_package(); if let Some(package) = root_package { @@ -1170,7 +1172,7 @@ fn get_packages(metadata: &Metadata) -> anyhow::Result> { } else { let smart_contract_pkgs: Vec = metadata.workspace_packages().into_iter().cloned().collect(); - if smart_contract_pkgs.len() != 0 { + if smart_contract_pkgs.is_empty() { Ok(smart_contract_pkgs) } else { bail!("Error: No package found!"); diff --git a/reproducible/.gitignore b/reproducible/.gitignore deleted file mode 100644 index 41fbeb02..00000000 --- a/reproducible/.gitignore +++ /dev/null @@ -1 +0,0 @@ -**/result diff --git a/reproducible/run-copy.sh b/reproducible/run-copy.sh index 0305efa9..d0686083 100755 --- a/reproducible/run-copy.sh +++ b/reproducible/run-copy.sh @@ -22,6 +22,5 @@ shift 2 # execute the supplied command which consists of everything apart from the first # 2 arguments to the `run-copy` script. "$@" -ls "$BUILD_DIR" mv "$BUILD_DIR"/*.wasm "$TMP"/out.wasm wasm-opt -O0 -o /artifacts/out.wasm "$TMP"/out.wasm From 9c1264d44bf9655d186e80b9d93149868f0e6bcd Mon Sep 17 00:00:00 2001 From: rasmus-kirk Date: Sun, 23 Nov 2025 12:28:13 +0100 Subject: [PATCH 3/5] Appease Clippy --- cargo-concordium/src/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cargo-concordium/src/build.rs b/cargo-concordium/src/build.rs index 8295a99a..c1046a9c 100644 --- a/cargo-concordium/src/build.rs +++ b/cargo-concordium/src/build.rs @@ -1485,7 +1485,7 @@ struct CargoBuildParameters<'a> { extra_args: &'a [String], } -impl<'a> CargoBuildParameters<'a> { +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![ From a0eea0d04c82cc07325556a024c8303ffffa5216 Mon Sep 17 00:00:00 2001 From: Rasmus Kirk Date: Mon, 24 Nov 2025 10:23:02 +0000 Subject: [PATCH 4/5] Update cargo-concordium/src/build.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emil Holm Gjørup --- cargo-concordium/src/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cargo-concordium/src/build.rs b/cargo-concordium/src/build.rs index c1046a9c..f6282edd 100644 --- a/cargo-concordium/src/build.rs +++ b/cargo-concordium/src/build.rs @@ -262,7 +262,7 @@ struct ContainerBuildOutput { /// /// - `image`, the docker image that will be used to build. /// - `package`, the package to build. -/// - `package_root_path`, the cargo metadata object. +/// - `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` From 7504898c5d839d44b68b55a83174c816e91ad831 Mon Sep 17 00:00:00 2001 From: rasmus-kirk Date: Mon, 24 Nov 2025 14:16:13 +0100 Subject: [PATCH 5/5] Ignore rust-toolchain in workspace root --- .gitignore | 2 ++ cargo-concordium/CHANGELOG.md | 3 ++- cargo-concordium/src/build.rs | 16 +++++++++++++--- cargo-concordium/src/main.rs | 18 +++++++++--------- .../concordium-out/module.wasm.v1 | Bin 20220 -> 0 bytes .../concordium-out/module.wasm.v1.tar | Bin 8192 -> 0 bytes 6 files changed, 26 insertions(+), 13 deletions(-) delete mode 100644 reproducible/example-contract/concordium-out/module.wasm.v1 delete mode 100644 reproducible/example-contract/concordium-out/module.wasm.v1.tar diff --git a/.gitignore b/.gitignore index f91f51bd..57f7a156 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ pkg *.state **/.idea +**/concordium-out + **/*.nix **/flake.lock **/result diff --git a/cargo-concordium/CHANGELOG.md b/cargo-concordium/CHANGELOG.md index d2d4f18e..27a6e138 100644 --- a/cargo-concordium/CHANGELOG.md +++ b/cargo-concordium/CHANGELOG.md @@ -3,7 +3,8 @@ ## Unreleased - Support specifying a profile using the `--profile` flag. -- Support for workspaces. +- 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 diff --git a/cargo-concordium/src/build.rs b/cargo-concordium/src/build.rs index c1046a9c..8e8815dc 100644 --- a/cargo-concordium/src/build.rs +++ b/cargo-concordium/src/build.rs @@ -292,7 +292,12 @@ fn build_in_container( { target_dir = target_dir.canonicalize()?; }; - let tar_archive = create_archive(package, workspace_root, &[out_path, tar_path, &target_dir])?; + let toolchain_file = workspace_root.join("rust-toolchain.toml"); + let tar_archive = create_archive( + package, + workspace_root, + &[out_path, tar_path, &target_dir, &toolchain_file], + )?; let archive_hash = sha2::Sha256::digest(&tar_archive.tar_archive); @@ -1155,7 +1160,9 @@ pub(crate) fn build_and_run_integration_tests( }; crate::handle_build(build_options.clone(), false)?; let metadata = get_crate_metadata(&cargo_args)?; - let package = metadata.root_package().unwrap(); + 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(); @@ -1443,7 +1450,10 @@ pub fn build_and_run_wasm_test( } } -// NOTE: This seems to not work if there's a rust-toolchain file +// 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<()> { diff --git a/cargo-concordium/src/main.rs b/cargo-concordium/src/main.rs index 64ac3fff..950b2df8 100644 --- a/cargo-concordium/src/main.rs +++ b/cargo-concordium/src/main.rs @@ -1015,6 +1015,7 @@ fn handle_print_build_info(source: PathBuf) -> anyhow::Result<()> { Ok(()) } +/// Handles the build of a single package. fn handle_package( build_info: &BuildInfo, options: &BuildOptions, @@ -1163,24 +1164,23 @@ fn handle_package( Ok(()) } -/// Get a list of packages in the workspace from the metadata. -/// If it's a non-workspace package, get the single package. +/// 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 { - let smart_contract_pkgs: Vec = - metadata.workspace_packages().into_iter().cloned().collect(); - if smart_contract_pkgs.is_empty() { - Ok(smart_contract_pkgs) - } else { - bail!("Error: No package found!"); - } + 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 diff --git a/reproducible/example-contract/concordium-out/module.wasm.v1 b/reproducible/example-contract/concordium-out/module.wasm.v1 deleted file mode 100644 index 8beec2ed9458d9525b61ffc6ed5762f134caee0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20220 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

PY*kTFH4Wv2mmY4)5a+WEr3A! zvW=>SVwt3ZREBAFUEf~cLx7RU_ksaqFiQV7NzAfsS3k|!k4#xufsjR20yW=)NCG|J z1qvkoi*)%BBg4MtU^ggG^fCwYq^|XUSk6P=|DN2FK&J+|?Af!By&H4!mLMU~;08Hc z$TguRHu=C*(1g6N)Wrs5V=-L4hki9BbwUZM{OPo#9;Bw-cNC|Zmb>q$RFySCr$5S8 z<(DNoxUpmuZUybhHHYZUSq<82_CV@~E}$G}1InUhPPG}B7U7!A%V=j8CKvR{uRpE3 z_+VFZ53Lh3tAee7t`v$?A*Gh21wat<|BDX|19c_#&W;_)IC@Z=$t={&pBAOG7%HciG@R!6-y@+GV%<1jF*X+b&z* zCm8+YM+Y|qL&~*BFxcl4y@4Za?7I4u-l0pI8$&B=bF(W$bF&-g*M>G0&M$8AlR!h8 z8*@X8{I<~L<8vGQOwZ==#kIka>GA2gb5nEevlC}0CMM4fb$_#HkmAdlqKxdqU93S3s0^B?fBxz&GeXncNpWPV|MVSH+2dTM@lbYX04d}M6l-2B}9^xXXD z+}!Zoc)L9{btA}I8;h&wMQJ05-D%)nh0X!d^;5&%n0@HX_QD*%F97>fz(lj1wm&02 z4L+sp!<4SV$J0N*@Zjdq{K`e6e{dL~Od1#RDge%I>i3Bj*H)8rvx_SW^LHf=>Ia{a zPyRw;&yOV+7gx`$Z7*!_`$Y5Zb>=??{O6glhv&_Pf1*2l_HuW4XZ$~6d>`ZePbejZ z;H;ssb}mWIK8(_32s!sy7`xl(`%Ru2lS_4TYu>1_!I1J2?T2We3p{Q`-c+gAzZxI$ zN>BaE-$hF=pE@7$Q}e`p)b|!X2eNnZd~Wu^*}287hZAw-+{)TR)J;XP$>Z4Y@bJj+ z=rkCr777 zr^klJM#e_R(7CaRvG&;H*wonc`0)71_~`i9`1tt5czb+ud}@4pVt8U?Vsv6`Vtis^ zqCGJ=F*PyW9&V4cN84lV@%BW!-JWbuwWlYCCr2hnC&wnoCnqM`larHElhaefQzKKO zQ)5%(Qxj9|smZCSsp)A@oMw6~96-}_nx0P9E^Q^qVqiMk; zSqr~jm6)qY+})+vc=i#VEqLMw_(pydY-4LPncYIT0A`fExCiL_LHcg<6wl|@9(?%B zxsA1pXKeIc$>!Rnjk$$v@@(d#$CCLq6l8U6E4etkHFqJIU)bDQT=fd%r_H+p^Bcha zS)SkH`7xfqjI2-*4F6!khi%SXShzULrjuIS9|yM;+Rk2DT$vwSTs^nuZoBRDAAI*e z{{DBL`R0-Fcdvb7sB-<^|K5v}kAD5p!SK1i_~KuzxsNj1oa%)&JkZ652T_v6bBnWQ zR~8242<}>uN8OUb$ym+vN(%=E8MVAHuS%QGSHd@b(oHOSh~KIk8|}Eh%moV0;NZr> h$^yR}Ce0lj^g{;c7dNE6LuZF?j|KpHzrv zp~cUBD>9lqV^pM!CN$w;oC%)b{G;stnML}x(+we?nf3YM6}CHYpMT^3V#agwZdSC! z7bOsEb>G!<-4#}s<&yk5g$f$ofg@%~R&EhJC$G9NwW_hHI;b2D`??`>UG=&o;EJxO zrg3xwO@cZ;Y0-Ag z@=vp_TeRnAXVapa^(MCU{H#IaO-fx&&uoygjf;9xpgbQ(k)46PZu@!ICwEDnIWR0w zq*1v~thJ20EalQ`nla&7VpXP{1!gnJWomeCb#5}2DVd@;2gS#zv328$etsK3Ek4cX zb+v;jkCT`l8Im&Edah}za>uo0Qcxv9`dxwcK6 z4669Oue+6U+U1w2qwU;|zpx<-9~rw+SD>e3OAq{6L zqq#^kMIB3x^^oDuLF^BB$^DKHd}iCyXy>CxJoh<(MTn03M9?&MU`?()lTujiGOHAY z#OXw6>~#u_t}%g2&a_d9W%oq(o2yFuRx_<8Hck|xCgqO~n|Ufzonwf3%*rX+RAJyB$iv%4x;LMO?6M6H&zP7| z^xUH!oytODGH-Li499R`TxqH^8=Orl<1GS0C(QB;`_a(SW+}y^mVy)9YX1fJ$mlL* zexY&ksJ|nvJ6c$$&I3x_%2grZ-mBML5wTUszjOZo{zKhdx5&-mGHRf$OJwbVGb2Mp9=GA_x0HRyT@FPaULa<5Gcd~0 z#VtW5zc{#ovR>eo;McoI3?%;L=rS6v48u)fgf~FUVP#>VY6t%R&)5H$4E)(aYnx)x zt*#WKH-Kr<^|mm0b1{8Ux|Rg#NITa}M{twjN*jlRuq@1FG+YrT-Et9@eLu2piQS`9 zGHOB_SnGDy!1=e*PcUHwoiIE-P$x|tZcA|kCEx|-GO8DN2?_ARoc3D-@{jcO18eUX zbRGX&$m_m4@X{l@Li}e^?%(@2-^1{J{7X*7Gx^AUzH0h+9{-2z-!}HERC(V^UtxdF zu>SL8|NAt0y#9Oae}*|RZrbDQ6YT%M`JZqp_TwMxfBfnEKaTnzT;=TS2lBRiwekxqAXVj zHPMwL$TJGRBzoOdd(wqM{@B)JRr}IaU=1xemxf`Zh>v zLq$7O>DC3iIO>sI+UEvlj$qd)^`2}I)d{SmI{E7T*dcQs7AvfamX^5ZLf5i&rr8`e;$NDeT+{Yi5*?Iogs9z zz&`Y0XuLAt9%iz}8|-!n^Ml|@=m=I{2kMTi$HVjZWQ8FNwxQM=h3ZtiLJv3{fPd7`P{5jb0!9@5p_@sd+4LoV!JJZ1b02%+KqyPW_