diff --git a/Cargo.lock b/Cargo.lock index b4c60757..d736e6db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.88" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arbitrary" @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -405,9 +405,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bytesize" @@ -462,7 +462,6 @@ dependencies = [ "indicatif", "inquire", "interactive-clap", - "interactive-clap-derive", "linked-hash-map", "names", "near-cli-rs", @@ -483,9 +482,7 @@ dependencies = [ [[package]] name = "cargo-near-build" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110e4e3b4dc5187c77581a49866018db00c1f4b1b108762aea203e1f35b14445" +version = "0.1.1" dependencies = [ "bs58 0.5.1", "camino", @@ -493,21 +490,32 @@ dependencies = [ "colored", "dunce", "eyre", + "git2", "hex", + "home", "libloading", - "log", "near-abi", + "nix", + "pathdiff", "rustc_version", "schemars", + "serde", "serde_json", "sha2 0.10.8", + "shell-words", "symbolic-debuginfo", + "tempfile", + "tracing", + "unix_path", + "url", "zstd 0.13.2", ] [[package]] name = "cargo-near-build" version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd00f13698319e43d9af5b1afc7045131342f3097dd78320a09bb6303bbf2d06" dependencies = [ "bs58 0.5.1", "camino", @@ -515,23 +523,15 @@ dependencies = [ "colored", "dunce", "eyre", - "git2", "hex", - "home", "libloading", "near-abi", - "nix", - "pathdiff", "rustc_version", "schemars", - "serde", "serde_json", "sha2 0.10.8", "symbolic-debuginfo", - "tempfile", "tracing", - "unix_path", - "url", "zstd 0.13.2", ] @@ -609,9 +609,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.18" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", "libc", @@ -1805,9 +1805,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1943,9 +1943,9 @@ dependencies = [ [[package]] name = "interactive-clap" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1e6acfe2ceaaa893a54c57d445a820d3b0fa4c6187b67b3f69fd07473c726e" +checksum = "3b0f7ba4a74027eb091780d5f44c60ab8d7d7bcb0770af33ef024d4e7d1c8cf3" dependencies = [ "interactive-clap-derive", "strum", @@ -1954,9 +1954,9 @@ dependencies = [ [[package]] name = "interactive-clap-derive" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab1ce8e6ef82771b125341b0a6bd5eb45888b01235aa61125ecc72cc22be4738" +checksum = "bdced66b21ea0b5ce63c96e34ebfdadf05cf74594a5659d2bdd5d2c8cf586fa0" dependencies = [ "proc-macro-error", "proc-macro2", @@ -2835,7 +2835,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bs58 0.5.1", - "cargo-near-build 0.1.0", + "cargo-near-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono", "fs2", "json-patch", @@ -4604,9 +4604,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap 2.5.0", "serde", @@ -4799,9 +4799,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" diff --git a/cargo-near-build/Cargo.toml b/cargo-near-build/Cargo.toml index 75eb1936..e7f17bba 100644 --- a/cargo-near-build/Cargo.toml +++ b/cargo-near-build/Cargo.toml @@ -30,6 +30,7 @@ home = { version = "0.5.9", optional = true } pathdiff = { version = "0.2.1", features = ["camino"], optional = true } unix_path = { version = "1.0.1", optional = true } tempfile = { version = "3.10.1", optional = true } +shell-words = { version = "1.0.0", optional = true} [target.'cfg(target_os = "linux")'.dependencies] nix = { version = "0.29.0", features = ["user", "process"], optional = true } @@ -48,6 +49,6 @@ abi_build = [] docker = [ "dep:url", "dep:serde", "dep:git2", "dep:home", "dep:pathdiff", "dep:unix_path", - "dep:tempfile", "dep:nix" + "dep:tempfile", "dep:nix", "dep:shell-words" ] diff --git a/cargo-near-build/src/cargo_native/compile.rs b/cargo-near-build/src/cargo_native/compile.rs index 66d21771..c5f8f67f 100644 --- a/cargo-near-build/src/cargo_native/compile.rs +++ b/cargo-near-build/src/cargo_native/compile.rs @@ -134,7 +134,7 @@ where ColorPreference::Never => cmd.args(["--color", "never"]), }; - tracing::info!("Invoking cargo: {:?}", cmd); + tracing::info!("Invoking cargo: {:#?}", cmd); let mut child = cmd // capture the stdout to return from this function as bytes diff --git a/cargo-near-build/src/near/abi/generate/mod.rs b/cargo-near-build/src/near/abi/generate/mod.rs index 921f4c08..f2cda718 100644 --- a/cargo-near-build/src/near/abi/generate/mod.rs +++ b/cargo-near-build/src/near/abi/generate/mod.rs @@ -16,6 +16,7 @@ pub fn procedure( generate_docs: bool, hide_warnings: bool, cargo_feature_args: &[&str], + env: &[(&str, &str)], color: ColorPreference, ) -> eyre::Result { let root_node = crate_metadata @@ -68,15 +69,19 @@ pub fn procedure( pretty_print::step("Generating ABI"); - let dylib_artifact = cargo_native::compile::run::( - &crate_metadata.manifest_path, - cargo_args.as_slice(), - vec![ + let compile_env = { + let compile_env = vec![ ("CARGO_PROFILE_DEV_OPT_LEVEL", "0"), ("CARGO_PROFILE_DEV_DEBUG", "0"), ("CARGO_PROFILE_DEV_LTO", "off"), (env_keys::BUILD_RS_ABI_STEP_HINT, "true"), - ], + ]; + [&compile_env, env].concat() + }; + let dylib_artifact = cargo_native::compile::run::( + &crate_metadata.manifest_path, + cargo_args.as_slice(), + compile_env, hide_warnings, color, )?; diff --git a/cargo-near-build/src/near/abi/mod.rs b/cargo-near-build/src/near/abi/mod.rs index 59557926..0898a298 100644 --- a/cargo-near-build/src/near/abi/mod.rs +++ b/cargo-near-build/src/near/abi/mod.rs @@ -38,6 +38,7 @@ pub fn build(args: abi_types::Opts) -> eyre::Result<()> { !args.no_doc, false, &[], + &[], color, )?; let abi_types::Result { path } = write_to_file( diff --git a/cargo-near-build/src/near/build/mod.rs b/cargo-near-build/src/near/build/mod.rs index 3b5b8dd9..702d6db5 100644 --- a/cargo-near-build/src/near/build/mod.rs +++ b/cargo-near-build/src/near/build/mod.rs @@ -51,6 +51,11 @@ pub fn run(args: Opts) -> eyre::Result { let out_dir = crate_metadata.resolve_output_dir(args.out_dir.map(Into::into))?; let mut build_env = vec![("RUSTFLAGS", "-C link-arg=-s")]; + build_env.extend( + args.env + .iter() + .map(|(key, value)| (key.as_ref(), value.as_ref())), + ); let mut cargo_args = vec!["--target", COMPILATION_TARGET]; let cargo_feature_args = { let mut feat_args = vec![]; @@ -76,14 +81,22 @@ pub fn run(args: Opts) -> eyre::Result { let (builder_version, builder_version_mismatch) = VersionMismatch::get_coerced_builder_version()?; if !args.no_abi { - let mut contract_abi = abi::generate::procedure( - &crate_metadata, - args.no_locked, - !args.no_doc, - true, - &cargo_feature_args, - color.clone(), - )?; + let mut contract_abi = { + let env = args + .env + .iter() + .map(|(key, value)| (key.as_ref(), value.as_ref())) + .collect::>(); + abi::generate::procedure( + &crate_metadata, + args.no_locked, + !args.no_doc, + true, + &cargo_feature_args, + &env, + color.clone(), + )? + }; let embedding_binary = args.cli_description.cli_name_abi; contract_abi.metadata.build = Some(BuildInfo { diff --git a/cargo-near-build/src/near/docker_build/subprocess_step.rs b/cargo-near-build/src/near/docker_build/subprocess_step.rs index 7ccda8df..4b05385d 100644 --- a/cargo-near-build/src/near/docker_build/subprocess_step.rs +++ b/cargo-near-build/src/near/docker_build/subprocess_step.rs @@ -46,9 +46,20 @@ pub fn run( let env = env_vars::EnvVars::new(&docker_build_meta, cloned_repo)?; let env_args = env.docker_args(); - let cargo_cmd = opts - .get_cli_build_command_in_docker(docker_build_meta.container_build_command.clone())?; - println!("{} {}", "build command in container:".green(), cargo_cmd); + let shell_escaped_cargo_cmd = { + let cargo_cmd = opts.get_cli_build_command_in_docker( + docker_build_meta.container_build_command.clone(), + docker_build_meta.passed_env.clone(), + )?; + tracing::debug!("cli_build_command_in_docker {:#?}", cargo_cmd); + shell_words::join(cargo_cmd) + }; + println!( + "{} {}", + "build command in container:".green(), + shell_escaped_cargo_cmd + ); + println!(); let docker_args = { let mut docker_args = vec![ @@ -74,8 +85,8 @@ pub fn run( docker_args.extend(vec![&docker_image, "/bin/bash", "-c"]); - docker_args.push(&cargo_cmd); - tracing::debug!("docker command : {:?}", docker_args); + docker_args.push(&shell_escaped_cargo_cmd); + tracing::debug!("docker command : {:#?}", docker_args); docker_args }; diff --git a/cargo-near-build/src/types/near/build/input/docker_context.rs b/cargo-near-build/src/types/near/build/input/docker_context.rs index 0f93fce1..c74e2bdc 100644 --- a/cargo-near-build/src/types/near/build/input/docker_context.rs +++ b/cargo-near-build/src/types/near/build/input/docker_context.rs @@ -14,100 +14,187 @@ impl super::Opts { Ok(contract_path) } - const BUILD_COMMAND_CLI_CONFIG_ERR: &'static str = "cannot be used, when `container_build_command` is configured from `[package.metadata.near.reproducible_build]` in Cargo.toml"; + const BUILD_COMMAND_CLI_CONFIG_ERR: &'static str = "flag cannot be used, when `container_build_command` is configured from `[package.metadata.near.reproducible_build]` in Cargo.toml"; pub fn get_cli_build_command_in_docker( &self, manifest_command: Option>, - ) -> eyre::Result { + passed_env: Option>, + ) -> eyre::Result> { if let Some(manifest_command) = manifest_command { - // NOTE: `--no-locked` is allowed for docker builds - // if self.no_locked { - // no-op - // } - if self.no_release { - return Err(eyre::eyre!(format!( - "`{}` {}", - "--no-release", - Self::BUILD_COMMAND_CLI_CONFIG_ERR - ))); - } - if self.no_abi { - return Err(eyre::eyre!(format!( - "`{}` {}", - "--no-abi", - Self::BUILD_COMMAND_CLI_CONFIG_ERR - ))); - } - if self.no_embed_abi { - return Err(eyre::eyre!(format!( - "`{}` {}", - "--no-embed-abi", - Self::BUILD_COMMAND_CLI_CONFIG_ERR - ))); - } - if self.no_doc { - return Err(eyre::eyre!(format!( - "`{}` {}", - "--no-doc", - Self::BUILD_COMMAND_CLI_CONFIG_ERR - ))); - } - if self.features.is_some() { - return Err(eyre::eyre!(format!( - "`{}` {}", - "--features", - Self::BUILD_COMMAND_CLI_CONFIG_ERR - ))); - } - if self.no_default_features { - return Err(eyre::eyre!(format!( - "`{}` {}", - "--no-default-features", - Self::BUILD_COMMAND_CLI_CONFIG_ERR - ))); + self.append_env_suffix(manifest_command, passed_env) + } else { + println!( + "{}", + "configuring `container_build_command` from cli args, passed to current command" + .cyan() + ); + println!(); + Ok(self.passthrough_some_opts_into_docker_cmd()) + } + } + + fn append_env_suffix( + &self, + mut manifest_command: Vec, + passed_env: Option>, + ) -> eyre::Result> { + self.check_flag_conflicts_with_manifest_command()?; + if let Some(passed_env) = passed_env { + let suffix_env = passed_env + .into_iter() + .filter(|env_key| std::env::var(env_key).is_ok()) + .flat_map(|env_key| { + println!( + "{}{}{}", + "detected environment build parameter, which has been set: `".cyan(), + env_key.yellow(), + "`".cyan(), + ); + let value = std::env::var(&env_key).unwrap(); + let pair = [env_key, value].join("="); + ["--env".to_string(), pair] + }) + .collect::>(); + + if !suffix_env.is_empty() { + println!( + "{}{}{}", + "(listed in `".cyan(), + "passed_env".yellow(), + "` from `[package.metadata.near.reproducible_build]` in Cargo.toml)".cyan(), + ); + println!(); } - return Ok(manifest_command.join(" ")); + + manifest_command.extend(suffix_env); } - println!( - "{}", - "configuring `container_build_command` from cli args, passed to current command".cyan() - ); - let mut cli_args = vec![]; + + Ok(manifest_command) + } + + fn passthrough_some_opts_into_docker_cmd(&self) -> Vec { + let mut cli_args: Vec = vec![]; // NOTE: not passing through `no_locked` to cmd in container, // an invisible Cargo.lock was generated by implicit `cargo metadata` anyway // if self.no_locked { // no-op // } + + cli_args.extend(self.no_release.then_some("--no-release".into())); + cli_args.extend(self.no_abi.then_some("--no-abi".into())); + cli_args.extend(self.no_embed_abi.then_some("--no-embed-abi".into())); + cli_args.extend(self.no_doc.then_some("--no-doc".into())); + + if let Some(ref features) = self.features { + cli_args.extend(["--features".into(), features.clone()]); + } + cli_args.extend( + self.no_default_features + .then_some("--no-default-features".into()), + ); + if let Some(ref color) = self.color { + cli_args.extend(["--color".into(), color.to_string()]); + } + cli_args.extend(self.env.clone().into_iter().flat_map(|(key, value)| { + let equal_pair = [key, value].join("="); + ["--env".to_string(), equal_pair] + })); + + let mut cli_command_prefix = self.cli_description.cli_command_prefix.clone(); + cli_command_prefix.extend(cli_args); + cli_command_prefix + } + fn check_flag_conflicts_with_manifest_command(&self) -> eyre::Result<()> { + // NOTE: `--no-locked` is allowed for docker builds + // if self.no_locked { + // no-op + // } if self.no_release { - cli_args.push("--no-release"); + return Err(eyre::eyre!(format!( + "`{}` {}", + "--no-release", + Self::BUILD_COMMAND_CLI_CONFIG_ERR + ))); } if self.no_abi { - cli_args.push("--no-abi"); + return Err(eyre::eyre!(format!( + "`{}` {}", + "--no-abi", + Self::BUILD_COMMAND_CLI_CONFIG_ERR + ))); } if self.no_embed_abi { - cli_args.push("--no-embed-abi"); + return Err(eyre::eyre!(format!( + "`{}` {}", + "--no-embed-abi", + Self::BUILD_COMMAND_CLI_CONFIG_ERR + ))); } if self.no_doc { - cli_args.push("--no-doc"); + return Err(eyre::eyre!(format!( + "`{}` {}", + "--no-doc", + Self::BUILD_COMMAND_CLI_CONFIG_ERR + ))); } - if let Some(ref features) = self.features { - cli_args.extend(&["--features", features]); + if self.features.is_some() { + return Err(eyre::eyre!(format!( + "`{}` {}", + "--features", + Self::BUILD_COMMAND_CLI_CONFIG_ERR + ))); } if self.no_default_features { - cli_args.push("--no-default-features"); + return Err(eyre::eyre!(format!( + "`{}` {}", + "--no-default-features", + Self::BUILD_COMMAND_CLI_CONFIG_ERR + ))); } - - let color; - if let Some(ref color_arg) = self.color { - color = color_arg.to_string(); - cli_args.extend(&["--color", &color]); + if !self.env.is_empty() { + return Err(eyre::eyre!(format!( + "`{}` {}\n{}", + "--env", + Self::BUILD_COMMAND_CLI_CONFIG_ERR, + "You can specify environment vars in `passed_env` list in `[package.metadata.near.reproducible_build]` instead" + ))); } - let cli_command_prefix = self.cli_description.cli_command_prefix.clone(); - let mut cli_command_prefix: Vec<&str> = - cli_command_prefix.iter().map(|ele| ele.as_str()).collect(); - cli_command_prefix.extend(&cli_args); - let cli_command = cli_command_prefix.join(" "); - Ok(cli_command) + Ok(()) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_passthrough_some_opts_into_docker_cmd() { + let opts = crate::BuildOpts { + no_release: true, + no_abi: true, + no_embed_abi: true, + no_doc: true, + features: Some("cool_feature".into()), + no_default_features: true, + color: Some(crate::ColorPreference::Always), + ..Default::default() + }; + + assert_eq!( + opts.passthrough_some_opts_into_docker_cmd(), + vec![ + "cargo".to_string(), + "near".to_string(), + "build".to_string(), + "--no-release".to_string(), + "--no-abi".to_string(), + "--no-embed-abi".to_string(), + "--no-doc".to_string(), + "--features".to_string(), + "cool_feature".to_string(), + "--no-default-features".to_string(), + "--color".to_string(), + "always".to_string() + ], + ); } } diff --git a/cargo-near-build/src/types/near/build/input/mod.rs b/cargo-near-build/src/types/near/build/input/mod.rs index 023e6788..e5d4bd81 100644 --- a/cargo-near-build/src/types/near/build/input/mod.rs +++ b/cargo-near-build/src/types/near/build/input/mod.rs @@ -16,6 +16,7 @@ pub enum BuildContext { /// [std::default::Default] implementation is derived: /// - `false` for `bool`-s, /// - `None` - for `Option`-s +/// - empty vector - for `Vec` /// - delegates to [impl Default for CliDescription](struct.CliDescription.html#impl-Default-for-CliDescription) #[derive(Debug, Default, Clone)] pub struct Opts { @@ -43,6 +44,9 @@ pub struct Opts { /// description of cli command, where [BuildOpts](crate::BuildOpts) are being used from, either real /// or emulated pub cli_description: CliDescription, + /// additional environment key-value pairs, that should be passed to underlying + /// build commands + pub env: Vec<(String, String)>, } /// used as field in [BuildOpts](crate::BuildOpts) @@ -107,6 +111,16 @@ impl Opts { color = color_arg.to_string(); cargo_args.extend(&["--color", &color]); } + + let equal_pairs: Vec = self + .env + .iter() + .map(|(key, value)| [key.as_str(), value.as_str()].join("=")) + .collect(); + for equal_pair in equal_pairs.iter() { + cargo_args.extend(&["--env", equal_pair]); + } + cargo_args .into_iter() .map(|el| el.to_string()) @@ -163,3 +177,30 @@ impl ColorPreference { } } } + +#[cfg(test)] +mod tests { + + #[test] + fn test_opts_get_cli_build_command_for_env_vals() { + let opts = super::Opts { + env: vec![ + ("KEY".into(), "VALUE".into()), + ( + "GOOGLE_QUERY".into(), + "https://www.google.com/search?q=google+translate&sca_esv=3c150c50f502bc5d" + .into(), + ), + ], + ..Default::default() + }; + + assert_eq!(opts.get_cli_build_command(), ["cargo".to_string(), + "near".to_string(), + "build".to_string(), + "--env".to_string(), + "KEY=VALUE".to_string(), + "--env".to_string(), + "GOOGLE_QUERY=https://www.google.com/search?q=google+translate&sca_esv=3c150c50f502bc5d".to_string()]); + } +} diff --git a/cargo-near-build/src/types/near/docker_build/metadata.rs b/cargo-near-build/src/types/near/docker_build/metadata.rs index 140b595f..03b32740 100644 --- a/cargo-near-build/src/types/near/docker_build/metadata.rs +++ b/cargo-near-build/src/types/near/docker_build/metadata.rs @@ -11,6 +11,7 @@ use crate::types::cargo::metadata::CrateMetadata; pub struct ReproducibleBuild { image: String, image_digest: String, + pub passed_env: Option>, pub container_build_command: Option>, /// a clonable git remote url, /// currently, only ones, starting with `https://`, are supported; @@ -29,6 +30,20 @@ impl std::fmt::Display for ReproducibleBuild { writeln!(f, " {}: {}", "image", self.image)?; writeln!(f, " {}: {}", "image digest", self.image_digest)?; + if let Some(ref passed_env) = self.passed_env { + writeln!( + f, + " {}: {:?}", + "passed environment variables", passed_env + )?; + } else { + writeln!( + f, + " {}: {}", + "passed environment variables", + "ABSENT".green() + )?; + } if let Some(ref cmd) = self.container_build_command { writeln!(f, " {}: {:?}", "container build command", cmd)?; } else { @@ -53,7 +68,7 @@ impl std::fmt::Display for ReproducibleBuild { } impl ReproducibleBuild { - fn validate(&self) -> eyre::Result<()> { + fn validate_image(&self) -> eyre::Result<()> { if self .image .chars() @@ -66,6 +81,9 @@ impl ReproducibleBuild { "`image`: string contains invalid characters", )); } + Ok(()) + } + fn validate_image_digest(&self) -> eyre::Result<()> { if self .image_digest .chars() @@ -78,6 +96,9 @@ impl ReproducibleBuild { "`image_digest`: string contains invalid characters", )); } + Ok(()) + } + fn validate_container_build_command(&self) -> eyre::Result<()> { let is_cargo_near = { let build_command = self.container_build_command.clone().unwrap_or_default(); Some("cargo") == build_command.first().map(AsRef::as_ref) @@ -103,6 +124,10 @@ impl ReproducibleBuild { )); } } + Ok(()) + } + + fn validate_if_unknown_keys_present(&self) -> eyre::Result<()> { if !self.unknown_keys.is_empty() { let keys = self .unknown_keys @@ -114,6 +139,10 @@ impl ReproducibleBuild { keys.join(",") )); } + Ok(()) + } + + fn validate_repository(&self) -> eyre::Result<()> { match self.repository { Some(ref repository) => { if repository.scheme() != "https" { @@ -135,6 +164,23 @@ impl ReproducibleBuild { } Ok(()) } + + fn validate(&self) -> eyre::Result<()> { + self.validate_image()?; + self.validate_image_digest()?; + self.validate_container_build_command()?; + self.validate_if_unknown_keys_present()?; + self.validate_repository()?; + + if self.passed_env.is_some() && self.container_build_command.is_none() { + return Err(eyre::eyre!( + "{}: \n{}", + "Malformed `[package.metadata.near.reproducible_build]` in Cargo.toml", + "using optional `passed_env` field requires that `container_build_command` is set too", + )); + } + Ok(()) + } pub fn parse(cargo_metadata: &CrateMetadata) -> eyre::Result { let build_meta_value = cargo_metadata .root_package diff --git a/cargo-near/Cargo.toml b/cargo-near/Cargo.toml index e8429ef2..83ee0a3c 100644 --- a/cargo-near/Cargo.toml +++ b/cargo-near/Cargo.toml @@ -38,8 +38,7 @@ linked-hash-map = { version = "0.5", features = ["serde_impl"] } names = { version = "0.14.0", default-features = false } derive_more = "0.99.9" shell-words = "1.0.0" -interactive-clap = "0.3" -interactive-clap-derive = "0.3" +interactive-clap = "0.3.1" near-cli-rs = { version = "0.15.0", default-features = false } reqwest = "0.12.5" indicatif = "0.17.8" diff --git a/cargo-near/src/commands/build_command/mod.rs b/cargo-near/src/commands/build_command/mod.rs index c6bfdc23..18970ddf 100644 --- a/cargo-near/src/commands/build_command/mod.rs +++ b/cargo-near/src/commands/build_command/mod.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use cargo_near_build::{env_keys, BuildArtifact, BuildContext, BuildOpts}; #[derive(Debug, Default, Clone, interactive_clap::InteractiveClap)] @@ -43,10 +45,23 @@ pub struct BuildCommand { #[interactive_clap(value_enum)] #[interactive_clap(skip_interactive_input)] pub color: Option, + /// env overrides in the form of `"KEY=VALUE"` strings + #[interactive_clap(long_vec_multiple_opt)] + pub env: Vec, } impl BuildCommand { + fn validate_env_opt(&self) -> color_eyre::eyre::Result<()> { + for pair in self.env.iter() { + pair.split_once('=').ok_or(color_eyre::eyre::eyre!( + "invalid \"key=value\" environment argument (must contain '='): {}", + pair + ))?; + } + Ok(()) + } pub fn run(self, context: BuildContext) -> color_eyre::eyre::Result { + self.validate_env_opt()?; if self.no_docker() { if let BuildContext::Deploy { skip_git_remote_check: true, @@ -83,10 +98,25 @@ impl From for BuildCommand { out_dir: value.out_dir, manifest_path: value.manifest_path, color: value.color, + env: value.env, } } } +fn get_env_key_vals(input: Vec) -> Vec<(String, String)> { + let iterator = input.iter().flat_map(|pair_string| { + pair_string + .split_once('=') + .map(|(env_key, value)| (env_key.to_string(), value.to_string())) + }); + + let dedup_map: HashMap = HashMap::from_iter(iterator); + + let result = dedup_map.into_iter().collect(); + tracing::trace!("passed additional environment pairs: {:#?}", result); + result +} + impl From for BuildOpts { fn from(value: BuildCommand) -> Self { Self { @@ -101,6 +131,7 @@ impl From for BuildOpts { manifest_path: value.manifest_path.map(Into::into), color: value.color.map(Into::into), cli_description: Default::default(), + env: get_env_key_vals(value.env), } } } @@ -124,6 +155,7 @@ impl BuildCommandlContext { features: scope.features.clone(), no_default_features: scope.no_default_features, color: scope.color.clone(), + env: scope.env.clone(), }; args.run(BuildContext::Build)?; Ok(Self)