diff --git a/.github/workflows/crontab_new_template_renewal.yml b/.github/workflows/crontab_new_template_renewal.yml index ab441145..42625d5c 100644 --- a/.github/workflows/crontab_new_template_renewal.yml +++ b/.github/workflows/crontab_new_template_renewal.yml @@ -30,7 +30,7 @@ jobs: let record = http get "https://hub.docker.com/v2/namespaces/sourcescan/repositories/cargo-near/tags" | get results | first; let mod_content = ( - open cargo-near/src/commands/new/new-project-template/Cargo.toml.template --raw | lines + open cargo-near/src/commands/new/new-project-template/Cargo.template.toml --raw | lines | each { |line| if ($line | str starts-with "image = ") { $'image = "sourcescan/cargo-near:($record.name)"' @@ -44,7 +44,7 @@ jobs: | to text ); - $mod_content | save -f cargo-near/src/commands/new/new-project-template/Cargo.toml.template + $mod_content | save -f cargo-near/src/commands/new/new-project-template/Cargo.template.toml git diff diff --git a/Cargo.lock b/Cargo.lock index 3e53846f..24f34c63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,7 +572,6 @@ dependencies = [ "tokio", "toml", "tracing", - "tracing-subscriber", "zstd 0.13.2", ] @@ -2687,9 +2686,9 @@ dependencies = [ [[package]] name = "near-cli-rs" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6696da4e6fb1a4b7f37a2ca2a25bd6eecf54a9d84fde59ea3a67d53e6ee48002" +checksum = "7961d3f890e0823a1737f1732a8019c6f74ac4cf7549b76722ad9c80c6769d1c" dependencies = [ "bip39", "bs58 0.5.1", diff --git a/README.md b/README.md index 3d9126c2..a2b47954 100644 --- a/README.md +++ b/README.md @@ -99,26 +99,57 @@ cargo near build Builds a NEAR smart contract along with its [ABI](https://github.com/near/abi) (while in the directory containing contract's Cargo.toml). -By default, this runs a reproducible build in a [Docker](https://docs.docker.com/) container, which: +Running the above command opens a menu with following variants: + +### `non-reproducible-wasm` + +**Recommended variant for use during local development.** + +This is a regular build, which behaves much like and is a thin wrapper around a regular `cargo build --target wasm32-unknown-unknown --release`. + +Additional flags for build configuration can be looked up by + +```bash +cargo near build non-reproducible-wasm --help +``` +if needed. + +### `reproducible-wasm` + +**Recommended variant for the production releases.** + +This variant runs a reproducible build in a [Docker](https://docs.docker.com/) container, which: 1. runs against source code version, committed to git, ignoring any uncommitted changes 2. requires that `Cargo.lock` of project is created (e.g. via `cargo update`) and added to git. - this enables `--locked` build by downstream `cargo` command. -3. will use configuration in [`[package.metadata.near.reproducible_build]`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.toml.template#L14-L25) - section of contract's `Cargo.toml` and [`package.repository`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.toml.template#L9) field +3. will use configuration in [`[package.metadata.near.reproducible_build]`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L14-L25) + section of contract's `Cargo.toml` and [`package.repository`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L9) field - default values for this section can also be found in `Cargo.toml` of template project, generated by `cargo near new` -Important flags: +**What's a reproducible build in context of NEAR?** +Why is it needed? Explanation of these points and a step-by-step tutorial is present at [SourceScan/verification-guide](https://github.com/SourceScan/verification-guide). -1. `--no-docker` - - flag can be used to perform a regular build with rust toolchain installed onto host, running the `cargo-near` cli. - - *NO*-Docker builds run against actual state of code in filesystem and not against a version, committed to source control. - -2. `--no-locked` - - flag is allowed in *NO*-Docker builds, e.g. to generate a `Cargo.lock` *and* simultaneously build the contract. - - flag is allowed in Docker builds, but - - such builds are not reproducible due to potential update of dependencies and compiled `wasm` mismatch as the result. +
+ Additional (optional) details on possible [package.metadata.near.reproducible_build] configuration

+ +1. available images can be found by this link https://hub.docker.com/r/sourcescan/cargo-near/tags + - [`image`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L18) and [`image_digest`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L19) are straightforward to configure: + ![image_and_digest_pinpoint](./docs/image_and_digest_pinpoint.png) +2. flags of build command, run inside of docker container, can be configured, if needed, by changing [`container_build_command`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L29) field + - base `container_build_command` for images starting with **sourcescan/cargo-near:0.13.0-rust-1.83.0** and after it is `["cargo", "near", "build", "non-reproducible-wasm", "--locked"]`, where the `--locked` flag is required + - base `container_build_command` for images prior to **sourcescan/cargo-near:0.13.0-rust-1.83.0** is `["cargo", "near", "build"]` + - additional flags, if needed, can be looked up on + - `cargo near build non-reproducible-wasm --help` for newer/latest images + - `cargo near build --help` for older ones + - running `docker run -it sourcescan/cargo-near:0.11.0-rust-1.82.0` (or another specific image) and checking the `--help` message of exact `cargo-near` in container may be helpful when in doubt +3. `cargo near` allows parameterizing build with values of environment variables, present at the time of the build and not present in a contract's source code, + by specifying their names in [`passed_env`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L24) array + - supported by **sourcescan/cargo-near:0.10.1-rust-1.82.0** image or later images + - SourceScan/Nearblocks does not support verifying such contracts with additional parameters present in their metadata yet + +

--- @@ -146,22 +177,22 @@ cargo near deploy Builds the smart contract (equivalent to `cargo near build`) and guides you to deploy it to the blockchain. -By default, this runs a reproducible build in a Docker container. +Similar to `build`, running the above command opens a menu with following variants: + +### `build-non-reproducible-wasm` + +This forwards to [non-reproducible-wasm](#non-reproducible-wasm) variant of `build` command. + +### `build-reproducible-wasm` + +This forwards to [reproducible-wasm](#reproducible-wasm) variant of `build` command. `deploy` command from Docker build requires that contract's source code: 1. doesn't have any modified tracked files, any staged changes or any untracked content. 2. has been pushed to remote repository, identified by - [`package.repository`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.toml.template#L9). - -Important flags: - -1. `--no-docker` - - flag can be used to perform a regular *NO*-Docker build *and* deploy. - - Similar to `build` command, in this case none of the git-related concerns and restrictions apply. + [`package.repository`](https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L9). -2. `--no-locked` - - flag is declined for deploy, due to its effects on `build` result ## Contribution diff --git a/cargo-near-build/src/near/docker_build/docker_checks/mod.rs b/cargo-near-build/src/near/docker_build/docker_checks/mod.rs index df83f188..42830c6a 100644 --- a/cargo-near-build/src/near/docker_build/docker_checks/mod.rs +++ b/cargo-near-build/src/near/docker_build/docker_checks/mod.rs @@ -19,7 +19,6 @@ pub fn handle_command_io_error( println!(); println!("{}", "`docker` executable isn't available".yellow()); print_installation_links(); - print_non_docker_suggestion(); Err(report) } Err(io_err) => { @@ -33,7 +32,6 @@ pub fn handle_command_io_error( .yellow() ); println!("{}", format!("Error `{:?}`", io_err).yellow()); - print_non_docker_suggestion(); Err(report) } } @@ -127,13 +125,4 @@ pub fn print_command_status(status: std::process::ExitStatus, command: std::proc ) .yellow() ); - print_non_docker_suggestion(); -} - -fn print_non_docker_suggestion() { - println!( - "{}", - "You can choose to opt out into non-docker build behaviour by using `--no-docker` flag." - .cyan() - ); } diff --git a/cargo-near-build/src/near/docker_build/mod.rs b/cargo-near-build/src/near/docker_build/mod.rs index cf47bbaf..731af488 100644 --- a/cargo-near-build/src/near/docker_build/mod.rs +++ b/cargo-near-build/src/near/docker_build/mod.rs @@ -1,5 +1,7 @@ use std::process::{Command, ExitStatus}; +use colored::Colorize; + use crate::docker::DockerBuildOpts; use crate::types::near::build::input::BuildContext; use crate::types::near::build::output::CompilationArtifact; @@ -33,10 +35,13 @@ pub fn run(opts: DockerBuildOpts) -> eyre::Result { }, )?; - let docker_build_meta = - pretty_print::handle_step("Parsing and validating `Cargo.toml` metadata...", || { - metadata::ReproducibleBuild::parse(cloned_repo.crate_metadata()) - })?; + let docker_build_meta = pretty_print::handle_step( + &format!( + "Parsing and validating `{}` section of contract's `Cargo.toml` ...", + "[package.metadata.near.reproducible_build]".magenta() + ), + || metadata::ReproducibleBuild::parse(cloned_repo.crate_metadata()), + )?; if let BuildContext::Deploy { skip_git_remote_check, diff --git a/cargo-near-build/src/types/cargo/metadata.rs b/cargo-near-build/src/types/cargo/metadata.rs index d4933ce5..97dff376 100644 --- a/cargo-near-build/src/types/cargo/metadata.rs +++ b/cargo-near-build/src/types/cargo/metadata.rs @@ -170,7 +170,9 @@ fn get_cargo_metadata( ); println!( "{}", - "You can choose to disable `--locked` flag for downstream `cargo` command with `--no-locked` flag.".cyan() + "You can choose to disable `--locked` flag for downstream `cargo` command \ + by adding `--no-locked` flag OR by removing `--locked` flag" + .cyan() ); thread::sleep(Duration::new(5, 0)); return Err(cargo_metadata::Error::CargoMetadata { 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 ba60b733..f2998731 100644 --- a/cargo-near-build/src/types/near/build/input/mod.rs +++ b/cargo-near-build/src/types/near/build/input/mod.rs @@ -88,7 +88,12 @@ impl Default for CliDescription { fn default() -> Self { Self { cli_name_abi: "cargo-near".into(), - cli_command_prefix: vec!["cargo".into(), "near".into(), "build".into()], + cli_command_prefix: vec![ + "cargo".into(), + "near".into(), + "build".into(), + "non-reproducible-wasm".into(), + ], } } } @@ -97,13 +102,16 @@ impl Opts { /// this is just 1-to-1 mapping of each struct's field to a cli flag /// in order of fields, as specified in struct's definition. /// `Default` implementation corresponds to plain `cargo near build` command without any args - pub(crate) fn get_cli_command_for_lib_context(&self) -> Vec { + pub fn get_cli_command_for_lib_context(&self) -> Vec { let cargo_args = self.cli_description.cli_command_prefix.clone(); let mut cargo_args: Vec<&str> = cargo_args.iter().map(|ele| ele.as_str()).collect(); - if self.no_locked { - cargo_args.push("--no-locked"); + // this logical NOT is needed to avoid writing manually `Default` trait impl for `Opts` + // with `self.locked` field and to keep default (if nothing is specified) to *locked* behavior + // which is a desired default for [crate::extended::build] functionality + if !self.no_locked { + cargo_args.push("--locked"); } - // `no_docker` field isn't present + if self.no_release { cargo_args.push("--no-release"); } @@ -220,6 +228,8 @@ mod tests { assert_eq!(opts.get_cli_command_for_lib_context(), ["cargo".to_string(), "near".to_string(), "build".to_string(), + "non-reproducible-wasm".to_string(), + "--locked".to_string(), "--env".to_string(), "KEY=VALUE".to_string(), "--env".to_string(), diff --git a/cargo-near-build/src/types/near/docker_build/compute_command.rs b/cargo-near-build/src/types/near/docker_build/compute_command.rs index 3318715c..7bb88143 100644 --- a/cargo-near-build/src/types/near/docker_build/compute_command.rs +++ b/cargo-near-build/src/types/near/docker_build/compute_command.rs @@ -3,26 +3,25 @@ use colored::Colorize; use super::metadata; impl super::Opts { - 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, docker_build_meta: &metadata::ReproducibleBuild, ) -> eyre::Result> { - if let Some(manifest_command) = docker_build_meta.container_build_command.as_ref() { - self.append_env_suffix( - manifest_command.clone(), - docker_build_meta.passed_env.clone(), - ) - } else { - println!( - "{}", - "configuring `container_build_command` from cli args, passed to current command" - .cyan() - ); - println!(); - Ok(self.passthrough_some_opts_into_docker_cmd()) - } + let Some(manifest_command) = docker_build_meta.container_build_command.as_ref() else { + return Err(eyre::eyre!( + "`container_build_command` is expected to be non-empty (after validation)" + )); + }; + println!( + "{}`{}`{}", + "using `container_build_command` from ".cyan(), + "[package.metadata.near.reproducible_build]".magenta(), + " in Cargo.toml".cyan() + ); + self.append_env_suffix( + manifest_command.clone(), + docker_build_meta.passed_env.clone(), + ) } fn append_env_suffix( @@ -30,7 +29,6 @@ impl super::Opts { 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() @@ -63,137 +61,4 @@ impl super::Opts { 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())); - cli_args.extend(self.no_wasmopt.then_some("--no-wasmopt".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 { - 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.no_wasmopt { - return Err(eyre::eyre!(format!( - "`{}` {}", - "--no-wasmopt ", - 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 - ))); - } - 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" - ))); - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_passthrough_some_opts_into_docker_cmd() { - let opts = crate::docker::DockerBuildOpts { - 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/docker_build/metadata.rs b/cargo-near-build/src/types/near/docker_build/metadata.rs index d884f272..12e64e8b 100644 --- a/cargo-near-build/src/types/near/docker_build/metadata.rs +++ b/cargo-near-build/src/types/near/docker_build/metadata.rs @@ -99,12 +99,14 @@ impl ReproducibleBuild { Ok(()) } fn validate_container_build_command(&self) -> eyre::Result<()> { + let build_command = self.container_build_command.clone().ok_or(eyre::eyre!( + "`container_build_command` field not found! It is required since 0.13.0 version of `cargo-near`" + ))?; let is_cargo_near = { - let build_command = self.container_build_command.clone().unwrap_or_default(); Some("cargo") == build_command.first().map(AsRef::as_ref) && Some("near") == build_command.get(1).map(AsRef::as_ref) }; - for command_token in self.container_build_command.clone().unwrap_or_default() { + for command_token in build_command { if command_token .chars() .any(|c| !c.is_ascii() || c.is_ascii_control() || c.is_ascii_whitespace()) @@ -116,6 +118,8 @@ impl ReproducibleBuild { "`container_build_command`: string token contains invalid characters", )); } + // for versions of cargo-near inside of container <0.13 + // versions >=0.13 require `--locked` flag instead, but this isn't validated if is_cargo_near && command_token == "--no-locked" { return Err(eyre::eyre!( "{}:\n{}", @@ -181,13 +185,6 @@ impl ReproducibleBuild { 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 { @@ -199,26 +196,22 @@ impl ReproducibleBuild { let mut build_meta: ReproducibleBuild = match build_meta_value { None => { - println!( - "{}{}{}", - "An error with missing ".yellow(), - "`[package.metadata.near.reproducible_build]`".magenta(), - " in Cargo.toml has been encountered...".yellow() - ); println!( "{}", - "You can choose to disable docker build with `--no-docker` flag...".cyan() + "Metadata section in contract's Cargo.toml, \ + that is prerequisite for reproducible builds, has not been found..." + .yellow() ); thread::sleep(Duration::new(7, 0)); println!(); println!( "{}{}{}", - "Alternatively you can add and commit ".cyan(), + "You can add and commit ".cyan(), "`[package.metadata.near.reproducible_build]` ".magenta(), "to your contract's Cargo.toml:".cyan() ); println!("{}{}", "- default values for the section can be found at ".cyan(), - "https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.toml.template#L14-L25".magenta()); + "https://github.com/near/cargo-near/blob/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml#L14-L29".magenta()); println!( "{}{}", "- the same can also be found in Cargo.toml of template project, generated by " @@ -249,11 +242,6 @@ impl ReproducibleBuild { .transpose()?; build_meta.validate()?; println!("{} {}", "reproducible build metadata:".green(), build_meta); - if build_meta.container_build_command.is_some() { - println!( - "{}", "using `container_build_command` from `[package.metadata.near.reproducible_build]` in Cargo.toml".cyan() - ); - } Ok(build_meta) } pub fn concat_image(&self) -> String { diff --git a/cargo-near-build/src/types/near/docker_build/mod.rs b/cargo-near-build/src/types/near/docker_build/mod.rs index 2261f819..a981377b 100644 --- a/cargo-near-build/src/types/near/docker_build/mod.rs +++ b/cargo-near-build/src/types/near/docker_build/mod.rs @@ -1,5 +1,5 @@ use crate::types::cargo::manifest_path::ManifestPath; -use crate::{CliDescription, ColorPreference}; +use crate::ColorPreference; use super::build::input::BuildContext; @@ -15,42 +15,13 @@ pub struct Opts { /// disable implicit `--locked` flag for all `cargo` commands, enabled by default #[builder(default)] pub no_locked: bool, - /// Build contract in debug mode, without optimizations and bigger in size - #[builder(default)] - pub no_release: bool, - /// Do not generate ABI for the contract - #[builder(default)] - pub no_abi: bool, - /// Do not embed the ABI in the contract binary - #[builder(default)] - pub no_embed_abi: bool, - /// Do not include rustdocs in the embedded ABI - #[builder(default)] - pub no_doc: bool, - /// do not run `wasm-opt -O` on the generated output as a post-step - #[builder(default)] - pub no_wasmopt: bool, /// Copy final artifacts to this directory pub out_dir: Option, /// Path to the `Cargo.toml` of the contract to build pub manifest_path: Option, - /// Set compile-time feature flags. - #[builder(into)] - pub features: Option, - /// Disables default feature flags. - #[builder(default)] - pub no_default_features: bool, /// Coloring: auto, always, never; /// assumed to be auto when `None` pub color: Option, - /// description of cli command, where [BuildOpts](crate::BuildOpts) are being used from, either real - /// or emulated - #[builder(default)] - pub cli_description: CliDescription, - /// additional environment key-value pairs, that should be passed to underlying - /// build commands - #[builder(default)] - pub env: Vec<(String, String)>, #[builder(default)] pub context: BuildContext, } diff --git a/cargo-near/src/commands/abi_command/mod.rs b/cargo-near/src/commands/abi/mod.rs similarity index 81% rename from cargo-near/src/commands/abi_command/mod.rs rename to cargo-near/src/commands/abi/mod.rs index bcc8bc32..28ff252c 100644 --- a/cargo-near/src/commands/abi_command/mod.rs +++ b/cargo-near/src/commands/abi/mod.rs @@ -3,10 +3,10 @@ use cargo_near_build::abi::AbiOpts; #[derive(Debug, Clone, interactive_clap::InteractiveClap)] #[interactive_clap(input_context = near_cli_rs::GlobalContext)] #[interactive_clap(output_context = AbiCommandlContext)] -pub struct AbiCommand { - /// disable implicit `--locked` flag for all `cargo` commands, enabled by default +pub struct Command { + /// enable `--locked` flag for all `cargo` commands, disabled by default #[interactive_clap(long)] - pub no_locked: bool, + pub locked: bool, /// Include rustdocs in the ABI file #[interactive_clap(long)] pub no_doc: bool, @@ -28,10 +28,10 @@ pub struct AbiCommand { pub color: Option, } -impl From for AbiOpts { - fn from(value: AbiCommand) -> Self { +impl From for AbiOpts { + fn from(value: Command) -> Self { Self { - no_locked: value.no_locked, + no_locked: !value.locked, no_doc: value.no_doc, compact_abi: value.compact_abi, out_dir: value.out_dir.map(Into::into), @@ -47,10 +47,10 @@ pub struct AbiCommandlContext; impl AbiCommandlContext { pub fn from_previous_context( _previous_context: near_cli_rs::GlobalContext, - scope: &::InteractiveClapContextScope, + scope: &::InteractiveClapContextScope, ) -> color_eyre::eyre::Result { - let args = AbiCommand { - no_locked: scope.no_locked, + let args = Command { + locked: scope.locked, no_doc: scope.no_doc, compact_abi: scope.compact_abi, out_dir: scope.out_dir.clone(), diff --git a/cargo-near/src/commands/build/actions/non_reproducible_wasm/mod.rs b/cargo-near/src/commands/build/actions/non_reproducible_wasm/mod.rs new file mode 100644 index 00000000..4338375e --- /dev/null +++ b/cargo-near/src/commands/build/actions/non_reproducible_wasm/mod.rs @@ -0,0 +1,215 @@ +use cargo_near_build::BuildArtifact; + +#[derive(Default, Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = cargo_near_build::BuildContext)] +#[interactive_clap(output_context = context::Context)] +pub struct BuildOpts { + /// enable `--locked` flag for all `cargo` commands, disabled by default + #[interactive_clap(long)] + pub locked: bool, + /// Build contract in debug mode, without optimizations and bigger is size + #[interactive_clap(long)] + pub no_release: bool, + /// Do not generate ABI for the contract + #[interactive_clap(long)] + pub no_abi: bool, + /// Do not embed the ABI in the contract binary + #[interactive_clap(long)] + pub no_embed_abi: bool, + /// Do not include rustdocs in the embedded ABI + #[interactive_clap(long)] + pub no_doc: bool, + /// Do not run `wasm-opt -O` on the generated output as a post-step + #[interactive_clap(long)] + pub no_wasmopt: bool, + /// Copy final artifacts to this directory + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub out_dir: Option, + /// Path to the `Cargo.toml` of the contract to build + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub manifest_path: Option, + /// Set compile-time feature flags. + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub features: Option, + /// Disables default feature flags. + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub no_default_features: bool, + /// Coloring: auto, always, never + #[interactive_clap(long)] + #[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 From for BuildOpts { + fn from(value: CliBuildOpts) -> Self { + Self { + locked: value.locked, + no_release: value.no_release, + no_abi: value.no_abi, + no_embed_abi: value.no_embed_abi, + no_doc: value.no_doc, + no_wasmopt: value.no_wasmopt, + out_dir: value.out_dir, + manifest_path: value.manifest_path, + features: value.features, + no_default_features: value.no_default_features, + color: value.color, + env: value.env, + } + } +} + +pub mod context { + + #[derive(Debug)] + pub struct Context; + + impl Context { + pub fn from_previous_context( + _previous_context: cargo_near_build::BuildContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let opts = super::BuildOpts { + locked: scope.locked, + no_release: scope.no_release, + no_abi: scope.no_abi, + no_embed_abi: scope.no_embed_abi, + no_doc: scope.no_doc, + no_wasmopt: scope.no_wasmopt, + features: scope.features.clone(), + no_default_features: scope.no_default_features, + env: scope.env.clone(), + out_dir: scope.out_dir.clone(), + manifest_path: scope.manifest_path.clone(), + color: scope.color.clone(), + }; + super::run(opts)?; + Ok(Self) + } + } +} + +impl From for cargo_near_build::BuildOpts { + fn from(value: BuildOpts) -> Self { + Self { + no_locked: !value.locked, + no_release: value.no_release, + no_abi: value.no_abi, + no_embed_abi: value.no_embed_abi, + no_doc: value.no_doc, + no_wasmopt: value.no_wasmopt, + features: value.features, + no_default_features: value.no_default_features, + out_dir: value.out_dir.map(Into::into), + manifest_path: value.manifest_path.map(Into::into), + color: value.color.map(Into::into), + cli_description: Default::default(), + env: env_pairs::get_key_vals(value.env), + override_nep330_contract_path: None, + override_cargo_target_dir: None, + } + } +} + +mod env_pairs { + use std::collections::HashMap; + + impl super::BuildOpts { + pub(super) 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(super) fn get_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::info!( + target: "near_teach_me", + parent: &tracing::Span::none(), + "Passed additional environment pairs:\n{}", + near_cli_rs::common::indent_payload(&format!("{:#?}", result)) + ); + result + } +} + +pub mod rule { + use color_eyre::Section; + use colored::Colorize; + + const COMMAND_ERR_MSG: &str = "`container_build_command` is required to start with"; + + fn is_inside_docker_context() -> bool { + std::env::var(cargo_near_build::env_keys::nep330::BUILD_ENVIRONMENT).is_ok() + } + pub fn assert_locked(opts: &super::BuildOpts) { + if is_inside_docker_context() { + assert!( + opts.locked, + "build command should have `--locked` flag in docker" + ); + } + } + + fn get_docker_image() -> String { + std::env::var(cargo_near_build::env_keys::nep330::BUILD_ENVIRONMENT).unwrap_or_else(|_| { + panic!( + "`{}` is expected to be set", + cargo_near_build::env_keys::nep330::BUILD_ENVIRONMENT + ) + }) + } + pub fn enforce_this_program_args() -> color_eyre::eyre::Result<()> { + if is_inside_docker_context() { + let args = std::env::args().collect::>(); + let default_cmd = + cargo_near_build::BuildOpts::default().get_cli_command_for_lib_context(); + let default_cmd_len = default_cmd.len(); + if (args.len() < default_cmd_len) + || (args[1..default_cmd_len] != default_cmd[1..default_cmd_len]) + { + return Err(color_eyre::eyre::eyre!( + "{}\n`{}` for the used image:\n{}", + COMMAND_ERR_MSG, + serde_json::to_string(&default_cmd).unwrap(), + get_docker_image() + ) + .note(format!( + "The default `{}` has changed since `{}` image\n\ + See {}", + "container_build_command".cyan(), + "sourcescan/cargo-near:0.13.0-rust-1.83.0".cyan(), + "https://github.com/near/cargo-near/releases".cyan() + ))); + } + } + Ok(()) + } +} + +pub fn run(opts: BuildOpts) -> color_eyre::eyre::Result { + rule::assert_locked(&opts); + opts.validate_env_opt()?; + cargo_near_build::build(opts.into()) +} diff --git a/cargo-near/src/commands/build/actions/reproducible_wasm/mod.rs b/cargo-near/src/commands/build/actions/reproducible_wasm/mod.rs new file mode 100644 index 00000000..6fad36a0 --- /dev/null +++ b/cargo-near/src/commands/build/actions/reproducible_wasm/mod.rs @@ -0,0 +1,76 @@ +use cargo_near_build::BuildContext; + +use cargo_near_build::docker; +use cargo_near_build::BuildArtifact; + +#[derive(Debug, Default, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = cargo_near_build::BuildContext)] +#[interactive_clap(output_context = context::Context)] +pub struct BuildOpts { + /// disable implicit `--locked` flag for all `cargo` commands, enabled by default + #[interactive_clap(long)] + pub no_locked: bool, + /// Copy final artifacts to this directory + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub out_dir: Option, + /// Path to the `Cargo.toml` of the contract to build + #[interactive_clap(long)] + #[interactive_clap(skip_interactive_input)] + pub manifest_path: Option, + /// Coloring: auto, always, never + #[interactive_clap(long)] + #[interactive_clap(value_enum)] + #[interactive_clap(skip_interactive_input)] + pub color: Option, +} + +impl From for BuildOpts { + fn from(value: CliBuildOpts) -> Self { + Self { + no_locked: value.no_locked, + out_dir: value.out_dir, + manifest_path: value.manifest_path, + color: value.color, + } + } +} + +mod context { + #[derive(Debug)] + pub struct Context; + + impl Context { + pub fn from_previous_context( + previous_context: cargo_near_build::BuildContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let opts = super::BuildOpts { + no_locked: scope.no_locked, + out_dir: scope.out_dir.clone(), + manifest_path: scope.manifest_path.clone(), + color: scope.color.clone(), + }; + super::run(opts, previous_context)?; + Ok(Self) + } + } +} + +/// this is more or less equivalent to +/// impl From<(BuildCommand, BuildContext)> for docker::DockerBuildOpts +/// which is not possible due to BuildContext being a non-local type to current (cli) crate +fn docker_opts_from(value: (BuildOpts, BuildContext)) -> docker::DockerBuildOpts { + docker::DockerBuildOpts { + no_locked: value.0.no_locked, + out_dir: value.0.out_dir.map(Into::into), + manifest_path: value.0.manifest_path.map(Into::into), + color: value.0.color.map(Into::into), + context: value.1, + } +} + +pub fn run(opts: BuildOpts, context: BuildContext) -> color_eyre::eyre::Result { + let docker_opts = docker_opts_from((opts, context)); + cargo_near_build::docker::build(docker_opts) +} diff --git a/cargo-near/src/commands/build/mod.rs b/cargo-near/src/commands/build/mod.rs new file mode 100644 index 00000000..77d98447 --- /dev/null +++ b/cargo-near/src/commands/build/mod.rs @@ -0,0 +1,50 @@ +pub mod context { + #[derive(Debug, Clone)] + pub struct Context; + + impl From for cargo_near_build::BuildContext { + fn from(_value: Context) -> Self { + Self::Build + } + } + + impl Context { + pub fn from_previous_context( + _previous_context: near_cli_rs::GlobalContext, + _scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + Ok(Self) + } + } +} + +pub mod actions { + pub mod non_reproducible_wasm; + pub mod reproducible_wasm; + + use strum::{EnumDiscriminants, EnumIter, EnumMessage}; + + #[derive(Debug, Clone, EnumDiscriminants, interactive_clap::InteractiveClap)] + #[strum_discriminants(derive(EnumMessage, EnumIter))] + #[interactive_clap(input_context = near_cli_rs::GlobalContext)] + #[interactive_clap(output_context = super::context::Context)] + pub enum Actions { + #[strum_discriminants(strum( + message = "non-reproducible-wasm - Fast and simple (recommended for use during local development)" + ))] + /// Fast and simple (recommended for use during local development) + NonReproducibleWasm(self::non_reproducible_wasm::BuildOpts), + #[strum_discriminants(strum( + message = "reproducible-wasm - Requires [reproducible_build] section in Cargo.toml, and all changes committed to git (recommended for the production release)" + ))] + /// Requires `[reproducible_build]` section in Cargo.toml, and all changes committed to git (recommended for the production release) + ReproducibleWasm(self::reproducible_wasm::BuildOpts), + } +} + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(context = near_cli_rs::GlobalContext)] +pub struct Command { + #[interactive_clap(subcommand)] + actions: actions::Actions, +} diff --git a/cargo-near/src/commands/build_command/mod.rs b/cargo-near/src/commands/build_command/mod.rs deleted file mode 100644 index 5510ce6d..00000000 --- a/cargo-near/src/commands/build_command/mod.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::collections::HashMap; - -use cargo_near_build::docker; -use cargo_near_build::{env_keys, BuildArtifact, BuildContext, BuildOpts}; - -#[derive(Debug, Default, Clone, interactive_clap::InteractiveClap)] -#[interactive_clap(input_context = near_cli_rs::GlobalContext)] -#[interactive_clap(output_context = BuildCommandlContext)] -pub struct BuildCommand { - /// disable implicit `--locked` flag for all `cargo` commands, enabled by default - #[interactive_clap(long)] - pub no_locked: bool, - /// Build contract on host system and without embedding SourceScan NEP-330 metadata - #[interactive_clap(long)] - no_docker: bool, - /// Build contract in debug mode, without optimizations and bigger is size - #[interactive_clap(long)] - pub no_release: bool, - /// Do not generate ABI for the contract - #[interactive_clap(long)] - pub no_abi: bool, - /// Do not embed the ABI in the contract binary - #[interactive_clap(long)] - pub no_embed_abi: bool, - /// Do not include rustdocs in the embedded ABI - #[interactive_clap(long)] - pub no_doc: bool, - /// Do not run `wasm-opt -O` on the generated output as a post-step - #[interactive_clap(long)] - pub no_wasmopt: bool, - /// Copy final artifacts to this directory - #[interactive_clap(long)] - #[interactive_clap(skip_interactive_input)] - pub out_dir: Option, - /// Path to the `Cargo.toml` of the contract to build - #[interactive_clap(long)] - #[interactive_clap(skip_interactive_input)] - pub manifest_path: Option, - /// Set compile-time feature flags. - #[interactive_clap(long)] - #[interactive_clap(skip_interactive_input)] - pub features: Option, - /// Disables default feature flags. - #[interactive_clap(long)] - #[interactive_clap(skip_interactive_input)] - pub no_default_features: bool, - /// Coloring: auto, always, never - #[interactive_clap(long)] - #[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, - } = context - { - return Err(color_eyre::eyre::eyre!( - "`--skip-git-remote-check` flag is only applicable for docker builds" - )); - } - cargo_near_build::build(self.into()) - } else { - let docker_opts = docker_opts_from((self, context)); - cargo_near_build::docker::build(docker_opts) - } - } - pub fn no_docker(&self) -> bool { - std::env::var(env_keys::nep330::BUILD_ENVIRONMENT).is_ok() || self.no_docker - } -} - -impl From for BuildCommand { - fn from(value: CliBuildCommand) -> Self { - Self { - no_locked: value.no_locked, - no_docker: value.no_docker, - no_release: value.no_release, - no_abi: value.no_abi, - no_embed_abi: value.no_embed_abi, - no_doc: value.no_doc, - no_wasmopt: value.no_wasmopt, - features: value.features, - no_default_features: value.no_default_features, - 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::info!( - target: "near_teach_me", - parent: &tracing::Span::none(), - "Passed additional environment pairs:\n{}", - near_cli_rs::common::indent_payload(&format!("{:#?}", result)) - ); - result -} - -impl From for BuildOpts { - fn from(value: BuildCommand) -> Self { - Self { - no_locked: value.no_locked, - no_release: value.no_release, - no_abi: value.no_abi, - no_embed_abi: value.no_embed_abi, - no_doc: value.no_doc, - no_wasmopt: value.no_wasmopt, - features: value.features, - no_default_features: value.no_default_features, - out_dir: value.out_dir.map(Into::into), - 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), - override_nep330_contract_path: None, - override_cargo_target_dir: None, - } - } -} - -/// this is more or less equivalent to -/// impl From<(BuildCommand, BuildContext)> for docker::DockerBuildOpts -/// which is not possible due to BuildContext being a non-local type to current (cli) crate -fn docker_opts_from(value: (BuildCommand, BuildContext)) -> docker::DockerBuildOpts { - docker::DockerBuildOpts { - no_locked: value.0.no_locked, - no_release: value.0.no_release, - no_abi: value.0.no_abi, - no_embed_abi: value.0.no_embed_abi, - no_doc: value.0.no_doc, - no_wasmopt: value.0.no_wasmopt, - features: value.0.features, - no_default_features: value.0.no_default_features, - out_dir: value.0.out_dir.map(Into::into), - manifest_path: value.0.manifest_path.map(Into::into), - color: value.0.color.map(Into::into), - cli_description: Default::default(), - env: get_env_key_vals(value.0.env), - context: value.1, - } -} - -#[derive(Debug, Clone)] -pub struct BuildCommandlContext; - -impl BuildCommandlContext { - pub fn from_previous_context( - _previous_context: near_cli_rs::GlobalContext, - scope: &::InteractiveClapContextScope, - ) -> color_eyre::eyre::Result { - let args = BuildCommand { - no_locked: scope.no_locked, - no_docker: scope.no_docker, - no_release: scope.no_release, - no_abi: scope.no_abi, - no_embed_abi: scope.no_embed_abi, - no_doc: scope.no_doc, - no_wasmopt: scope.no_wasmopt, - out_dir: scope.out_dir.clone(), - manifest_path: scope.manifest_path.clone(), - 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) - } -} diff --git a/cargo-near/src/commands/deploy/actions/non_reproducible_wasm.rs b/cargo-near/src/commands/deploy/actions/non_reproducible_wasm.rs new file mode 100644 index 00000000..8bfab79d --- /dev/null +++ b/cargo-near/src/commands/deploy/actions/non_reproducible_wasm.rs @@ -0,0 +1,149 @@ +use near_cli_rs::commands::contract::deploy::initialize_mode::InitializeMode; + +use crate::commands::build as build_command; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = near_cli_rs::GlobalContext)] +#[interactive_clap(output_context = context::Context)] +#[interactive_clap(skip_default_from_cli)] +pub struct DeployOpts { + #[interactive_clap(flatten)] + /// Specify a build command args: + build_command_opts: build_command::actions::non_reproducible_wasm::BuildOpts, + #[interactive_clap(skip_default_input_arg)] + /// What is the contract account ID? + contract_account_id: near_cli_rs::types::account_id::AccountId, + #[interactive_clap(subcommand)] + initialize: InitializeMode, +} + +mod context { + use crate::commands::build as build_command; + + #[derive(Debug, Clone)] + pub struct Context(near_cli_rs::commands::contract::deploy::ContractFileContext); + + impl From for near_cli_rs::commands::contract::deploy::ContractFileContext { + fn from(item: Context) -> Self { + item.0 + } + } + + impl Context { + pub fn from_previous_context( + previous_context: near_cli_rs::GlobalContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let artifact = build_command::actions::non_reproducible_wasm::run( + scope.build_command_opts.clone(), + )?; + + let code = std::fs::read(&artifact.path).map_err(|err| { + color_eyre::eyre::eyre!( + "accessing {} to read wasm contents: {}", + artifact.path, + err + ) + })?; + Ok(Self( + near_cli_rs::commands::contract::deploy::ContractFileContext { + global_context: previous_context, + receiver_account_id: scope.contract_account_id.clone().into(), + signer_account_id: scope.contract_account_id.clone().into(), + code, + }, + )) + } + } +} + +/// this module is needed because of `#[interactive_clap(skip_default_input_arg)]` +/// on `contract_account_id` +mod manual_input { + impl super::DeployOpts { + pub fn input_contract_account_id( + context: &near_cli_rs::GlobalContext, + ) -> color_eyre::eyre::Result> { + near_cli_rs::common::input_signer_account_id_from_used_account_list( + &context.config.credentials_home_dir, + "What is the contract account ID?", + ) + } + } +} + +/// this module is needed because of #[interactive_clap(skip_default_from_cli)] +/// on `DeployOpts` +mod manual_from_cli { + use crate::commands::build as build_command; + use near_cli_rs::commands::contract::deploy::initialize_mode::InitializeMode; + + impl interactive_clap::FromCli for super::DeployOpts { + type FromCliContext = near_cli_rs::GlobalContext; + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + optional_clap_variant: Option<::CliVariant>, + context: Self::FromCliContext, + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + Self::FromCliError, + > + where + Self: Sized + interactive_clap::ToCli, + { + let mut clap_variant = optional_clap_variant.unwrap_or_default(); + + let build_command_opts = + if let Some(cli_build_command_opts) = &clap_variant.build_command_opts { + build_command::actions::non_reproducible_wasm::BuildOpts::from( + cli_build_command_opts.clone(), + ) + } else { + build_command::actions::non_reproducible_wasm::BuildOpts::default() + }; + + if clap_variant.contract_account_id.is_none() { + clap_variant.contract_account_id = match Self::input_contract_account_id(&context) { + Ok(Some(contract_account_id)) => Some(contract_account_id), + Ok(None) => return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err) + } + }; + } + let contract_account_id = clap_variant + .contract_account_id + .clone() + .expect("Unexpected error"); + + let new_context_scope = super::InteractiveClapContextScopeForDeployOpts { + build_command_opts, + contract_account_id, + }; + + let output_context = + match super::context::Context::from_previous_context(context, &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err) + } + }; + + match InitializeMode::from_cli(clap_variant.initialize.take(), output_context.into()) { + interactive_clap::ResultFromCli::Ok(initialize) => { + clap_variant.initialize = Some(initialize); + interactive_clap::ResultFromCli::Ok(clap_variant) + } + interactive_clap::ResultFromCli::Cancel(optional_initialize) => { + clap_variant.initialize = optional_initialize; + interactive_clap::ResultFromCli::Cancel(Some(clap_variant)) + } + interactive_clap::ResultFromCli::Back => interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(optional_initialize, err) => { + clap_variant.initialize = optional_initialize; + interactive_clap::ResultFromCli::Err(Some(clap_variant), err) + } + } + } + } +} diff --git a/cargo-near/src/commands/deploy/actions/reproducible_wasm.rs b/cargo-near/src/commands/deploy/actions/reproducible_wasm.rs new file mode 100644 index 00000000..5c4ff3be --- /dev/null +++ b/cargo-near/src/commands/deploy/actions/reproducible_wasm.rs @@ -0,0 +1,158 @@ +use near_cli_rs::commands::contract::deploy::initialize_mode::InitializeMode; + +use crate::commands::build as build_command; + +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(input_context = near_cli_rs::GlobalContext)] +#[interactive_clap(output_context = context::Context)] +#[interactive_clap(skip_default_from_cli)] +pub struct DeployOpts { + #[interactive_clap(flatten)] + /// Specify a build command args: + build_command_opts: build_command::actions::reproducible_wasm::BuildOpts, + /// whether to check that code has been pushed to repository during docker build + #[interactive_clap(long)] + skip_git_remote_check: bool, + #[interactive_clap(skip_default_input_arg)] + /// What is the contract account ID? + contract_account_id: near_cli_rs::types::account_id::AccountId, + #[interactive_clap(subcommand)] + initialize: InitializeMode, +} + +mod context { + use crate::commands::build as build_command; + + #[derive(Debug, Clone)] + pub struct Context(near_cli_rs::commands::contract::deploy::ContractFileContext); + + impl From for near_cli_rs::commands::contract::deploy::ContractFileContext { + fn from(item: Context) -> Self { + item.0 + } + } + + impl Context { + pub fn from_previous_context( + previous_context: near_cli_rs::GlobalContext, + scope: &::InteractiveClapContextScope, + ) -> color_eyre::eyre::Result { + let artifact = build_command::actions::reproducible_wasm::run( + scope.build_command_opts.clone(), + cargo_near_build::BuildContext::Deploy { + skip_git_remote_check: scope.skip_git_remote_check, + }, + )?; + let code = std::fs::read(&artifact.path).map_err(|err| { + color_eyre::eyre::eyre!( + "accessing {} to read wasm contents: {}", + artifact.path, + err + ) + })?; + + Ok(Self( + near_cli_rs::commands::contract::deploy::ContractFileContext { + global_context: previous_context, + receiver_account_id: scope.contract_account_id.clone().into(), + signer_account_id: scope.contract_account_id.clone().into(), + code, + }, + )) + } + } +} + +/// this module is needed because of `#[interactive_clap(skip_default_input_arg)]` +/// on `contract_account_id` +mod manual_input { + impl super::DeployOpts { + pub fn input_contract_account_id( + context: &near_cli_rs::GlobalContext, + ) -> color_eyre::eyre::Result> { + near_cli_rs::common::input_signer_account_id_from_used_account_list( + &context.config.credentials_home_dir, + "What is the contract account ID?", + ) + } + } +} + +/// this module is needed because of #[interactive_clap(skip_default_from_cli)] +/// on `DeployOpts` +mod manual_from_cli { + use crate::commands::build as build_command; + use near_cli_rs::commands::contract::deploy::initialize_mode::InitializeMode; + + impl interactive_clap::FromCli for super::DeployOpts { + type FromCliContext = near_cli_rs::GlobalContext; + type FromCliError = color_eyre::eyre::Error; + fn from_cli( + optional_clap_variant: Option<::CliVariant>, + context: Self::FromCliContext, + ) -> interactive_clap::ResultFromCli< + ::CliVariant, + Self::FromCliError, + > + where + Self: Sized + interactive_clap::ToCli, + { + let mut clap_variant = optional_clap_variant.unwrap_or_default(); + + let build_command_opts = + if let Some(cli_build_command_opts) = &clap_variant.build_command_opts { + build_command::actions::reproducible_wasm::BuildOpts::from( + cli_build_command_opts.clone(), + ) + } else { + build_command::actions::reproducible_wasm::BuildOpts::default() + }; + + if clap_variant.contract_account_id.is_none() { + clap_variant.contract_account_id = match Self::input_contract_account_id(&context) { + Ok(Some(contract_account_id)) => Some(contract_account_id), + Ok(None) => return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)), + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err) + } + }; + } + let contract_account_id = clap_variant + .contract_account_id + .clone() + .expect("Unexpected error"); + + let skip_git_remote_check = clap_variant.skip_git_remote_check; + + let new_context_scope = super::InteractiveClapContextScopeForDeployOpts { + build_command_opts, + contract_account_id, + skip_git_remote_check, + }; + + let output_context = + match super::context::Context::from_previous_context(context, &new_context_scope) { + Ok(new_context) => new_context, + Err(err) => { + return interactive_clap::ResultFromCli::Err(Some(clap_variant), err) + } + }; + + match InitializeMode::from_cli(clap_variant.initialize.take(), output_context.into()) { + interactive_clap::ResultFromCli::Ok(initialize) => { + clap_variant.initialize = Some(initialize); + interactive_clap::ResultFromCli::Ok(clap_variant) + } + interactive_clap::ResultFromCli::Cancel(optional_initialize) => { + clap_variant.initialize = optional_initialize; + interactive_clap::ResultFromCli::Cancel(Some(clap_variant)) + } + interactive_clap::ResultFromCli::Back => interactive_clap::ResultFromCli::Back, + interactive_clap::ResultFromCli::Err(optional_initialize, err) => { + clap_variant.initialize = optional_initialize; + interactive_clap::ResultFromCli::Err(Some(clap_variant), err) + } + } + } + } +} diff --git a/cargo-near/src/commands/deploy/mod.rs b/cargo-near/src/commands/deploy/mod.rs index a3e81c15..c462cf32 100644 --- a/cargo-near/src/commands/deploy/mod.rs +++ b/cargo-near/src/commands/deploy/mod.rs @@ -1,131 +1,29 @@ -use near_cli_rs::commands::contract::deploy::initialize_mode::InitializeMode; - -use crate::commands::build_command; - -#[derive(Debug, Clone, interactive_clap::InteractiveClap)] -#[interactive_clap(input_context = near_cli_rs::GlobalContext)] -#[interactive_clap(output_context = ContractContext)] -#[interactive_clap(skip_default_from_cli)] -pub struct Contract { - #[interactive_clap(flatten)] - /// Specify a build command args: - build_command_args: build_command::BuildCommand, - /// whether to check that code has been pushed to repository during docker build - #[interactive_clap(long)] - skip_git_remote_check: bool, - #[interactive_clap(skip_default_input_arg)] - /// What is the contract account ID? - contract_account_id: near_cli_rs::types::account_id::AccountId, - #[interactive_clap(subcommand)] - initialize: InitializeMode, -} - -#[derive(Debug, Clone)] -pub struct ContractContext(near_cli_rs::commands::contract::deploy::ContractFileContext); - -impl ContractContext { - pub fn from_previous_context( - previous_context: near_cli_rs::GlobalContext, - scope: &::InteractiveClapContextScope, - ) -> color_eyre::eyre::Result { - let file_path = scope - .build_command_args - .clone() - .run(cargo_near_build::BuildContext::Deploy { - skip_git_remote_check: scope.skip_git_remote_check, - })? - .path; - - Ok(Self( - near_cli_rs::commands::contract::deploy::ContractFileContext { - global_context: previous_context, - receiver_account_id: scope.contract_account_id.clone().into(), - signer_account_id: scope.contract_account_id.clone().into(), - code: std::fs::read(file_path)?, - }, - )) - } -} - -impl From for near_cli_rs::commands::contract::deploy::ContractFileContext { - fn from(item: ContractContext) -> Self { - item.0 +mod actions { + mod non_reproducible_wasm; + mod reproducible_wasm; + + use strum::{EnumDiscriminants, EnumIter, EnumMessage}; + + #[derive(Debug, Clone, EnumDiscriminants, interactive_clap::InteractiveClap)] + #[strum_discriminants(derive(EnumMessage, EnumIter))] + #[interactive_clap(context = near_cli_rs::GlobalContext)] + pub enum Actions { + #[strum_discriminants(strum( + message = "build-non-reproducible-wasm - Fast and simple build (recommended for use during local development)" + ))] + /// Fast and simple build (recommended for use during local development) + BuildNonReproducibleWasm(self::non_reproducible_wasm::DeployOpts), + #[strum_discriminants(strum( + message = "build-reproducible-wasm - Build requires [reproducible_build] section in Cargo.toml, and all changes committed and pushed to git (recommended for the production release)" + ))] + /// Build requires `[reproducible_build]` section in Cargo.toml, and all changes committed and pushed to git (recommended for the production release) + BuildReproducibleWasm(self::reproducible_wasm::DeployOpts), } } -impl interactive_clap::FromCli for Contract { - type FromCliContext = near_cli_rs::GlobalContext; - type FromCliError = color_eyre::eyre::Error; - fn from_cli( - optional_clap_variant: Option<::CliVariant>, - context: Self::FromCliContext, - ) -> interactive_clap::ResultFromCli< - ::CliVariant, - Self::FromCliError, - > - where - Self: Sized + interactive_clap::ToCli, - { - let mut clap_variant = optional_clap_variant.unwrap_or_default(); - - let build_command_args = - if let Some(cli_build_command_args) = &clap_variant.build_command_args { - build_command::BuildCommand::from(cli_build_command_args.clone()) - } else { - build_command::BuildCommand::default() - }; - - if clap_variant.contract_account_id.is_none() { - clap_variant.contract_account_id = match Self::input_contract_account_id(&context) { - Ok(Some(contract_account_id)) => Some(contract_account_id), - Ok(None) => return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)), - Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), - }; - } - let contract_account_id = clap_variant - .contract_account_id - .clone() - .expect("Unexpected error"); - - let skip_git_remote_check = clap_variant.skip_git_remote_check; - - let new_context_scope = InteractiveClapContextScopeForContract { - build_command_args, - contract_account_id, - skip_git_remote_check, - }; - - let output_context = - match ContractContext::from_previous_context(context, &new_context_scope) { - Ok(new_context) => new_context, - Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err), - }; - - match InitializeMode::from_cli(clap_variant.initialize.take(), output_context.into()) { - interactive_clap::ResultFromCli::Ok(initialize) => { - clap_variant.initialize = Some(initialize); - interactive_clap::ResultFromCli::Ok(clap_variant) - } - interactive_clap::ResultFromCli::Cancel(optional_initialize) => { - clap_variant.initialize = optional_initialize; - interactive_clap::ResultFromCli::Cancel(Some(clap_variant)) - } - interactive_clap::ResultFromCli::Back => interactive_clap::ResultFromCli::Back, - interactive_clap::ResultFromCli::Err(optional_initialize, err) => { - clap_variant.initialize = optional_initialize; - interactive_clap::ResultFromCli::Err(Some(clap_variant), err) - } - } - } -} - -impl Contract { - pub fn input_contract_account_id( - context: &near_cli_rs::GlobalContext, - ) -> color_eyre::eyre::Result> { - near_cli_rs::common::input_signer_account_id_from_used_account_list( - &context.config.credentials_home_dir, - "What is the contract account ID?", - ) - } +#[derive(Debug, Clone, interactive_clap::InteractiveClap)] +#[interactive_clap(context = near_cli_rs::GlobalContext)] +pub struct Command { + #[interactive_clap(subcommand)] + actions: actions::Actions, } diff --git a/cargo-near/src/commands/mod.rs b/cargo-near/src/commands/mod.rs index 37788456..b5c8a081 100644 --- a/cargo-near/src/commands/mod.rs +++ b/cargo-near/src/commands/mod.rs @@ -1,7 +1,7 @@ use strum::{EnumDiscriminants, EnumIter, EnumMessage}; -pub mod abi_command; -pub mod build_command; +pub mod abi; +pub mod build; pub mod create_dev_account; pub mod deploy; pub mod new; @@ -19,15 +19,15 @@ pub enum NearCommand { /// Initializes a new project to create a contract New(self::new::New), #[strum_discriminants(strum( - message = "build - Build a NEAR contract with embed ABI (opt out by passing `--no-embed-abi`)" + message = "build - Build a NEAR contract with embedded ABI" ))] - /// Build a NEAR contract with embed ABI (opt out by passing `--no-embed-abi`) - Build(self::build_command::BuildCommand), + /// Build a NEAR contract with embedded ABI + Build(self::build::Command), #[strum_discriminants(strum( message = "abi - Generates ABI for the contract" ))] /// Generates ABI for the contract - Abi(self::abi_command::AbiCommand), + Abi(self::abi::Command), #[strum_discriminants(strum( message = "create-dev-account - Create a development account using a faucet service sponsor to receive some NEAR tokens (testnet only). To create an account on a different network, use NEAR CLI [https://near.cli.rs]" @@ -37,5 +37,5 @@ pub enum NearCommand { CreateDevAccount(self::create_dev_account::CreateAccount), #[strum_discriminants(strum(message = "deploy - Add a new contract code"))] /// Add a new contract code - Deploy(self::deploy::Contract), + Deploy(self::deploy::Command), } diff --git a/cargo-near/src/commands/new/mod.rs b/cargo-near/src/commands/new/mod.rs index 541b1385..8ceb3943 100644 --- a/cargo-near/src/commands/new/mod.rs +++ b/cargo-near/src/commands/new/mod.rs @@ -210,7 +210,7 @@ const NEW_PROJECT_FILES: &[NewProjectFile] = &[ }, NewProjectFile { file_path: "Cargo.toml", - content: include_str!("new-project-template/Cargo.toml.template"), + content: include_str!("new-project-template/Cargo.template.toml"), }, NewProjectFile { file_path: "README.md", diff --git a/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-production.yml b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-production.yml index f892d532..c3f31aa2 100644 --- a/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-production.yml +++ b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-production.yml @@ -18,7 +18,7 @@ jobs: run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/download/cargo-near-vcargo-near-new-ci-tool-version-self/cargo-near-installer.sh | sh - name: Deploy to production run: | - cargo near deploy "${{ vars.NEAR_CONTRACT_PRODUCTION_ACCOUNT_ID }}" \ + cargo near deploy build-reproducible-wasm "${{ vars.NEAR_CONTRACT_PRODUCTION_ACCOUNT_ID }}" \ without-init-call \ network-config "${{ vars.NEAR_CONTRACT_PRODUCTION_NETWORK }}" \ sign-with-plaintext-private-key \ diff --git a/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-staging.yml b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-staging.yml index 0f75164a..62c781bf 100644 --- a/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-staging.yml +++ b/cargo-near/src/commands/new/new-project-template/.github/workflows/deploy-staging.yml @@ -41,7 +41,7 @@ jobs: # # WASM reproducibility check akin to SourceScan won't be available for staging contracts, deployed from PRs run: | - cargo near deploy --skip-git-remote-check "${{ env.NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID }}" \ + cargo near deploy build-reproducible-wasm --skip-git-remote-check "${{ env.NEAR_CONTRACT_PR_STAGING_ACCOUNT_ID }}" \ without-init-call \ network-config "${{ vars.NEAR_CONTRACT_STAGING_NETWORK }}" \ sign-with-plaintext-private-key \ diff --git a/cargo-near/src/commands/new/new-project-template/Cargo.toml.template b/cargo-near/src/commands/new/new-project-template/Cargo.template.toml similarity index 100% rename from cargo-near/src/commands/new/new-project-template/Cargo.toml.template rename to cargo-near/src/commands/new/new-project-template/Cargo.template.toml diff --git a/cargo-near/src/commands/new/new-project-template/README.md b/cargo-near/src/commands/new/new-project-template/README.md index 3843c83f..82648b96 100644 --- a/cargo-near/src/commands/new/new-project-template/README.md +++ b/cargo-near/src/commands/new/new-project-template/README.md @@ -22,7 +22,7 @@ Deployment is automated with GitHub Actions CI/CD pipeline. To deploy manually, install [`cargo-near`](https://github.com/near/cargo-near) and run: ```bash -cargo near deploy +cargo near deploy build-reproducible-wasm ``` ## Useful Links diff --git a/cargo-near/src/lib.rs b/cargo-near/src/lib.rs index 17926838..5cf562aa 100644 --- a/cargo-near/src/lib.rs +++ b/cargo-near/src/lib.rs @@ -23,7 +23,7 @@ pub struct Cmd { /// Near pub enum Opts { #[strum_discriminants(strum(message = "near"))] - /// Which cargo extension do you want to use? + /// Which subcommand of `near` extension do you want to use? Near(NearArgs), } diff --git a/cargo-near/src/main.rs b/cargo-near/src/main.rs index 9c0579cc..e82d29a2 100644 --- a/cargo-near/src/main.rs +++ b/cargo-near/src/main.rs @@ -6,9 +6,33 @@ use interactive_clap::ToCliArgs; pub use near_cli_rs::CliResult; -use cargo_near::{setup_tracing, CliOpts, Cmd, Opts}; +use cargo_near::{ + commands::build::actions::non_reproducible_wasm as build_non_reproducible_wasm, setup_tracing, + CliOpts, Cmd, Opts, +}; + +/// this part of cli setup doesn't depend on command arguments in any way +fn pre_setup() -> CliResult { + match env::var("NO_COLOR") { + Ok(v) if v != "0" => colored::control::set_override(false), + _ => colored::control::set_override(std::io::stderr().is_terminal()), + } + + #[cfg(not(debug_assertions))] + let display_env_section = false; + #[cfg(debug_assertions)] + let display_env_section = true; + color_eyre::config::HookBuilder::default() + .display_env_section(display_env_section) + .install()?; + Ok(()) +} fn main() -> CliResult { + pre_setup()?; + + build_non_reproducible_wasm::rule::enforce_this_program_args()?; + let cli_cmd = match Cmd::try_parse() { Ok(cli) => cli, Err(error) => error.exit(), @@ -32,19 +56,6 @@ fn main() -> CliResult { setup_tracing(env::var("RUST_LOG").is_ok(), cli_near_args.teach_me)?; - match env::var("NO_COLOR") { - Ok(v) if v != "0" => colored::control::set_override(false), - _ => colored::control::set_override(std::io::stderr().is_terminal()), - } - - #[cfg(not(debug_assertions))] - let display_env_section = false; - #[cfg(debug_assertions)] - let display_env_section = true; - color_eyre::config::HookBuilder::default() - .display_env_section(display_env_section) - .install()?; - let console_command_path = if env::var("CARGO_HOME").is_ok() { "cargo".to_string() } else if let Ok(value) = env::var("CARGO") { diff --git a/docs/image_and_digest_pinpoint.png b/docs/image_and_digest_pinpoint.png new file mode 100644 index 00000000..bd620565 Binary files /dev/null and b/docs/image_and_digest_pinpoint.png differ diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index f96694c8..f2af844f 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -6,11 +6,13 @@ publish = false [dependencies] const_format = "0.2" +color-eyre = "0.6" cargo-near-build = { version = "0.3.2", path = "../cargo-near-build" } cargo-near = { path = "../cargo-near" } colored = "2.0" tracing = "0.1.40" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +prettyplease = "0.2" +syn = "2" [dev-dependencies] borsh = { version = "1.0.0", features = ["derive", "unstable__schema"] } diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index d3ab5d34..4b65405b 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use cargo_near_build::camino; /// NOTE: `near-sdk` version, published on crates.io @@ -41,81 +43,126 @@ pub fn common_root_for_test_projects_build() -> camino::Utf8PathBuf { workspace_dir } -#[macro_export] -macro_rules! invoke_cargo_near { - ($(Cargo: $cargo_path:expr;)? $(Vars: $cargo_vars:expr;)? Opts: $cli_opts:expr; Code: $($code:tt)*) => {{ - let workspace_dir = $crate::common_root_for_test_projects_build(); - let crate_dir = workspace_dir.join(function_name!()); - let src_dir = crate_dir.join("src"); - std::fs::create_dir_all(&src_dir)?; - - let mut cargo_toml = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/_Cargo.toml")).to_string(); - $(cargo_toml = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), $cargo_path)).to_string())?; - let mut cargo_vars = std::collections::HashMap::new(); - $(cargo_vars = $cargo_vars)?; - cargo_vars.insert("sdk-cratesio-version", $crate::from_crates_io::SDK_VERSION); - cargo_vars.insert("sdk-cratesio-version-toml", $crate::from_crates_io::SDK_VERSION_TOML); - cargo_vars.insert("sdk-git-version", $crate::from_git::SDK_VERSION); - cargo_vars.insert("sdk-git-short-version-toml", $crate::from_git::SDK_SHORT_VERSION_TOML); - cargo_vars.insert("sdk-git-version-toml", $crate::from_git::SDK_VERSION_TOML); - cargo_vars.insert("sdk-git-version-toml-table", $crate::from_git::SDK_VERSION_TOML_TABLE); - cargo_vars.insert("name", function_name!()); - for (k, v) in cargo_vars { - cargo_toml = cargo_toml.replace(&format!("::{}::", k), v); +pub fn invoke_cargo_near( + function_name: &str, + cargo_path: Option<&str>, + mut cargo_vars: HashMap<&str, String>, + lib_rs_file: syn::File, + cli_opts: String, +) -> color_eyre::eyre::Result { + let workspace_dir = crate::common_root_for_test_projects_build(); + let crate_dir = workspace_dir.join(function_name); + let src_dir = crate_dir.join("src"); + std::fs::create_dir_all(&src_dir)?; + + let mut cargo_toml = match cargo_path { + Some(cargo_path) => { + let file = [env!("CARGO_MANIFEST_DIR"), cargo_path].concat(); + String::from_utf8(std::fs::read(&file)?)? } - let cargo_path = crate_dir.join("Cargo.toml"); - std::fs::write(&cargo_path, cargo_toml)?; + None => include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/_Cargo.toml" + )) + .to_string(), + }; + cargo_vars.insert( + "sdk-cratesio-version", + crate::from_crates_io::SDK_VERSION.into(), + ); + cargo_vars.insert( + "sdk-cratesio-version-toml", + crate::from_crates_io::SDK_VERSION_TOML.into(), + ); + cargo_vars.insert("sdk-git-version", crate::from_git::SDK_VERSION.into()); + cargo_vars.insert( + "sdk-git-short-version-toml", + crate::from_git::SDK_SHORT_VERSION_TOML.into(), + ); + cargo_vars.insert( + "sdk-git-version-toml", + crate::from_git::SDK_VERSION_TOML.into(), + ); + cargo_vars.insert( + "sdk-git-version-toml-table", + crate::from_git::SDK_VERSION_TOML_TABLE.into(), + ); + cargo_vars.insert("name", function_name.into()); + for (k, v) in cargo_vars { + cargo_toml = cargo_toml.replace(&format!("::{}::", k), &v); + } + let cargo_path = crate_dir.join("Cargo.toml"); + std::fs::write(&cargo_path, cargo_toml)?; - let lib_rs_file = syn::parse_file("e::quote! { $($code)* }.to_string()).unwrap(); - let lib_rs = prettyplease::unparse(&lib_rs_file); - let lib_rs_path = src_dir.join("lib.rs"); - std::fs::write(lib_rs_path, lib_rs)?; - - let cargo_near::CliOpts::Near(cli_args) = cargo_near::Opts::try_parse_from($cli_opts.split(" "))?; - - let path: camino::Utf8PathBuf = match cli_args.cmd { - Some(cargo_near::commands::CliNearCommand::Abi(cmd)) => { - let args = cargo_near_build::abi::AbiOpts { - no_locked: cmd.no_locked, - no_doc: cmd.no_doc, - compact_abi: cmd.compact_abi, - out_dir: cmd.out_dir.map(Into::into), - manifest_path: Some(cargo_path.into()), - color: cmd.color.map(Into::into), - }; - tracing::debug!("AbiOpts: {:#?}", args); - let path = cargo_near_build::abi::build(args)?; - path - }, - Some(cargo_near::commands::CliNearCommand::Build(cmd)) => { - let args = { - let mut args = cargo_near::commands::build_command::BuildCommand::from(cmd) ; - args.manifest_path = Some(cargo_path.into()); - args - }; - tracing::debug!("BuildCommand: {:#?}", args); - let artifact = args.run(cargo_near_build::BuildContext::Build)?; - artifact.path + let lib_rs = prettyplease::unparse(&lib_rs_file); + let lib_rs_path = src_dir.join("lib.rs"); + std::fs::write(lib_rs_path, lib_rs)?; + + let cargo_near::CliOpts::Near(cli_args) = + cargo_near::Opts::try_parse_from(cli_opts.split(" "))?; + + let path: camino::Utf8PathBuf = match cli_args.cmd { + Some(cargo_near::commands::CliNearCommand::Abi(cmd)) => { + let args = cargo_near_build::abi::AbiOpts { + no_locked: !cmd.locked, + no_doc: cmd.no_doc, + compact_abi: cmd.compact_abi, + out_dir: cmd.out_dir.map(Into::into), + manifest_path: Some(cargo_path), + color: cmd.color.map(Into::into), + }; + tracing::debug!("AbiOpts: {:#?}", args); + cargo_near_build::abi::build(args)? + } + Some(cargo_near::commands::CliNearCommand::Build( + cargo_near::commands::build::CliCommand { + actions: + Some(cargo_near::commands::build::actions::CliActions::NonReproducibleWasm( + cli_build_otps, + )), }, - Some(_) => todo!(), - None => unreachable!(), - }; - path + )) => { + let build_opts = { + let mut build_opts = + cargo_near::commands::build::actions::non_reproducible_wasm::BuildOpts::from( + cli_build_otps, + ); + build_opts.manifest_path = Some(cargo_path.into()); + build_opts + }; + tracing::debug!("non_reproducible_wasm::BuildOpts: {:#?}", build_opts); - }}; + let artifact = + cargo_near::commands::build::actions::non_reproducible_wasm::run(build_opts)?; + artifact.path + } + Some(_) => todo!(), + None => unreachable!(), + }; + Ok(path) } #[macro_export] macro_rules! generate_abi_with { ($(Cargo: $cargo_path:expr;)? $(Vars: $cargo_vars:expr;)? $(Opts: $cli_opts:expr;)? Code: $($code:tt)*) => {{ - let opts = "cargo near abi --no-locked"; - $(let opts = format!("cargo near abi --no-locked {}", $cli_opts);)?; - let result_file = $crate::invoke_cargo_near! { - $(Cargo: $cargo_path;)? $(Vars: $cargo_vars;)? - Opts: &opts; - Code: - $($code)* - }; + let opts: String = "cargo near abi".into(); + $(let opts = format!("cargo near abi {}", $cli_opts);)?; + + let cargo_vars: std::collections::HashMap<&str, String> = std::collections::HashMap::new(); + $(let cargo_vars = $cargo_vars)?; + + let cargo_path: Option<&str> = None; + $(let cargo_path = Some($cargo_path))?; + + let lib_rs_file = syn::parse_file("e::quote! { $($code)* }.to_string()).unwrap(); + + let result_file = $crate::invoke_cargo_near( + function_name!(), + cargo_path, + cargo_vars, + lib_rs_file, + opts, + )?; let result_dir = result_file.as_std_path().parent().expect("has parent"); let abi_root: cargo_near_build::near_abi::AbiRoot = @@ -178,14 +225,24 @@ pub struct BuildResult { #[macro_export] macro_rules! build_with { ($(Cargo: $cargo_path:expr;)? $(Vars: $cargo_vars:expr;)? $(Opts: $cli_opts:expr;)? Code: $($code:tt)*) => {{ - let opts = "cargo near build --no-docker --no-locked"; - $(let opts = format!("cargo near build --no-docker --no-locked {}", $cli_opts);)?; - let result_file = $crate::invoke_cargo_near! { - $(Cargo: $cargo_path;)? $(Vars: $cargo_vars;)? - Opts: &opts; - Code: - $($code)* - }; + let opts: String = "cargo near build non-reproducible-wasm".into(); + $(let opts = format!("cargo near build non-reproducible-wasm {}", $cli_opts);)?; + + let cargo_vars: std::collections::HashMap<&str, String> = std::collections::HashMap::new(); + $(let cargo_vars = $cargo_vars)?; + + let cargo_path: Option<&str> = None; + $(let cargo_path = Some($cargo_path))?; + + let lib_rs_file = syn::parse_file("e::quote! { $($code)* }.to_string()).unwrap(); + + let result_file = $crate::invoke_cargo_near( + function_name!(), + cargo_path, + cargo_vars, + lib_rs_file, + opts, + )?; let result_dir = result_file.as_std_path().parent().expect("has parent"); let wasm_path = result_dir. diff --git a/integration-tests/tests/cargo/mod.rs b/integration-tests/tests/cargo/mod.rs index ce2e16bf..24358745 100644 --- a/integration-tests/tests/cargo/mod.rs +++ b/integration-tests/tests/cargo/mod.rs @@ -26,7 +26,7 @@ fn test_dependency_local_path() -> cargo_near::CliResult { // near-sdk = { path = "::path::", features = ["abi"] } let abi_root = generate_abi_fn_with! { Cargo: "/templates/sdk-dependency/_Cargo_local_path.toml"; - Vars: HashMap::from([("path", near_sdk_dep_path.to_str().unwrap())]); + Vars: HashMap::from([("path", near_sdk_dep_path.to_str().unwrap().to_owned())]); Code: pub fn foo(&self, a: bool, b: u32) {} }; @@ -47,7 +47,7 @@ fn test_dependency_local_path_with_version() -> cargo_near::CliResult { let abi_root = generate_abi_fn_with! { Cargo: "/templates/sdk-dependency/_Cargo_local_path_with_version.toml"; - Vars: HashMap::from([("path", near_sdk_dep_path.to_str().unwrap())]); + Vars: HashMap::from([("path", near_sdk_dep_path.to_str().unwrap().to_owned())]); Code: pub fn foo(&self, a: bool, b: u32) {} };