From 4fbb6748454b63e16fe7f240e4fd95c3d24a61d4 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 1 Nov 2024 14:18:13 +0100 Subject: [PATCH 01/35] fix: add chain to specify the chain specification --- crates/pop-cli/src/commands/build/spec.rs | 17 ++++++++++++++++- crates/pop-cli/tests/parachain.rs | 5 ++++- crates/pop-parachains/README.md | 4 ++-- crates/pop-parachains/src/build.rs | 6 ++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 11c94ff0..f191774d 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -146,6 +146,10 @@ pub struct BuildSpecCommand { /// Type of the chain [default: development]. #[arg(short = 't', long = "type", value_enum)] pub(crate) chain_type: Option, + /// Specify the chain specification. It can be one of the predefined ones (dev, local) or a + /// custom one. + #[arg(short = 'c', long = "chain", default_value = "dev")] + pub(crate) chain: Option, /// Relay chain this parachain will connect to [default: paseo-local]. #[arg(long, value_enum)] pub(crate) relay: Option, @@ -237,7 +241,12 @@ impl BuildSpecCommand { // Generate plain spec. spinner.set_message("Generating plain chain specification..."); let mut generated_files = vec![]; - generate_plain_chain_spec(&binary_path, &plain_chain_spec, self.default_bootnode)?; + generate_plain_chain_spec( + &binary_path, + &plain_chain_spec, + self.default_bootnode, + self.chain.as_deref(), + )?; generated_files.push(format!( "Plain text chain specification file generated at: {}", plain_chain_spec.display() @@ -352,6 +361,11 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result anyhow::Result Result<()> { let temp_parachain_dir = temp_dir.join("test_parachain"); // pop build spec --output ./target/pop/test-spec.json --id 2222 --type development --relay - // paseo-local --protocol-id pop-protocol" + // paseo-local --protocol-id pop-protocol" --chain local Command::cargo_bin("pop") .unwrap() .current_dir(&temp_parachain_dir) @@ -61,6 +61,8 @@ async fn parachain_lifecycle() -> Result<()> { "2222", "--type", "development", + "--chain", + "local", "--relay", "paseo-local", "--genesis-state", @@ -86,6 +88,7 @@ async fn parachain_lifecycle() -> Result<()> { assert!(content.contains("\"tokenSymbol\": \"POP\"")); assert!(content.contains("\"relay_chain\": \"paseo-local\"")); assert!(content.contains("\"protocolId\": \"pop-protocol\"")); + assert!(content.contains("\"id\": \"local_testnet\"")); // pop up parachain -p "./test_parachain" let mut cmd = Cmd::new(cargo_bin("pop")) diff --git a/crates/pop-parachains/README.md b/crates/pop-parachains/README.md index 38fa24c1..aeda3c10 100644 --- a/crates/pop-parachains/README.md +++ b/crates/pop-parachains/README.md @@ -46,7 +46,7 @@ let package = None; // The optional package to be built. let binary_path = build_parachain(&path, package, &Profile::Release, None).unwrap();; // Generate a plain chain specification file of a parachain let plain_chain_spec_path = path.join("plain-parachain-chainspec.json"); -generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true); +generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true, Some("dev")); // Customize your chain specification let mut chain_spec = ChainSpec::from(&plain_chain_spec_path).unwrap(); chain_spec.replace_para_id(2002); @@ -70,7 +70,7 @@ let package = None; // The optional package to be built. let binary_path = build_parachain(&path, package, &Profile::Release, None).unwrap();; // Generate a plain chain specification file of a parachain let plain_chain_spec_path = path.join("plain-parachain-chainspec.json"); -generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true); +generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true, Some("dev")); // Generate a raw chain specification file of a parachain let chain_spec = generate_raw_chain_spec(&binary_path, &plain_chain_spec_path, "raw-parachain-chainspec.json").unwrap(); // Export the WebAssembly runtime for the parachain. diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index 3c25b2a0..496fdffa 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -79,9 +79,14 @@ pub fn generate_plain_chain_spec( binary_path: &Path, plain_chain_spec: &Path, default_bootnode: bool, + chain: Option<&str>, ) -> Result<(), Error> { check_command_exists(binary_path, "build-spec")?; let mut args = vec!["build-spec"]; + if let Some(chain_spec_id) = chain { + args.push("--chain"); + args.push(chain_spec_id); + } if !default_bootnode { args.push("--disable-default-bootnode"); } @@ -458,6 +463,7 @@ default_command = "pop-node" &binary_path, &temp_dir.path().join("plain-parachain-chainspec.json"), true, + Some("local"), )?; assert!(plain_chain_spec.exists()); { From eaf1f940bef512b628af605170e6e0af5570b494 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Fri, 1 Nov 2024 15:42:32 +0100 Subject: [PATCH 02/35] fix: default_bootnode by default to true --- crates/pop-cli/src/commands/build/spec.rs | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index f191774d..65ea3f7f 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -141,7 +141,7 @@ pub struct BuildSpecCommand { #[arg(short = 'i', long = "id")] pub(crate) id: Option, /// Whether to keep localhost as a bootnode. - #[clap(long, default_value = "true")] + #[clap(long, default_value = "false")] pub(crate) default_bootnode: bool, /// Type of the chain [default: development]. #[arg(short = 't', long = "type", value_enum)] @@ -379,13 +379,13 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result + ChainType::Live => { for relay in RelayChain::VARIANTS { if !matches!( relay, - RelayChain::Westend | - RelayChain::Paseo | RelayChain::Kusama | - RelayChain::Polkadot + RelayChain::Westend + | RelayChain::Paseo | RelayChain::Kusama + | RelayChain::Polkadot ) { continue; } else { @@ -395,14 +395,15 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result + } + }, + _ => { for relay in RelayChain::VARIANTS { if matches!( relay, - RelayChain::Westend | - RelayChain::Paseo | RelayChain::Kusama | - RelayChain::Polkadot + RelayChain::Westend + | RelayChain::Paseo | RelayChain::Kusama + | RelayChain::Polkadot ) { continue; } else { @@ -412,7 +413,8 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result Date: Fri, 1 Nov 2024 15:43:16 +0100 Subject: [PATCH 03/35] chore: fmt --- crates/pop-cli/src/commands/build/spec.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 65ea3f7f..7feadbb5 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -379,13 +379,13 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result { + ChainType::Live => for relay in RelayChain::VARIANTS { if !matches!( relay, - RelayChain::Westend - | RelayChain::Paseo | RelayChain::Kusama - | RelayChain::Polkadot + RelayChain::Westend | + RelayChain::Paseo | RelayChain::Kusama | + RelayChain::Polkadot ) { continue; } else { @@ -395,15 +395,14 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result { + }, + _ => for relay in RelayChain::VARIANTS { if matches!( relay, - RelayChain::Westend - | RelayChain::Paseo | RelayChain::Kusama - | RelayChain::Polkadot + RelayChain::Westend | + RelayChain::Paseo | RelayChain::Kusama | + RelayChain::Polkadot ) { continue; } else { @@ -413,8 +412,7 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result Date: Tue, 5 Nov 2024 10:20:12 +0100 Subject: [PATCH 04/35] chore: deprecate flag --release in build specs --- crates/pop-cli/src/commands/build/spec.rs | 20 ++++++-------------- crates/pop-parachains/src/build.rs | 4 +++- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 7feadbb5..8d2fd86b 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -134,7 +134,7 @@ pub struct BuildSpecCommand { /// [default: ./chain-spec.json]. #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, - /// For production, always build in release mode to exclude debug features. + /// [DEPRECATED] For production, always build in release mode to exclude debug features. #[clap(short = 'r', long, default_value = "true")] pub(crate) release: bool, /// Parachain ID to be used when generating the chain spec files. @@ -199,7 +199,9 @@ impl BuildSpecCommand { let para_id = self.id.unwrap_or(DEFAULT_PARA_ID); // Notify user in case we need to build the parachain project. if !self.release { - cli.warning("NOTE: this command defaults to DEBUG builds for development chain types. Please use `--release` (or simply `-r` for a release build...)")?; + cli.warning( + "NOTE: this flag is deprecated, this command defaults to a release build builds.", + )?; #[cfg(not(test))] sleep(Duration::from_secs(3)) } @@ -227,7 +229,7 @@ impl BuildSpecCommand { plain_chain_spec.set_extension("json"); // Locate binary, if it doesn't exist trigger build. - let mode: Profile = self.release.into(); + let mode: Profile = Profile::Release; let cwd = current_dir().unwrap_or(PathBuf::from("./")); let binary_path = match binary_path(&mode.target_directory(&cwd), &cwd.join("node")) { Ok(binary_path) => binary_path, @@ -444,19 +446,9 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result Date: Tue, 5 Nov 2024 21:38:42 +0100 Subject: [PATCH 05/35] fix: clean output (#334) --- crates/pop-cli/src/commands/build/spec.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 8d2fd86b..0c19d1a2 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -206,8 +206,7 @@ impl BuildSpecCommand { sleep(Duration::from_secs(3)) } - let spinner = cliclack::spinner(); - spinner.start("Generating chain specification..."); + cli.info("Generating chain specification...")?; // Create output path if needed let mut output_path = self.output_file.unwrap_or_else(|| PathBuf::from("./")); @@ -241,7 +240,7 @@ impl BuildSpecCommand { }; // Generate plain spec. - spinner.set_message("Generating plain chain specification..."); + cli.info("Generating plain chain specification...")?; let mut generated_files = vec![]; generate_plain_chain_spec( &binary_path, @@ -268,7 +267,7 @@ impl BuildSpecCommand { chain_spec.to_file(&plain_chain_spec)?; // Generate raw spec. - spinner.set_message("Generating raw chain specification..."); + cli.info("Generating raw chain specification...")?; let spec_name = plain_chain_spec .file_name() .and_then(|s| s.to_str()) @@ -284,7 +283,7 @@ impl BuildSpecCommand { // Generate genesis artifacts. if self.genesis_code { - spinner.set_message("Generating genesis code..."); + cli.info("Generating genesis code...")?; let wasm_file_name = format!("para-{}.wasm", para_id); let wasm_file = export_wasm_file(&binary_path, &raw_chain_spec, &wasm_file_name)?; generated_files @@ -292,7 +291,7 @@ impl BuildSpecCommand { } if self.genesis_state { - spinner.set_message("Generating genesis state..."); + cli.info("Generating genesis state...")?; let genesis_file_name = format!("para-{}-genesis-state", para_id); let genesis_state_file = generate_genesis_state_file(&binary_path, &raw_chain_spec, &genesis_file_name)?; From a51df3a60cad87c4ea54fb1e5bfde52e4a46e9b9 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Tue, 5 Nov 2024 21:47:27 +0100 Subject: [PATCH 06/35] fix: undo deprecation of --release flag --- crates/pop-cli/src/commands/build/spec.rs | 41 +++++++++++++++-------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 0c19d1a2..81960de9 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -134,7 +134,8 @@ pub struct BuildSpecCommand { /// [default: ./chain-spec.json]. #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, - /// [DEPRECATED] For production, always build in release mode to exclude debug features. + /// This command builds the node if it has not been built already. + /// For production, always build in release mode to exclude debug features. #[clap(short = 'r', long, default_value = "true")] pub(crate) release: bool, /// Parachain ID to be used when generating the chain spec files. @@ -200,7 +201,7 @@ impl BuildSpecCommand { // Notify user in case we need to build the parachain project. if !self.release { cli.warning( - "NOTE: this flag is deprecated, this command defaults to a release build builds.", + "NOTE: this command defaults to DEBUG builds for development chain types. Please use `--release` (or simply `-r` for a release build...)", )?; #[cfg(not(test))] sleep(Duration::from_secs(3)) @@ -228,7 +229,7 @@ impl BuildSpecCommand { plain_chain_spec.set_extension("json"); // Locate binary, if it doesn't exist trigger build. - let mode: Profile = Profile::Release; + let mode: Profile = self.release.into(); let cwd = current_dir().unwrap_or(PathBuf::from("./")); let binary_path = match binary_path(&mode.target_directory(&cwd), &cwd.join("node")) { Ok(binary_path) => binary_path, @@ -380,13 +381,13 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result + ChainType::Live => { for relay in RelayChain::VARIANTS { if !matches!( relay, - RelayChain::Westend | - RelayChain::Paseo | RelayChain::Kusama | - RelayChain::Polkadot + RelayChain::Westend + | RelayChain::Paseo | RelayChain::Kusama + | RelayChain::Polkadot ) { continue; } else { @@ -396,14 +397,15 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result + } + }, + _ => { for relay in RelayChain::VARIANTS { if matches!( relay, - RelayChain::Westend | - RelayChain::Paseo | RelayChain::Kusama | - RelayChain::Polkadot + RelayChain::Westend + | RelayChain::Paseo | RelayChain::Kusama + | RelayChain::Polkadot ) { continue; } else { @@ -413,7 +415,8 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result anyhow::Result Date: Tue, 5 Nov 2024 21:56:30 +0100 Subject: [PATCH 07/35] refactor: small fix --- crates/pop-cli/src/commands/build/spec.rs | 24 +++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 81960de9..a9313e9d 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -142,7 +142,7 @@ pub struct BuildSpecCommand { #[arg(short = 'i', long = "id")] pub(crate) id: Option, /// Whether to keep localhost as a bootnode. - #[clap(long, default_value = "false")] + #[clap(long)] pub(crate) default_bootnode: bool, /// Type of the chain [default: development]. #[arg(short = 't', long = "type", value_enum)] @@ -381,13 +381,13 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result { + ChainType::Live => for relay in RelayChain::VARIANTS { if !matches!( relay, - RelayChain::Westend - | RelayChain::Paseo | RelayChain::Kusama - | RelayChain::Polkadot + RelayChain::Westend | + RelayChain::Paseo | RelayChain::Kusama | + RelayChain::Polkadot ) { continue; } else { @@ -397,15 +397,14 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result { + }, + _ => for relay in RelayChain::VARIANTS { if matches!( relay, - RelayChain::Westend - | RelayChain::Paseo | RelayChain::Kusama - | RelayChain::Polkadot + RelayChain::Westend | + RelayChain::Paseo | RelayChain::Kusama | + RelayChain::Polkadot ) { continue; } else { @@ -415,8 +414,7 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result Date: Fri, 8 Nov 2024 10:07:32 +0100 Subject: [PATCH 08/35] style: remove extra space --- crates/pop-cli/src/commands/build/spec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index a9313e9d..71817470 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -364,7 +364,7 @@ async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result Date: Fri, 8 Nov 2024 12:53:15 +0100 Subject: [PATCH 09/35] fix(spec): better handling of spinner --- crates/pop-cli/src/commands/build/spec.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 71817470..e9294c0c 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -6,7 +6,7 @@ use crate::{ style::style, }; use clap::{Args, ValueEnum}; -use cliclack::{confirm, input}; +use cliclack::{confirm, input, multi_progress, spinner}; use pop_common::Profile; use pop_parachains::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, @@ -206,8 +206,7 @@ impl BuildSpecCommand { #[cfg(not(test))] sleep(Duration::from_secs(3)) } - - cli.info("Generating chain specification...")?; + let multi = multi_progress("Generating chain specification..."); // Create output path if needed let mut output_path = self.output_file.unwrap_or_else(|| PathBuf::from("./")); @@ -234,6 +233,7 @@ impl BuildSpecCommand { let binary_path = match binary_path(&mode.target_directory(&cwd), &cwd.join("node")) { Ok(binary_path) => binary_path, _ => { + multi.stop(); cli.info("Node was not found. The project will be built locally.".to_string())?; cli.warning("NOTE: this may take some time...")?; build_parachain(&cwd, None, &mode, None)? @@ -241,7 +241,8 @@ impl BuildSpecCommand { }; // Generate plain spec. - cli.info("Generating plain chain specification...")?; + let spinner = multi.add(spinner()); + spinner.start("Generating plain chain specification..."); let mut generated_files = vec![]; generate_plain_chain_spec( &binary_path, @@ -268,7 +269,7 @@ impl BuildSpecCommand { chain_spec.to_file(&plain_chain_spec)?; // Generate raw spec. - cli.info("Generating raw chain specification...")?; + spinner.set_message("Generating raw chain specification..."); let spec_name = plain_chain_spec .file_name() .and_then(|s| s.to_str()) @@ -284,7 +285,7 @@ impl BuildSpecCommand { // Generate genesis artifacts. if self.genesis_code { - cli.info("Generating genesis code...")?; + spinner.set_message("Generating genesis code..."); let wasm_file_name = format!("para-{}.wasm", para_id); let wasm_file = export_wasm_file(&binary_path, &raw_chain_spec, &wasm_file_name)?; generated_files @@ -292,7 +293,7 @@ impl BuildSpecCommand { } if self.genesis_state { - cli.info("Generating genesis state...")?; + spinner.set_message("Generating genesis state..."); let genesis_file_name = format!("para-{}-genesis-state", para_id); let genesis_state_file = generate_genesis_state_file(&binary_path, &raw_chain_spec, &genesis_file_name)?; @@ -300,7 +301,9 @@ impl BuildSpecCommand { .push(format!("Genesis State file exported at: {}", genesis_state_file.display())); } - cli.intro("Building your chain spec".to_string())?; + spinner.stop("Done."); + multi.stop(); + let generated_files: Vec<_> = generated_files .iter() .map(|s| style(format!("{} {s}", console::Emoji("●", ">"))).dim().to_string()) From c7275b3343569322cb22052eb02a8fb9a5c43272 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 11:00:21 +0100 Subject: [PATCH 10/35] style: use spinner instead of multispinner --- crates/pop-cli/src/commands/build/spec.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index e9294c0c..f5c08b0d 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -6,7 +6,7 @@ use crate::{ style::style, }; use clap::{Args, ValueEnum}; -use cliclack::{confirm, input, multi_progress, spinner}; +use cliclack::{confirm, input, spinner}; use pop_common::Profile; use pop_parachains::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, @@ -206,7 +206,8 @@ impl BuildSpecCommand { #[cfg(not(test))] sleep(Duration::from_secs(3)) } - let multi = multi_progress("Generating chain specification..."); + let spinner = spinner(); + spinner.start("Generating chain specification..."); // Create output path if needed let mut output_path = self.output_file.unwrap_or_else(|| PathBuf::from("./")); @@ -233,7 +234,7 @@ impl BuildSpecCommand { let binary_path = match binary_path(&mode.target_directory(&cwd), &cwd.join("node")) { Ok(binary_path) => binary_path, _ => { - multi.stop(); + spinner.clear(); cli.info("Node was not found. The project will be built locally.".to_string())?; cli.warning("NOTE: this may take some time...")?; build_parachain(&cwd, None, &mode, None)? @@ -241,8 +242,7 @@ impl BuildSpecCommand { }; // Generate plain spec. - let spinner = multi.add(spinner()); - spinner.start("Generating plain chain specification..."); + spinner.set_message("Generating plain chain specification..."); let mut generated_files = vec![]; generate_plain_chain_spec( &binary_path, @@ -301,8 +301,7 @@ impl BuildSpecCommand { .push(format!("Genesis State file exported at: {}", genesis_state_file.display())); } - spinner.stop("Done."); - multi.stop(); + spinner.stop("Chain specification built successfully."); let generated_files: Vec<_> = generated_files .iter() From f532a4b05741baa040e49fbe9a6400e220014858 Mon Sep 17 00:00:00 2001 From: AlexD10S Date: Wed, 20 Nov 2024 11:38:56 +0100 Subject: [PATCH 11/35] docs: help message to include build --- crates/pop-cli/src/commands/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/mod.rs b/crates/pop-cli/src/commands/mod.rs index 34c2f10b..aa385f28 100644 --- a/crates/pop-cli/src/commands/mod.rs +++ b/crates/pop-cli/src/commands/mod.rs @@ -46,9 +46,9 @@ pub(crate) enum Command { /// Help message for the build command. fn about_build() -> &'static str { #[cfg(all(feature = "parachain", feature = "contract"))] - return "Build a parachain, smart contract or Rust package."; + return "Build a parachain, chain specification, smart contract or Rust package."; #[cfg(all(feature = "parachain", not(feature = "contract")))] - return "Build a parachain or Rust package."; + return "Build a parachain, chain specification or Rust package."; #[cfg(all(feature = "contract", not(feature = "parachain")))] return "Build a smart contract or Rust package."; } From 8ed07d242a367e428efb70a6654961e640db4142 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 26 Nov 2024 10:25:13 +0100 Subject: [PATCH 12/35] feat: reuse existing chain spec --- crates/pop-cli/src/commands/build/spec.rs | 542 ++++++++++++---------- crates/pop-parachains/src/build.rs | 21 +- 2 files changed, 306 insertions(+), 257 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index f5c08b0d..5d00815a 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -12,11 +12,7 @@ use pop_parachains::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -use std::{ - env::current_dir, - fs::create_dir_all, - path::{Path, PathBuf}, -}; +use std::{env::current_dir, fs::create_dir_all, path::PathBuf}; #[cfg(not(test))] use std::{thread::sleep, time::Duration}; use strum::{EnumMessage, VariantArray}; @@ -127,157 +123,316 @@ pub(crate) enum RelayChain { PolkadotLocal, } -#[derive(Args)] +/// Command for generating a chain specification. +#[derive(Args, Clone)] pub struct BuildSpecCommand { /// File name for the resulting spec. If a path is given, /// the necessary directories will be created - /// [default: ./chain-spec.json]. #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, /// This command builds the node if it has not been built already. /// For production, always build in release mode to exclude debug features. - #[clap(short = 'r', long, default_value = "true")] + #[arg(short = 'r', long)] pub(crate) release: bool, /// Parachain ID to be used when generating the chain spec files. - #[arg(short = 'i', long = "id")] + #[arg(short = 'i', long)] pub(crate) id: Option, /// Whether to keep localhost as a bootnode. - #[clap(long)] + #[arg(long)] pub(crate) default_bootnode: bool, - /// Type of the chain [default: development]. + /// Type of the chain. #[arg(short = 't', long = "type", value_enum)] pub(crate) chain_type: Option, - /// Specify the chain specification. It can be one of the predefined ones (dev, local) or a - /// custom one. - #[arg(short = 'c', long = "chain", default_value = "dev")] + /// Specify the chain specification. It can be one of the predefined ones (e.g. dev, local or a + /// custom one) or the path to an existing chain spec. + #[arg(short = 'c', long = "chain")] pub(crate) chain: Option, - /// Relay chain this parachain will connect to [default: paseo-local]. + /// Relay chain this parachain will connect to. #[arg(long, value_enum)] pub(crate) relay: Option, /// Protocol-id to use in the specification. #[arg(long = "protocol-id")] pub(crate) protocol_id: Option, - /// Whether the genesis state file should be generated [default: true]. - #[clap(long = "genesis-state", default_value = "true")] + /// Whether the genesis state file should be generated. + #[arg(long = "genesis-state")] pub(crate) genesis_state: bool, - /// Whether the genesis code file should be generated [default: true]. - #[clap(long = "genesis-code", default_value = "true")] + /// Whether the genesis code file should be generated. + #[arg(long = "genesis-code")] pub(crate) genesis_code: bool, } impl BuildSpecCommand { - /// Executes the command. + /// Executes the build spec command. pub(crate) async fn execute(self) -> anyhow::Result<&'static str> { + let mut cli = Cli; // Checks for appchain project in `./`. if is_supported(None)? { - // If para id has been provided we can build the spec - // otherwise, we need to guide the user. - let _ = match self.id { - Some(_) => self.build(&mut Cli), - None => { - let config = guide_user_to_generate_spec(self).await?; - config.build(&mut Cli) - }, - }; - Ok("spec") + let build_spec = self.complete_build_spec(&mut cli).await?; + build_spec.build(&mut cli) } else { - Cli.intro("Building your chain spec")?; - Cli.outro_cancel( + cli.intro("Generate your chain spec")?; + cli.outro_cancel( "🚫 Can't build a specification for target. Maybe not a chain project ?", )?; Ok("spec") } } - /// Builds a parachain spec. - /// - /// # Arguments - /// * `cli` - The CLI implementation to be used. - fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { - cli.intro("Building your chain spec")?; + /// Completes chain specification requirements by prompting for missing inputs, validating + /// provided values, and preparing a BuildSpec for generating chain spec files. + async fn complete_build_spec( + self, + cli: &mut impl cli::traits::Cli, + ) -> anyhow::Result { + cli.intro("Generate your chain spec")?; - // Either a para id was already provided or user has been guided to provide one. - let para_id = self.id.unwrap_or(DEFAULT_PARA_ID); - // Notify user in case we need to build the parachain project. - if !self.release { - cli.warning( - "NOTE: this command defaults to DEBUG builds for development chain types. Please use `--release` (or simply `-r` for a release build...)", - )?; - #[cfg(not(test))] - sleep(Duration::from_secs(3)) - } - let spinner = spinner(); - spinner.start("Generating chain specification..."); + let BuildSpecCommand { + output_file, + release, + id, + default_bootnode, + chain_type, + chain, + relay, + protocol_id, + genesis_state, + genesis_code, + } = self; - // Create output path if needed - let mut output_path = self.output_file.unwrap_or_else(|| PathBuf::from("./")); - let mut plain_chain_spec; - if output_path.is_dir() { - if !output_path.exists() { - // Generate the output path if needed - create_dir_all(&output_path)?; + // Prompt for chain specification. + let chain = match chain { + Some(chain) => chain, + _ => { + input("Specify the chain specification. It can be one of the predefined ones (e.g. dev, local or a custom one) or the path to an existing chain spec.") + .placeholder("dev") + .default_input("dev") + .interact()? + }, + }; + + // Check if the provided chain specification is a file. + let maybe_chain_spec_file = PathBuf::from(&chain); + let output_file = if maybe_chain_spec_file.exists() && maybe_chain_spec_file.is_file() { + if output_file.is_some() { + cli.warning("NOTE: If an existing chain spec file is provided it will be used for the output path.")?; } - plain_chain_spec = output_path.join(DEFAULT_SPEC_NAME); + // Set the provided chain specification file as output file. + maybe_chain_spec_file } else { - plain_chain_spec = output_path.clone(); - output_path.pop(); - if !output_path.exists() { - // Generate the output path if needed - create_dir_all(&output_path)?; + let output_file = match output_file { + Some(output) => output, + None => { + // Prompt for output file if not provided. + let default_output = format!("./{DEFAULT_SPEC_NAME}"); + input("Name of the plain chain spec file. If a path is given, the necessary directories will be created:") + .placeholder(&default_output) + .default_input(&default_output) + .interact()? + }, + }; + prepare_output_path(output_file)? + }; + // If chain specification file already exists, obtain values for defaults when prompting. + let chain_spec = ChainSpec::from(&output_file).ok(); + + // Prompt for para id if not provided. + let id = match id { + Some(id) => id, + None => { + let default_id = chain_spec + .as_ref() + .and_then(|cs| cs.get_parachain_id()) + .unwrap_or(DEFAULT_PARA_ID as u64) + .to_string(); + input("What parachain ID should be used?") + .placeholder(&default_id) + .default_input(&default_id) + .interact()? + }, + }; + + // Prompt for chain type if not provided. + let chain_type = match chain_type { + Some(chain_type) => chain_type, + None => { + let mut prompt = cliclack::select("Choose the chain type: ".to_string()); + let default = chain_spec + .as_ref() + .and_then(|cs| cs.get_chain_type()) + .and_then(|r| ChainType::from_str(r, true).ok()); + if let Some(chain_type) = default.as_ref() { + prompt = prompt.initial_value(chain_type); + } + for (i, chain_type) in ChainType::VARIANTS.iter().enumerate() { + if default.is_none() && i == 0 { + prompt = prompt.initial_value(chain_type); + } + prompt = prompt.item( + chain_type, + chain_type.get_message().unwrap_or(chain_type.as_ref()), + chain_type.get_detailed_message().unwrap_or_default(), + ); + } + prompt.interact()?.clone() + }, + }; + + // Prompt for relay chain if not provided. + let relay = match relay { + Some(relay) => relay, + None => { + let mut prompt = cliclack::select( + "Choose the relay chain your chain will be connecting to: ".to_string(), + ); + let default = chain_spec + .as_ref() + .and_then(|cs| cs.get_relay_chain()) + .and_then(|r| RelayChain::from_str(r, true).ok()); + if let Some(relay) = default.as_ref() { + prompt = prompt.initial_value(relay); + } + for relay in RelayChain::VARIANTS { + prompt = prompt.item( + relay, + relay.get_message().unwrap_or(relay.as_ref()), + relay.get_detailed_message().unwrap_or_default(), + ); + } + prompt.interact()?.clone() + }, + }; + + // Prompt for default bootnode if not provided and chain type is Local or Live. + let default_bootnode = if !default_bootnode { + match chain_type { + ChainType::Development => true, + _ => confirm("Would you like to use local host as a bootnode ?".to_string()) + .interact()?, } - } - plain_chain_spec.set_extension("json"); + } else { + true + }; - // Locate binary, if it doesn't exist trigger build. - let mode: Profile = self.release.into(); - let cwd = current_dir().unwrap_or(PathBuf::from("./")); - let binary_path = match binary_path(&mode.target_directory(&cwd), &cwd.join("node")) { - Ok(binary_path) => binary_path, - _ => { - spinner.clear(); - cli.info("Node was not found. The project will be built locally.".to_string())?; - cli.warning("NOTE: this may take some time...")?; - build_parachain(&cwd, None, &mode, None)? + // Prompt for protocol-id if not provided. + let protocol_id = match protocol_id { + Some(protocol_id) => protocol_id, + None => { + let default = chain_spec + .as_ref() + .and_then(|cs| cs.get_protocol_id()) + .unwrap_or(DEFAULT_PROTOCOL_ID) + .to_string(); + input("Enter the protocol ID that will identify your network:") + .placeholder(&default) + .default_input(&default) + .interact()? }, }; - // Generate plain spec. - spinner.set_message("Generating plain chain specification..."); + // Prompt for genesis state if not provided. + let genesis_state = if !genesis_state { + confirm("Should the genesis state file be generated ?".to_string()) + .initial_value(true) + .interact()? + } else { + true + }; + + // Prompt for genesis code if not provided. + let genesis_code = if !genesis_code { + confirm("Should the genesis code file be generated ?".to_string()) + .initial_value(true) + .interact()? + } else { + true + }; + + // Only prompt user for build profile if a live spec is being built on debug mode. + let release = if !release && matches!(chain_type, ChainType::Live) { + confirm("Using Debug profile to build a Live specification. Should Release be used instead ?") + .initial_value(true) + .interact()? + } else { + release + }; + + Ok(BuildSpec { + output_file, + release, + id, + default_bootnode, + chain_type, + chain, + relay, + protocol_id, + genesis_state, + genesis_code, + }) + } +} + +// Represents the configuration for building a chain specification. +struct BuildSpec { + output_file: PathBuf, + release: bool, + id: u32, + default_bootnode: bool, + chain_type: ChainType, + chain: String, + relay: RelayChain, + protocol_id: String, + genesis_state: bool, + genesis_code: bool, +} + +impl BuildSpec { + // Executes the process of generating the chain specification. + // + // This function generates plain and raw chain spec files based on the provided configuration, + // optionally including genesis state and runtime artifacts. If the node binary is missing, + // it triggers a build process. + fn build(&self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { + cli.intro("Building your chain spec")?; + let spinner = spinner(); + spinner.start("Generating chain specification..."); let mut generated_files = vec![]; + + // Ensure binary is built. + let mode: Profile = self.release.into(); + let binary_path = ensure_binary_exists(cli, &mode)?; + if !self.release { + cli.warning( + "NOTE: this command defaults to DEBUG builds for development chain types. Please use `--release` (or simply `-r` for a release build...)", + )?; + #[cfg(not(test))] + sleep(Duration::from_secs(3)) + } + + // Generate chain spec. generate_plain_chain_spec( &binary_path, - &plain_chain_spec, + &self.output_file, self.default_bootnode, - self.chain.as_deref(), + &self.chain, )?; + // Customize spec based on input. + self.customize()?; generated_files.push(format!( "Plain text chain specification file generated at: {}", - plain_chain_spec.display() + self.output_file.display() )); - // Customize spec based on input. - let mut chain_spec = ChainSpec::from(&plain_chain_spec)?; - chain_spec.replace_para_id(para_id)?; - let relay = self.relay.unwrap_or(RelayChain::PaseoLocal).to_string(); - chain_spec.replace_relay_chain(&relay)?; - let chain_type = self.chain_type.unwrap_or(ChainType::Development).to_string(); - chain_spec.replace_chain_type(&chain_type)?; - if self.protocol_id.is_some() { - let protocol_id = self.protocol_id.unwrap_or(DEFAULT_PROTOCOL_ID.to_string()); - chain_spec.replace_protocol_id(&protocol_id)?; - } - chain_spec.to_file(&plain_chain_spec)?; - // Generate raw spec. spinner.set_message("Generating raw chain specification..."); - let spec_name = plain_chain_spec + let spec_name = self + .output_file .file_name() .and_then(|s| s.to_str()) .unwrap_or(DEFAULT_SPEC_NAME) .trim_end_matches(".json"); let raw_spec_name = format!("{spec_name}-raw.json"); let raw_chain_spec = - generate_raw_chain_spec(&binary_path, &plain_chain_spec, &raw_spec_name)?; + generate_raw_chain_spec(&binary_path, &self.output_file, &raw_spec_name)?; generated_files.push(format!( "Raw chain specification file generated at: {}", raw_chain_spec.display() @@ -286,15 +441,14 @@ impl BuildSpecCommand { // Generate genesis artifacts. if self.genesis_code { spinner.set_message("Generating genesis code..."); - let wasm_file_name = format!("para-{}.wasm", para_id); + let wasm_file_name = format!("para-{}.wasm", self.id); let wasm_file = export_wasm_file(&binary_path, &raw_chain_spec, &wasm_file_name)?; generated_files .push(format!("WebAssembly runtime file exported at: {}", wasm_file.display())); } - if self.genesis_state { spinner.set_message("Generating genesis state..."); - let genesis_file_name = format!("para-{}-genesis-state", para_id); + let genesis_file_name = format!("para-{}-genesis-state", self.id); let genesis_state_file = generate_genesis_state_file(&binary_path, &raw_chain_spec, &genesis_file_name)?; generated_files @@ -302,7 +456,6 @@ impl BuildSpecCommand { } spinner.stop("Chain specification built successfully."); - let generated_files: Vec<_> = generated_files .iter() .map(|s| style(format!("{} {s}", console::Emoji("●", ">"))).dim().to_string()) @@ -312,162 +465,51 @@ impl BuildSpecCommand { "Need help? Learn more at {}\n", style("https://learn.onpop.io").magenta().underlined() ))?; - Ok("spec") } -} - -/// Guide the user to generate their chain specification. -async fn guide_user_to_generate_spec(args: BuildSpecCommand) -> anyhow::Result { - Cli.intro("Generate your chain spec")?; - - // Confirm output path - let default_output = format!("./{DEFAULT_SPEC_NAME}"); - let output_file: String = input("Name of the plain chain spec file. If a path is given, the necessary directories will be created:") - .placeholder(&default_output) - .default_input(&default_output) - .interact()?; - - // Check if specified chain spec already exists, allowing us to default values for prompts - let path = Path::new(&output_file); - let chain_spec = - (path.is_file() && path.exists()).then(|| ChainSpec::from(path).ok()).flatten(); - // Prompt for chain id. - let default = chain_spec - .as_ref() - .and_then(|cs| cs.get_parachain_id()) - .unwrap_or(DEFAULT_PARA_ID as u64) - .to_string(); - let para_id: u32 = input("What parachain ID should be used?") - .placeholder(&default) - .default_input(&default) - .interact()?; - - // Prompt for chain type. - // If relay is Kusama or Polkadot, then Live type is used and user is not prompted. - let mut prompt = cliclack::select("Choose the chain type: ".to_string()); - let default = chain_spec - .as_ref() - .and_then(|cs| cs.get_chain_type()) - .and_then(|r| ChainType::from_str(r, true).ok()); - if let Some(chain_type) = default.as_ref() { - prompt = prompt.initial_value(chain_type); - } - for (i, chain_type) in ChainType::VARIANTS.iter().enumerate() { - if default.is_none() && i == 0 { - prompt = prompt.initial_value(chain_type); - } - prompt = prompt.item( - chain_type, - chain_type.get_message().unwrap_or(chain_type.as_ref()), - chain_type.get_detailed_message().unwrap_or_default(), - ); + // Customize a chain specification. + fn customize(&self) -> anyhow::Result<()> { + let mut chain_spec = ChainSpec::from(&self.output_file)?; + chain_spec.replace_para_id(self.id)?; + chain_spec.replace_relay_chain(&self.relay.to_string())?; + chain_spec.replace_chain_type(&self.chain_type.to_string())?; + chain_spec.replace_protocol_id(&self.protocol_id)?; + chain_spec.to_file(&self.output_file)?; + Ok(()) } - let chain_type: ChainType = prompt.interact()?.clone(); - // Prompt for chain - let chain: String = input("Specify the chain specification. It can be one of the predefined ones (dev, local) or a custom one.") - .placeholder("dev") - .default_input("dev") - .interact()?; +} - // Prompt for relay chain. - let mut prompt = - cliclack::select("Choose the relay chain your chain will be connecting to: ".to_string()); - let default = chain_spec - .as_ref() - .and_then(|cs| cs.get_relay_chain()) - .and_then(|r| RelayChain::from_str(r, true).ok()); - if let Some(relay) = default.as_ref() { - prompt = prompt.initial_value(relay); - } - // Prompt relays chains based on the chain type - match chain_type { - ChainType::Live => - for relay in RelayChain::VARIANTS { - if !matches!( - relay, - RelayChain::Westend | - RelayChain::Paseo | RelayChain::Kusama | - RelayChain::Polkadot - ) { - continue; - } else { - prompt = prompt.item( - relay, - relay.get_message().unwrap_or(relay.as_ref()), - relay.get_detailed_message().unwrap_or_default(), - ); - } - }, - _ => - for relay in RelayChain::VARIANTS { - if matches!( - relay, - RelayChain::Westend | - RelayChain::Paseo | RelayChain::Kusama | - RelayChain::Polkadot - ) { - continue; - } else { - prompt = prompt.item( - relay, - relay.get_message().unwrap_or(relay.as_ref()), - relay.get_detailed_message().unwrap_or_default(), - ); - } - }, +// Locate binary, if it doesn't exist trigger build. +fn ensure_binary_exists( + cli: &mut impl cli::traits::Cli, + mode: &Profile, +) -> anyhow::Result { + let cwd = current_dir().unwrap_or(PathBuf::from("./")); + match binary_path(&mode.target_directory(&cwd), &cwd.join("node")) { + Ok(binary_path) => Ok(binary_path), + _ => { + cli.info("Node was not found. The project will be built locally.".to_string())?; + cli.warning("NOTE: this may take some time...")?; + build_parachain(&cwd, None, mode, None).map_err(|e| e.into()) + }, } +} - let relay_chain = prompt.interact()?.clone(); - - // Prompt for default bootnode if chain type is Local or Live. - let default_bootnode = match chain_type { - ChainType::Development => true, - _ => confirm("Would you like to use local host as a bootnode ?".to_string()).interact()?, - }; - - // Prompt for protocol-id. - let default = chain_spec - .as_ref() - .and_then(|cs| cs.get_protocol_id()) - .unwrap_or(DEFAULT_PROTOCOL_ID) - .to_string(); - let protocol_id: String = input("Enter the protocol ID that will identify your network:") - .placeholder(&default) - .default_input(&default) - .interact()?; - - // Prompt for genesis state - let genesis_state = confirm("Should the genesis state file be generated ?".to_string()) - .initial_value(true) - .interact()?; - - // Prompt for genesis code - let genesis_code = confirm("Should the genesis code file be generated ?".to_string()) - .initial_value(true) - .interact()?; - - // Only check user to check their profile selection if a live spec is being built on debug mode. - let profile = - if !args.release && matches!(chain_type, ChainType::Live) { - confirm("Using Debug profile to build a Live specification. Should Release be used instead ?") - .initial_value(true) - .interact()? - } else { - args.release - }; - - Ok(BuildSpecCommand { - output_file: Some(PathBuf::from(output_file)), - release: profile, - id: Some(para_id), - default_bootnode, - chain_type: Some(chain_type), - chain: Some(chain), - relay: Some(relay_chain), - protocol_id: Some(protocol_id), - genesis_state, - genesis_code, - }) +// Prepare the output path provided. +fn prepare_output_path(mut output_path: PathBuf) -> anyhow::Result { + if output_path.is_dir() { + if !output_path.exists() { + create_dir_all(&output_path)?; + } + output_path.push(DEFAULT_SPEC_NAME); + } else { + if let Some(parent_dir) = output_path.parent() { + if !parent_dir.exists() { + create_dir_all(&output_path)?; + } + } + } + output_path.set_extension("json"); + Ok(output_path) } diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index aa30607d..a3ba834d 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -75,22 +75,29 @@ pub fn binary_path(target_path: &Path, node_path: &Path) -> Result, + chain: &str, ) -> Result<(), Error> { check_command_exists(binary_path, "build-spec")?; let mut args = vec!["build-spec"]; - if let Some(chain_spec_id) = chain { - args.push("--chain"); - args.push(chain_spec_id); - } + args.push("--chain"); + args.push(chain); if !default_bootnode { args.push("--disable-default-bootnode"); } - cmd(binary_path, args).stdout_path(plain_chain_spec).stderr_null().run()?; + // Create a temporary file. + let temp_file = tempfile::NamedTempFile::new_in(std::env::temp_dir())?; + // Run the command and redirect output to the temporary file. + cmd(binary_path, args).stdout_path(temp_file.path()).stderr_null().run()?; + // Atomically replace the chain spec file with the temporary file. + let _ = temp_file + .persist(plain_chain_spec) + .map_err(|_| Error::AnyhowError(anyhow::anyhow!("error"))); Ok(()) } @@ -463,7 +470,7 @@ default_command = "pop-node" &binary_path, &temp_dir.path().join("plain-parachain-chainspec.json"), false, - Some("local"), + "local", )?; assert!(plain_chain_spec.exists()); { From e5a46ffbcba85347582a516ae2b95738fb1a21bb Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 26 Nov 2024 11:23:07 +0100 Subject: [PATCH 13/35] refactor: remove clone --- crates/pop-cli/src/commands/build/spec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 5d00815a..4136992e 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -124,7 +124,7 @@ pub(crate) enum RelayChain { } /// Command for generating a chain specification. -#[derive(Args, Clone)] +#[derive(Args)] pub struct BuildSpecCommand { /// File name for the resulting spec. If a path is given, /// the necessary directories will be created From 804a07b5df18ab1d80a3e083b177f0ae710c51da Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 26 Nov 2024 23:02:00 +0100 Subject: [PATCH 14/35] refactor: opt in to edit provided chain spec --- crates/pop-cli/src/commands/build/spec.rs | 135 ++++++++++++---------- 1 file changed, 71 insertions(+), 64 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 4136992e..86df1a14 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -199,10 +199,11 @@ impl BuildSpecCommand { genesis_code, } = self; - // Prompt for chain specification. + // Chain. let chain = match chain { Some(chain) => chain, _ => { + // Prompt for chain if not provided. input("Specify the chain specification. It can be one of the predefined ones (e.g. dev, local or a custom one) or the path to an existing chain spec.") .placeholder("dev") .default_input("dev") @@ -210,14 +211,18 @@ impl BuildSpecCommand { }, }; - // Check if the provided chain specification is a file. + // Output file. let maybe_chain_spec_file = PathBuf::from(&chain); - let output_file = if maybe_chain_spec_file.exists() && maybe_chain_spec_file.is_file() { + // Check if the provided chain specification is a file. + let (output_file, prompt) = if maybe_chain_spec_file.exists() && maybe_chain_spec_file.is_file() { if output_file.is_some() { cli.warning("NOTE: If an existing chain spec file is provided it will be used for the output path.")?; } - // Set the provided chain specification file as output file. - maybe_chain_spec_file + let prompt = confirm("An existing chain spec file is provided. Do you want to make changes to it?".to_string()) + .initial_value(false) + .interact()?; + // Set the provided chain specification file as output file and whether to prompt the user for additional changes to the spec. + (maybe_chain_spec_file, prompt) } else { let output_file = match output_file { Some(output) => output, @@ -230,43 +235,43 @@ impl BuildSpecCommand { .interact()? }, }; - prepare_output_path(output_file)? + (prepare_output_path(output_file)?, true) }; // If chain specification file already exists, obtain values for defaults when prompting. let chain_spec = ChainSpec::from(&output_file).ok(); - // Prompt for para id if not provided. - let id = match id { - Some(id) => id, - None => { - let default_id = chain_spec - .as_ref() - .and_then(|cs| cs.get_parachain_id()) - .unwrap_or(DEFAULT_PARA_ID as u64) - .to_string(); + // Para id. + let id = match id.or_else(|| { + chain_spec + .as_ref() + .and_then(|cs| cs.get_parachain_id().map(|id| id as u32)) + }) { + Some(id) if !prompt => id, + // No para id provided or user wants to make changes. + _ => { + let default = id.unwrap_or(DEFAULT_PARA_ID).to_string(); input("What parachain ID should be used?") - .placeholder(&default_id) - .default_input(&default_id) + .placeholder(&default) + .default_input(&default) .interact()? - }, + } }; - // Prompt for chain type if not provided. - let chain_type = match chain_type { - Some(chain_type) => chain_type, - None => { - let mut prompt = cliclack::select("Choose the chain type: ".to_string()); - let default = chain_spec - .as_ref() - .and_then(|cs| cs.get_chain_type()) - .and_then(|r| ChainType::from_str(r, true).ok()); - if let Some(chain_type) = default.as_ref() { - prompt = prompt.initial_value(chain_type); - } - for (i, chain_type) in ChainType::VARIANTS.iter().enumerate() { - if default.is_none() && i == 0 { - prompt = prompt.initial_value(chain_type); - } + // Chain type. + let chain_type = match chain_type.clone().or_else(|| { + chain_spec + .as_ref() + .and_then(|cs| cs.get_chain_type()) + .and_then(|r| ChainType::from_str(r, true).ok()) + }) { + Some(chain_type) if !prompt => chain_type, + // No chain type provided or user wants to make changes. + _ => { + // Prompt for chain type. + let default = chain_type.unwrap_or_default(); + let mut prompt = cliclack::select("Choose the chain type: ".to_string()) + .initial_value(&default); + for chain_type in ChainType::VARIANTS { prompt = prompt.item( chain_type, chain_type.get_message().unwrap_or(chain_type.as_ref()), @@ -277,20 +282,20 @@ impl BuildSpecCommand { }, }; - // Prompt for relay chain if not provided. - let relay = match relay { - Some(relay) => relay, - None => { - let mut prompt = cliclack::select( - "Choose the relay chain your chain will be connecting to: ".to_string(), - ); - let default = chain_spec - .as_ref() - .and_then(|cs| cs.get_relay_chain()) - .and_then(|r| RelayChain::from_str(r, true).ok()); - if let Some(relay) = default.as_ref() { - prompt = prompt.initial_value(relay); - } + // Relay. + let relay = match relay.clone().or_else(|| { + chain_spec + .as_ref() + .and_then(|cs| cs.get_relay_chain()) + .and_then(|r| RelayChain::from_str(r, true).ok()) + }) { + Some(relay) if !prompt => relay, + // No relay provided or user wants to make changes. + _ => { + // Prompt for relay chain if not provided. + let default = relay.unwrap_or_default(); + let mut prompt = cliclack::select("Choose the relay chain your chain will be connecting to: ".to_string()) + .initial_value(&default); for relay in RelayChain::VARIANTS { prompt = prompt.item( relay, @@ -302,6 +307,24 @@ impl BuildSpecCommand { }, }; + // Protocol ID. + let protocol_id = match protocol_id.clone().or_else(|| { + chain_spec + .as_ref() + .and_then(|cs| cs.get_protocol_id().map(String::from)) + }) { + Some(protocol_id) if !prompt => protocol_id, + // No protocol id provided or user wants to make changes. + _ => { + // Prompt for protocol-id if not provided. + let default = protocol_id.unwrap_or(DEFAULT_PROTOCOL_ID.to_string()); + input("Enter the protocol ID that will identify your network:") + .placeholder(&default) + .default_input(&default) + .interact()? + }, + }; + // Prompt for default bootnode if not provided and chain type is Local or Live. let default_bootnode = if !default_bootnode { match chain_type { @@ -313,22 +336,6 @@ impl BuildSpecCommand { true }; - // Prompt for protocol-id if not provided. - let protocol_id = match protocol_id { - Some(protocol_id) => protocol_id, - None => { - let default = chain_spec - .as_ref() - .and_then(|cs| cs.get_protocol_id()) - .unwrap_or(DEFAULT_PROTOCOL_ID) - .to_string(); - input("Enter the protocol ID that will identify your network:") - .placeholder(&default) - .default_input(&default) - .interact()? - }, - }; - // Prompt for genesis state if not provided. let genesis_state = if !genesis_state { confirm("Should the genesis state file be generated ?".to_string()) From 3f8dd45a52ffdc2241cb8f082a169daf0f75c837 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 26 Nov 2024 23:11:34 +0100 Subject: [PATCH 15/35] docs: improve --- crates/pop-cli/src/commands/build/spec.rs | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 86df1a14..930a1ea9 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -178,8 +178,8 @@ impl BuildSpecCommand { } } - /// Completes chain specification requirements by prompting for missing inputs, validating - /// provided values, and preparing a BuildSpec for generating chain spec files. + /// Complete chain specification requirements by prompting for missing inputs, validating + /// provided values, and preparing a BuildSpec to generate file(s). async fn complete_build_spec( self, cli: &mut impl cli::traits::Cli, @@ -214,14 +214,19 @@ impl BuildSpecCommand { // Output file. let maybe_chain_spec_file = PathBuf::from(&chain); // Check if the provided chain specification is a file. - let (output_file, prompt) = if maybe_chain_spec_file.exists() && maybe_chain_spec_file.is_file() { + let (output_file, prompt) = if maybe_chain_spec_file.exists() && + maybe_chain_spec_file.is_file() + { if output_file.is_some() { cli.warning("NOTE: If an existing chain spec file is provided it will be used for the output path.")?; } - let prompt = confirm("An existing chain spec file is provided. Do you want to make changes to it?".to_string()) + // Prompt whether the user wants to make additional changes to the provided chain spec + // file. + let prompt = confirm("An existing chain spec file is provided. Do you want to make additional changes to it?".to_string()) .initial_value(false) .interact()?; - // Set the provided chain specification file as output file and whether to prompt the user for additional changes to the spec. + // Set the provided chain specification file as output file and whether to prompt the + // user for additional changes to the provided spec. (maybe_chain_spec_file, prompt) } else { let output_file = match output_file { @@ -242,9 +247,7 @@ impl BuildSpecCommand { // Para id. let id = match id.or_else(|| { - chain_spec - .as_ref() - .and_then(|cs| cs.get_parachain_id().map(|id| id as u32)) + chain_spec.as_ref().and_then(|cs| cs.get_parachain_id().map(|id| id as u32)) }) { Some(id) if !prompt => id, // No para id provided or user wants to make changes. @@ -254,7 +257,7 @@ impl BuildSpecCommand { .placeholder(&default) .default_input(&default) .interact()? - } + }, }; // Chain type. @@ -269,8 +272,8 @@ impl BuildSpecCommand { _ => { // Prompt for chain type. let default = chain_type.unwrap_or_default(); - let mut prompt = cliclack::select("Choose the chain type: ".to_string()) - .initial_value(&default); + let mut prompt = + cliclack::select("Choose the chain type: ".to_string()).initial_value(&default); for chain_type in ChainType::VARIANTS { prompt = prompt.item( chain_type, @@ -292,10 +295,12 @@ impl BuildSpecCommand { Some(relay) if !prompt => relay, // No relay provided or user wants to make changes. _ => { - // Prompt for relay chain if not provided. + // Prompt for relay if not provided. let default = relay.unwrap_or_default(); - let mut prompt = cliclack::select("Choose the relay chain your chain will be connecting to: ".to_string()) - .initial_value(&default); + let mut prompt = cliclack::select( + "Choose the relay chain your chain will be connecting to: ".to_string(), + ) + .initial_value(&default); for relay in RelayChain::VARIANTS { prompt = prompt.item( relay, @@ -307,16 +312,15 @@ impl BuildSpecCommand { }, }; - // Protocol ID. - let protocol_id = match protocol_id.clone().or_else(|| { - chain_spec - .as_ref() - .and_then(|cs| cs.get_protocol_id().map(String::from)) - }) { + // Protocol id. + let protocol_id = match protocol_id + .clone() + .or_else(|| chain_spec.as_ref().and_then(|cs| cs.get_protocol_id().map(String::from))) + { Some(protocol_id) if !prompt => protocol_id, // No protocol id provided or user wants to make changes. _ => { - // Prompt for protocol-id if not provided. + // Prompt for protocol id if not provided. let default = protocol_id.unwrap_or(DEFAULT_PROTOCOL_ID.to_string()); input("Enter the protocol ID that will identify your network:") .placeholder(&default) From 88d1299b9005b247aa0a0dec75c78aa38b26e575 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 27 Nov 2024 00:05:08 +0100 Subject: [PATCH 16/35] refactor: flow flag input --- crates/pop-cli/src/commands/build/spec.rs | 127 +++++++++++----------- 1 file changed, 63 insertions(+), 64 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 930a1ea9..529be6f6 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -246,86 +246,85 @@ impl BuildSpecCommand { let chain_spec = ChainSpec::from(&output_file).ok(); // Para id. - let id = match id.or_else(|| { - chain_spec.as_ref().and_then(|cs| cs.get_parachain_id().map(|id| id as u32)) - }) { - Some(id) if !prompt => id, - // No para id provided or user wants to make changes. - _ => { - let default = id.unwrap_or(DEFAULT_PARA_ID).to_string(); - input("What parachain ID should be used?") - .placeholder(&default) - .default_input(&default) - .interact()? + let id = match id { + Some(id) => id, + None => { + let default = + chain_spec.as_ref().and_then(|cs| cs.get_parachain_id().map(|id| id as u32)).unwrap_or(DEFAULT_PARA_ID); + if prompt { + // Prompt for para id. + input("What parachain ID should be used?") + .default_input(&default.to_string()) + .interact()? + } else { default } }, }; // Chain type. - let chain_type = match chain_type.clone().or_else(|| { - chain_spec - .as_ref() - .and_then(|cs| cs.get_chain_type()) - .and_then(|r| ChainType::from_str(r, true).ok()) - }) { - Some(chain_type) if !prompt => chain_type, - // No chain type provided or user wants to make changes. - _ => { - // Prompt for chain type. - let default = chain_type.unwrap_or_default(); - let mut prompt = - cliclack::select("Choose the chain type: ".to_string()).initial_value(&default); - for chain_type in ChainType::VARIANTS { - prompt = prompt.item( - chain_type, - chain_type.get_message().unwrap_or(chain_type.as_ref()), - chain_type.get_detailed_message().unwrap_or_default(), - ); - } - prompt.interact()?.clone() + let chain_type = match chain_type { + Some(chain_type) => chain_type, + None => { + let default = + chain_spec + .as_ref() + .and_then(|cs| cs.get_chain_type()) + .and_then(|r| ChainType::from_str(r, true).ok()).unwrap_or_default(); + if prompt { + // Prompt for chain type. + let mut prompt = + cliclack::select("Choose the chain type: ".to_string()).initial_value(&default); + for chain_type in ChainType::VARIANTS { + prompt = prompt.item( + chain_type, + chain_type.get_message().unwrap_or(chain_type.as_ref()), + chain_type.get_detailed_message().unwrap_or_default(), + ); + } + prompt.interact()?.clone() + } else { default } }, }; // Relay. - let relay = match relay.clone().or_else(|| { - chain_spec - .as_ref() - .and_then(|cs| cs.get_relay_chain()) - .and_then(|r| RelayChain::from_str(r, true).ok()) - }) { - Some(relay) if !prompt => relay, - // No relay provided or user wants to make changes. - _ => { - // Prompt for relay if not provided. - let default = relay.unwrap_or_default(); - let mut prompt = cliclack::select( - "Choose the relay chain your chain will be connecting to: ".to_string(), - ) - .initial_value(&default); - for relay in RelayChain::VARIANTS { - prompt = prompt.item( - relay, - relay.get_message().unwrap_or(relay.as_ref()), - relay.get_detailed_message().unwrap_or_default(), - ); - } - prompt.interact()?.clone() + let relay = match relay { + Some(relay) => relay, + None => { + let default = + chain_spec + .as_ref() + .and_then(|cs| cs.get_relay_chain()) + .and_then(|r| RelayChain::from_str(r, true).ok()).unwrap_or_default(); + if prompt { + // Prompt for relay. + let mut prompt = cliclack::select( + "Choose the relay chain your chain will be connecting to: ".to_string(), + ) + .initial_value(&default); + for relay in RelayChain::VARIANTS { + prompt = prompt.item( + relay, + relay.get_message().unwrap_or(relay.as_ref()), + relay.get_detailed_message().unwrap_or_default(), + ); + } + prompt.interact()?.clone() + } else { default } }, }; // Protocol id. let protocol_id = match protocol_id - .clone() - .or_else(|| chain_spec.as_ref().and_then(|cs| cs.get_protocol_id().map(String::from))) { Some(protocol_id) if !prompt => protocol_id, - // No protocol id provided or user wants to make changes. _ => { - // Prompt for protocol id if not provided. - let default = protocol_id.unwrap_or(DEFAULT_PROTOCOL_ID.to_string()); - input("Enter the protocol ID that will identify your network:") - .placeholder(&default) - .default_input(&default) - .interact()? + let default = + chain_spec.as_ref().and_then(|cs| cs.get_protocol_id()).unwrap_or(DEFAULT_PROTOCOL_ID).to_string(); + if prompt { + // Prompt for protocol id. + input("Enter the protocol ID that will identify your network:") + .default_input(&default) + .interact()? + } else { default } }, }; From d164332ded884463960a44a7a84bf88d6ac2ae67 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 10:37:38 +0100 Subject: [PATCH 17/35] fix: prepare_output_path --- crates/pop-cli/src/commands/build/spec.rs | 58 ++++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 529be6f6..3046329e 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -12,7 +12,7 @@ use pop_parachains::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -use std::{env::current_dir, fs::create_dir_all, path::PathBuf}; +use std::{env::current_dir, fs::create_dir_all, path::{PathBuf, Path}}; #[cfg(not(test))] use std::{thread::sleep, time::Duration}; use strum::{EnumMessage, VariantArray}; @@ -240,7 +240,7 @@ impl BuildSpecCommand { .interact()? }, }; - (prepare_output_path(output_file)?, true) + (prepare_output_path(&output_file)?, true) }; // If chain specification file already exists, obtain values for defaults when prompting. let chain_spec = ChainSpec::from(&output_file).ok(); @@ -507,19 +507,65 @@ fn ensure_binary_exists( } // Prepare the output path provided. -fn prepare_output_path(mut output_path: PathBuf) -> anyhow::Result { - if output_path.is_dir() { +fn prepare_output_path(output_path: impl AsRef) -> anyhow::Result { + let mut output_path = output_path.as_ref().to_path_buf(); + // Check if the path ends with '.json' + let is_json_file = output_path + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.eq_ignore_ascii_case("json")) + .unwrap_or(false); + + if !is_json_file { + // Treat as directory. if !output_path.exists() { create_dir_all(&output_path)?; } output_path.push(DEFAULT_SPEC_NAME); } else { + // Treat as file. if let Some(parent_dir) = output_path.parent() { if !parent_dir.exists() { - create_dir_all(&output_path)?; + create_dir_all(parent_dir)?; } } } - output_path.set_extension("json"); Ok(output_path) } + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::{create_dir_all}; + use tempfile::TempDir; + + #[test] + fn prepare_output_path_works() -> anyhow::Result<()> { + // Create a temporary directory for testing + let temp_dir = TempDir::new()?; + let temp_dir_path = temp_dir.path(); + + // Existing Directory Path. + for dir in ["existing_dir", "existing_dir/", "existing_dir_json"] { + let existing_dir = temp_dir_path.join(dir); + create_dir_all(&existing_dir)?; + let result = prepare_output_path(&existing_dir)?; + // Expected path: existing_dir/chain-spec.json + let expected_path = existing_dir.join(DEFAULT_SPEC_NAME); + assert_eq!(result, expected_path); + } + + // Non-Existing Directory Path. + for dir in ["non_existing_dir", "non_existing_dir/", "non_existing_dir_json"] { + let non_existing_dir = temp_dir_path.join(dir); + let result = prepare_output_path(&non_existing_dir)?; + // Expected path: non_existing_dir/chain-spec.json + let expected_path = non_existing_dir.join(DEFAULT_SPEC_NAME); + assert_eq!(result, expected_path); + // The directory should now exist. + assert!(result.parent().unwrap().exists()); + } + + Ok(()) + } +} \ No newline at end of file From db65ebfdc0308d20f2175fff9c62512e92bc1cd4 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 10:47:46 +0100 Subject: [PATCH 18/35] refactor: resolve small improvements --- crates/pop-cli/src/commands/build/spec.rs | 10 +++++----- crates/pop-parachains/src/build.rs | 13 ++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 3046329e..d4c38f9d 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -143,8 +143,7 @@ pub struct BuildSpecCommand { /// Type of the chain. #[arg(short = 't', long = "type", value_enum)] pub(crate) chain_type: Option, - /// Specify the chain specification. It can be one of the predefined ones (e.g. dev, local or a - /// custom one) or the path to an existing chain spec. + /// Provide the chain specification to use (e.g. dev , local, custom or a path to an existing file). #[arg(short = 'c', long = "chain")] pub(crate) chain: Option, /// Relay chain this parachain will connect to. @@ -180,6 +179,9 @@ impl BuildSpecCommand { /// Complete chain specification requirements by prompting for missing inputs, validating /// provided values, and preparing a BuildSpec to generate file(s). + /// + /// # Arguments + /// * `cli` - The cli. async fn complete_build_spec( self, cli: &mut impl cli::traits::Cli, @@ -204,8 +206,7 @@ impl BuildSpecCommand { Some(chain) => chain, _ => { // Prompt for chain if not provided. - input("Specify the chain specification. It can be one of the predefined ones (e.g. dev, local or a custom one) or the path to an existing chain spec.") - .placeholder("dev") + input("Provide the chain specification to use (e.g. dev , local, custom or a path to an existing file)") .default_input("dev") .interact()? }, @@ -235,7 +236,6 @@ impl BuildSpecCommand { // Prompt for output file if not provided. let default_output = format!("./{DEFAULT_SPEC_NAME}"); input("Name of the plain chain spec file. If a path is given, the necessary directories will be created:") - .placeholder(&default_output) .default_input(&default_output) .interact()? }, diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index a3ba834d..1794aea2 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 -use crate::Error; -use anyhow::Result; +use crate::Error::{self, *}; +use anyhow::{anyhow, Result}; use duct::cmd; use pop_common::{manifest::from_path, Profile}; use serde_json::{json, Value}; @@ -84,9 +84,7 @@ pub fn generate_plain_chain_spec( chain: &str, ) -> Result<(), Error> { check_command_exists(binary_path, "build-spec")?; - let mut args = vec!["build-spec"]; - args.push("--chain"); - args.push(chain); + let mut args = vec!["build-spec", "--chain", chain]; if !default_bootnode { args.push("--disable-default-bootnode"); } @@ -95,9 +93,10 @@ pub fn generate_plain_chain_spec( // Run the command and redirect output to the temporary file. cmd(binary_path, args).stdout_path(temp_file.path()).stderr_null().run()?; // Atomically replace the chain spec file with the temporary file. - let _ = temp_file + temp_file .persist(plain_chain_spec) - .map_err(|_| Error::AnyhowError(anyhow::anyhow!("error"))); + .map_err(|e| AnyhowError(anyhow!("Failed to replace the chain spec file with the temporary file: {}", + e.to_string())))?; Ok(()) } From 505535f952c6400f9cbc6e8925825346d91c4a26 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 10:49:18 +0100 Subject: [PATCH 19/35] fix: protocol id prompt --- crates/pop-cli/src/commands/build/spec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index d4c38f9d..5eeb8e25 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -315,8 +315,8 @@ impl BuildSpecCommand { // Protocol id. let protocol_id = match protocol_id { - Some(protocol_id) if !prompt => protocol_id, - _ => { + Some(protocol_id) => protocol_id, + None => { let default = chain_spec.as_ref().and_then(|cs| cs.get_protocol_id()).unwrap_or(DEFAULT_PROTOCOL_ID).to_string(); if prompt { From 47ed21b7de6980d4b72ff27d90e25805b7589a75 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 10:56:31 +0100 Subject: [PATCH 20/35] fix: spinner --- crates/pop-cli/src/commands/build/spec.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 5eeb8e25..411697f4 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -143,7 +143,7 @@ pub struct BuildSpecCommand { /// Type of the chain. #[arg(short = 't', long = "type", value_enum)] pub(crate) chain_type: Option, - /// Provide the chain specification to use (e.g. dev , local, custom or a path to an existing file). + /// Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file). #[arg(short = 'c', long = "chain")] pub(crate) chain: Option, /// Relay chain this parachain will connect to. @@ -206,7 +206,7 @@ impl BuildSpecCommand { Some(chain) => chain, _ => { // Prompt for chain if not provided. - input("Provide the chain specification to use (e.g. dev , local, custom or a path to an existing file)") + input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)") .default_input("dev") .interact()? }, @@ -403,8 +403,6 @@ impl BuildSpec { // it triggers a build process. fn build(&self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { cli.intro("Building your chain spec")?; - let spinner = spinner(); - spinner.start("Generating chain specification..."); let mut generated_files = vec![]; // Ensure binary is built. @@ -417,6 +415,8 @@ impl BuildSpec { #[cfg(not(test))] sleep(Duration::from_secs(3)) } + let spinner = spinner(); + spinner.start("Generating chain specification..."); // Generate chain spec. generate_plain_chain_spec( From 5fd5cb5c2a290901b060131ff183ff0bdc981e03 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 11:00:11 +0100 Subject: [PATCH 21/35] fix: docs --- crates/pop-parachains/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pop-parachains/README.md b/crates/pop-parachains/README.md index aeda3c10..5d60382f 100644 --- a/crates/pop-parachains/README.md +++ b/crates/pop-parachains/README.md @@ -46,7 +46,7 @@ let package = None; // The optional package to be built. let binary_path = build_parachain(&path, package, &Profile::Release, None).unwrap();; // Generate a plain chain specification file of a parachain let plain_chain_spec_path = path.join("plain-parachain-chainspec.json"); -generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true, Some("dev")); +generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true, "dev"); // Customize your chain specification let mut chain_spec = ChainSpec::from(&plain_chain_spec_path).unwrap(); chain_spec.replace_para_id(2002); @@ -70,7 +70,7 @@ let package = None; // The optional package to be built. let binary_path = build_parachain(&path, package, &Profile::Release, None).unwrap();; // Generate a plain chain specification file of a parachain let plain_chain_spec_path = path.join("plain-parachain-chainspec.json"); -generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true, Some("dev")); +generate_plain_chain_spec(&binary_path, &plain_chain_spec_path, true, "dev"); // Generate a raw chain specification file of a parachain let chain_spec = generate_raw_chain_spec(&binary_path, &plain_chain_spec_path, "raw-parachain-chainspec.json").unwrap(); // Export the WebAssembly runtime for the parachain. From 1c013341ce43691babb246d2eccc226c8e26fb57 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 28 Nov 2024 19:54:44 +0100 Subject: [PATCH 22/35] test: test cli --- crates/pop-cli/src/cli.rs | 117 ++++++- crates/pop-cli/src/commands/build/spec.rs | 362 ++++++++++++++++++---- crates/pop-parachains/src/build.rs | 10 +- 3 files changed, 421 insertions(+), 68 deletions(-) diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index 968c1a4f..afcef292 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -68,6 +68,8 @@ pub(crate) mod traits { /// A select prompt. pub trait Select { + /// Sets the initially selected value. + fn initial_value(self, initial_value: T) -> Self; /// Starts the prompt interaction. fn interact(&mut self) -> Result; /// Adds an item to the selection prompt. @@ -134,6 +136,11 @@ impl traits::Cli for Cli { /// A confirmation prompt using cliclack. struct Confirm(cliclack::Confirm); impl traits::Confirm for Confirm { + /// Sets the initially selected value. + fn initial_value(mut self, initial_value: bool) -> Self { + self.0 = self.0.initial_value(initial_value); + self + } /// Starts the prompt interaction. fn interact(&mut self) -> Result { self.0.interact() @@ -177,6 +184,38 @@ impl traits::Input for Input { } } +/// A input prompt using cliclack. +struct Input(cliclack::Input); +impl traits::Input for Input { + /// Sets the default value for the input. + fn default_input(mut self, value: &str) -> Self { + self.0 = self.0.default_input(value); + self + } + /// Starts the prompt interaction. + fn interact(&mut self) -> Result { + self.0.interact() + } + /// Sets the placeholder (hint) text for the input. + fn placeholder(mut self, placeholder: &str) -> Self { + self.0 = self.0.placeholder(placeholder); + self + } + /// Sets whether the input is required. + fn required(mut self, required: bool) -> Self { + self.0 = self.0.required(required); + self + } + /// Sets a validation callback for the input that is called when the user submits. + fn validate( + mut self, + validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, + ) -> Self { + self.0 = self.0.validate(validator); + self + } +} + /// A multi-select prompt using cliclack. struct MultiSelect(cliclack::MultiSelect); @@ -215,6 +254,27 @@ impl traits::Select for Select { } } +/// A select prompt using cliclack. +struct Select(cliclack::Select); + +impl traits::Select for Select { + fn initial_value(mut self, initial_value: T) -> Self { + self.0 = self.0.initial_value(initial_value); + self + } + + /// Starts the prompt interaction. + fn interact(&mut self) -> Result { + self.0.interact() + } + + /// Adds an item to the selection prompt. + fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self { + self.0 = self.0.item(value, label, hint); + self + } +} + #[cfg(test)] pub(crate) mod tests { use super::traits::*; @@ -231,8 +291,7 @@ pub(crate) mod tests { multiselect_expectation: Option<(String, Option, bool, Option>)>, outro_cancel_expectation: Option, - select_expectation: - Option<(String, Option, bool, Option>, usize)>, + select_expectation: Vec<(String, Option, bool, Option>, usize)>, success_expectations: Vec, warning_expectations: Vec, } @@ -243,17 +302,17 @@ pub(crate) mod tests { } pub(crate) fn expect_confirm(mut self, prompt: impl Display, confirm: bool) -> Self { - self.confirm_expectation.push((prompt.to_string(), confirm)); + self.confirm_expectation.insert(0, (prompt.to_string(), confirm)); self } pub(crate) fn expect_input(mut self, prompt: impl Display, input: String) -> Self { - self.input_expectations.push((prompt.to_string(), input)); + self.input_expectations.insert(0, (prompt.to_string(), input)); self } pub(crate) fn expect_info(mut self, message: impl Display) -> Self { - self.info_expectations.push(message.to_string()); + self.info_expectations.insert(0, message.to_string()); self } @@ -283,6 +342,19 @@ pub(crate) mod tests { self } + pub(crate) fn expect_select( + mut self, + prompt: impl Display, + required: Option, + collect: bool, + items: Option>, + item: usize, + ) -> Self { + self.select_expectation + .insert(0, (prompt.to_string(), required, collect, items, item)); + self + } + pub(crate) fn expect_select( mut self, prompt: impl Display, @@ -327,8 +399,15 @@ pub(crate) mod tests { if let Some(expectation) = self.outro_cancel_expectation { panic!("`{expectation}` outro cancel expectation not satisfied") } - if let Some((prompt, _, _, _, _)) = self.select_expectation { - panic!("`{prompt}` select prompt expectation not satisfied") + if !self.select_expectation.is_empty() { + panic!( + "`{}` select prompt expectation not satisfied", + self.select_expectation + .iter() + .map(|(s, _, _, _, _)| s.clone()) // Extract the `String` part + .collect::>() + .join(", ") + ); } if !self.success_expectations.is_empty() { panic!( @@ -425,10 +504,16 @@ pub(crate) mod tests { fn select(&mut self, prompt: impl Display) -> impl Select { let prompt = prompt.to_string(); if let Some((expectation, _, collect, items_expectation, item)) = - self.select_expectation.take() + self.select_expectation.pop() { assert_eq!(expectation, prompt, "prompt does not satisfy expectation"); - return MockSelect { items_expectation, collect, items: vec![], item }; + return MockSelect { + items_expectation, + collect, + items: vec![], + item, + initial_value: None, + }; } MockSelect::default() @@ -553,15 +638,27 @@ pub(crate) mod tests { collect: bool, items: Vec, item: usize, + initial_value: Option, } impl MockSelect { pub(crate) fn default() -> Self { - Self { items_expectation: None, collect: false, items: vec![], item: 0 } + Self { + items_expectation: None, + collect: false, + items: vec![], + item: 0, + initial_value: None, + } } } impl Select for MockSelect { + fn initial_value(mut self, initial_value: T) -> Self { + self.initial_value = Some(initial_value); + self + } + fn interact(&mut self) -> Result { Ok(self.items[self.item].clone()) } diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 411697f4..eaa425f2 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -2,22 +2,30 @@ use crate::{ cli, - cli::{traits::Cli as _, Cli}, + cli::{ + traits::{Cli as _, *}, + Cli, + }, style::style, }; use clap::{Args, ValueEnum}; -use cliclack::{confirm, input, spinner}; +use cliclack::{spinner}; use pop_common::Profile; use pop_parachains::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, generate_plain_chain_spec, generate_raw_chain_spec, is_supported, ChainSpec, }; -use std::{env::current_dir, fs::create_dir_all, path::{PathBuf, Path}}; +use std::{ + env::current_dir, + fs::create_dir_all, + path::{Path, PathBuf}, +}; #[cfg(not(test))] use std::{thread::sleep, time::Duration}; use strum::{EnumMessage, VariantArray}; use strum_macros::{AsRefStr, Display, EnumString}; +const DEFAULT_CHAIN: &str = "dev"; const DEFAULT_PARA_ID: u32 = 2000; const DEFAULT_PROTOCOL_ID: &str = "my-protocol"; const DEFAULT_SPEC_NAME: &str = "chain-spec.json"; @@ -124,7 +132,7 @@ pub(crate) enum RelayChain { } /// Command for generating a chain specification. -#[derive(Args)] +#[derive(Args, Default)] pub struct BuildSpecCommand { /// File name for the resulting spec. If a path is given, /// the necessary directories will be created @@ -143,7 +151,8 @@ pub struct BuildSpecCommand { /// Type of the chain. #[arg(short = 't', long = "type", value_enum)] pub(crate) chain_type: Option, - /// Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file). + /// Provide the chain specification to use (e.g. dev, local, custom or a path to an existing + /// file). #[arg(short = 'c', long = "chain")] pub(crate) chain: Option, /// Relay chain this parachain will connect to. @@ -164,12 +173,12 @@ impl BuildSpecCommand { /// Executes the build spec command. pub(crate) async fn execute(self) -> anyhow::Result<&'static str> { let mut cli = Cli; + cli.intro("Generate your chain spec")?; // Checks for appchain project in `./`. if is_supported(None)? { - let build_spec = self.complete_build_spec(&mut cli).await?; + let build_spec = self.configure_build_spec(&mut cli).await?; build_spec.build(&mut cli) } else { - cli.intro("Generate your chain spec")?; cli.outro_cancel( "🚫 Can't build a specification for target. Maybe not a chain project ?", )?; @@ -177,17 +186,15 @@ impl BuildSpecCommand { } } - /// Complete chain specification requirements by prompting for missing inputs, validating + /// Configure chain specification requirements by prompting for missing inputs, validating /// provided values, and preparing a BuildSpec to generate file(s). /// /// # Arguments /// * `cli` - The cli. - async fn complete_build_spec( + async fn configure_build_spec( self, cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { - cli.intro("Generate your chain spec")?; - let BuildSpecCommand { output_file, release, @@ -206,8 +213,9 @@ impl BuildSpecCommand { Some(chain) => chain, _ => { // Prompt for chain if not provided. - input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)") - .default_input("dev") + cli.input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)") + .placeholder(DEFAULT_CHAIN) + .default_input(DEFAULT_CHAIN) .interact()? }, }; @@ -223,7 +231,7 @@ impl BuildSpecCommand { } // Prompt whether the user wants to make additional changes to the provided chain spec // file. - let prompt = confirm("An existing chain spec file is provided. Do you want to make additional changes to it?".to_string()) + let prompt = cli.confirm("An existing chain spec file is provided. Do you want to make additional changes to it?".to_string()) .initial_value(false) .interact()?; // Set the provided chain specification file as output file and whether to prompt the @@ -235,9 +243,12 @@ impl BuildSpecCommand { None => { // Prompt for output file if not provided. let default_output = format!("./{DEFAULT_SPEC_NAME}"); - input("Name of the plain chain spec file. If a path is given, the necessary directories will be created:") - .default_input(&default_output) - .interact()? + PathBuf::from( + cli.input("Name or path for the plain chain spec file:") + .placeholder(&default_output) + .default_input(&default_output) + .interact()?, + ) }, }; (prepare_output_path(&output_file)?, true) @@ -249,14 +260,22 @@ impl BuildSpecCommand { let id = match id { Some(id) => id, None => { - let default = - chain_spec.as_ref().and_then(|cs| cs.get_parachain_id().map(|id| id as u32)).unwrap_or(DEFAULT_PARA_ID); + let default = chain_spec + .as_ref() + .and_then(|cs| cs.get_parachain_id().map(|id| id as u32)) + .unwrap_or(DEFAULT_PARA_ID); if prompt { // Prompt for para id. - input("What parachain ID should be used?") - .default_input(&default.to_string()) + let default_str = default.to_string(); + cli.input("What parachain ID should be used?") + .default_input(&default_str) + .default_input(&default_str) .interact()? - } else { default } + .parse::() + .unwrap_or(DEFAULT_PARA_ID) + } else { + default + } }, }; @@ -264,15 +283,15 @@ impl BuildSpecCommand { let chain_type = match chain_type { Some(chain_type) => chain_type, None => { - let default = - chain_spec - .as_ref() - .and_then(|cs| cs.get_chain_type()) - .and_then(|r| ChainType::from_str(r, true).ok()).unwrap_or_default(); + let default = chain_spec + .as_ref() + .and_then(|cs| cs.get_chain_type()) + .and_then(|r| ChainType::from_str(r, true).ok()) + .unwrap_or_default(); if prompt { // Prompt for chain type. let mut prompt = - cliclack::select("Choose the chain type: ".to_string()).initial_value(&default); + cli.select("Choose the chain type: ".to_string()).initial_value(&default); for chain_type in ChainType::VARIANTS { prompt = prompt.item( chain_type, @@ -281,7 +300,9 @@ impl BuildSpecCommand { ); } prompt.interact()?.clone() - } else { default } + } else { + default + } }, }; @@ -289,16 +310,15 @@ impl BuildSpecCommand { let relay = match relay { Some(relay) => relay, None => { - let default = - chain_spec - .as_ref() - .and_then(|cs| cs.get_relay_chain()) - .and_then(|r| RelayChain::from_str(r, true).ok()).unwrap_or_default(); + let default = chain_spec + .as_ref() + .and_then(|cs| cs.get_relay_chain()) + .and_then(|r| RelayChain::from_str(r, true).ok()) + .unwrap_or_default(); if prompt { // Prompt for relay. - let mut prompt = cliclack::select( - "Choose the relay chain your chain will be connecting to: ".to_string(), - ) + let mut prompt = cli + .select("Choose the relay your chain will be connecting to: ".to_string()) .initial_value(&default); for relay in RelayChain::VARIANTS { prompt = prompt.item( @@ -308,23 +328,30 @@ impl BuildSpecCommand { ); } prompt.interact()?.clone() - } else { default } + } else { + default + } }, }; // Protocol id. - let protocol_id = match protocol_id - { + let protocol_id = match protocol_id { Some(protocol_id) => protocol_id, None => { - let default = - chain_spec.as_ref().and_then(|cs| cs.get_protocol_id()).unwrap_or(DEFAULT_PROTOCOL_ID).to_string(); + let default = chain_spec + .as_ref() + .and_then(|cs| cs.get_protocol_id()) + .unwrap_or(DEFAULT_PROTOCOL_ID) + .to_string(); if prompt { // Prompt for protocol id. - input("Enter the protocol ID that will identify your network:") + cli.input("Enter the protocol ID that will identify your network:") + .placeholder(&default) .default_input(&default) .interact()? - } else { default } + } else { + default + } }, }; @@ -332,7 +359,8 @@ impl BuildSpecCommand { let default_bootnode = if !default_bootnode { match chain_type { ChainType::Development => true, - _ => confirm("Would you like to use local host as a bootnode ?".to_string()) + _ => cli + .confirm("Would you like to use local host as a bootnode ?".to_string()) .interact()?, } } else { @@ -341,7 +369,7 @@ impl BuildSpecCommand { // Prompt for genesis state if not provided. let genesis_state = if !genesis_state { - confirm("Should the genesis state file be generated ?".to_string()) + cli.confirm("Should the genesis state file be generated ?".to_string()) .initial_value(true) .interact()? } else { @@ -350,7 +378,7 @@ impl BuildSpecCommand { // Prompt for genesis code if not provided. let genesis_code = if !genesis_code { - confirm("Should the genesis code file be generated ?".to_string()) + cli.confirm("Should the genesis code file be generated ?".to_string()) .initial_value(true) .interact()? } else { @@ -359,7 +387,7 @@ impl BuildSpecCommand { // Only prompt user for build profile if a live spec is being built on debug mode. let release = if !release && matches!(chain_type, ChainType::Live) { - confirm("Using Debug profile to build a Live specification. Should Release be used instead ?") + cli.confirm("Using Debug profile to build a Live specification. Should Release be used instead ?") .initial_value(true) .interact()? } else { @@ -382,6 +410,7 @@ impl BuildSpecCommand { } // Represents the configuration for building a chain specification. +#[derive(Debug)] struct BuildSpec { output_file: PathBuf, release: bool, @@ -535,9 +564,204 @@ fn prepare_output_path(output_path: impl AsRef) -> anyhow::Result #[cfg(test)] mod tests { - use super::*; - use std::fs::{create_dir_all}; - use tempfile::TempDir; + use super::{ChainType::*, RelayChain::*, *}; + use crate::cli::MockCli; + use std::{fs::create_dir_all, path::PathBuf}; + use tempfile::{tempdir, TempDir}; + + #[tokio::test] + async fn configure_build_spec_works() -> anyhow::Result<()> { + let chain = "local"; + let chain_type = Live; + let default_bootnode = true; + let genesis_code = true; + let genesis_state = true; + let output_file = "artifacts/chain-spec.json"; + let para_id = 4242; + let protocol_id = "pop"; + let relay = Polkadot; + let release = true; + + for build_spec_cmd in [ + // No flags used. + BuildSpecCommand::default(), + // All flags used. + BuildSpecCommand { + output_file: Some(PathBuf::from(output_file)), + release, + id: Some(para_id), + default_bootnode, + chain_type: Some(chain_type.clone()), + chain: Some(chain.to_string()), + relay: Some(relay.clone()), + protocol_id: Some(protocol_id.to_string()), + genesis_state, + genesis_code, + }, + ] { + let mut cli = MockCli::new(); + // If no flags are provided. + if build_spec_cmd.chain.is_none() { + cli = cli + .expect_input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)", chain.to_string()) + .expect_input( + "Name or path for the plain chain spec file:", output_file.to_string()) + .expect_input( + "What parachain ID should be used?", para_id.to_string()) + .expect_input( + "Enter the protocol ID that will identify your network:", protocol_id.to_string()) + .expect_select( + "Choose the chain type: ", + Some(false), + true, + Some(chain_types()), + chain_type.clone() as usize, + ).expect_select( + "Choose the relay your chain will be connecting to: ", + Some(false), + true, + Some(relays()), + relay.clone() as usize, + ).expect_confirm("Would you like to use local host as a bootnode ?", default_bootnode).expect_confirm("Should the genesis state file be generated ?", genesis_state).expect_confirm("Should the genesis code file be generated ?", genesis_code).expect_confirm( + "Using Debug profile to build a Live specification. Should Release be used instead ?", + release, + ); + } + let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; + assert_eq!(build_spec.chain, chain); + assert_eq!(build_spec.output_file, PathBuf::from(output_file)); + assert_eq!(build_spec.id, para_id); + assert_eq!(build_spec.release, release); + assert_eq!(build_spec.default_bootnode, default_bootnode); + assert_eq!(build_spec.chain_type, chain_type); + assert_eq!(build_spec.relay, relay); + assert_eq!(build_spec.protocol_id, protocol_id); + assert_eq!(build_spec.genesis_state, genesis_state); + assert_eq!(build_spec.genesis_code, genesis_code); + cli.verify()?; + } + Ok(()) + } + + #[tokio::test] + async fn configure_build_spec_with_existing_chain_file() -> anyhow::Result<()> { + let chain_type = Live; + let default_bootnode = true; + let genesis_code = true; + let genesis_state = true; + let output_file = "artifacts/chain-spec.json"; + let para_id = 4242; + let protocol_id = "pop"; + let relay = Polkadot; + let release = true; + + // Create a temporary file to act as the existing chain spec file. + let temp_dir = tempdir()?; + let chain_spec_path = temp_dir.path().join("existing-chain-spec.json"); + std::fs::write(&chain_spec_path, "{}")?; // Write a dummy JSON to the file + + // Whether to make changes to the provided chain spec file. + for changes in [true, false] { + for build_spec_cmd in [ + // No flags used except the provided chain spec file. + BuildSpecCommand { + chain: Some(chain_spec_path.to_string_lossy().to_string()), + ..Default::default() + }, + // All flags used. + BuildSpecCommand { + output_file: Some(PathBuf::from(output_file)), + release, + id: Some(para_id), + default_bootnode, + chain_type: Some(chain_type.clone()), + chain: Some(chain_spec_path.to_string_lossy().to_string()), + relay: Some(relay.clone()), + protocol_id: Some(protocol_id.to_string()), + genesis_state, + genesis_code, + }, + ] { + let mut cli = MockCli::new().expect_confirm( + "An existing chain spec file is provided. Do you want to make additional changes to it?", + changes, + ); + // When user wants to make changes to chain spec file via prompts and no flags + // provided. + let no_flags_used = build_spec_cmd.relay.is_none(); + if changes && no_flags_used { + if build_spec_cmd.id.is_none() { + cli = cli + .expect_input("What parachain ID should be used?", para_id.to_string()); + } + if build_spec_cmd.protocol_id.is_none() { + cli = cli.expect_input( + "Enter the protocol ID that will identify your network:", + protocol_id.to_string(), + ); + } + if build_spec_cmd.chain_type.is_none() { + cli = cli.expect_select( + "Choose the chain type: ", + Some(false), + true, + Some(chain_types()), + chain_type.clone() as usize, + ); + } + if build_spec_cmd.relay.is_none() { + cli = cli.expect_select( + "Choose the relay your chain will be connecting to: ", + Some(false), + true, + Some(relays()), + relay.clone() as usize, + ); + } + if !build_spec_cmd.default_bootnode { + cli = cli.expect_confirm( + "Would you like to use local host as a bootnode ?", + default_bootnode, + ); + } + if !build_spec_cmd.genesis_state { + cli = cli.expect_confirm( + "Should the genesis state file be generated ?", + genesis_state, + ); + } + if !build_spec_cmd.genesis_code { + cli = cli.expect_confirm( + "Should the genesis code file be generated ?", + genesis_code, + ); + } + if !build_spec_cmd.release { + cli = cli.expect_confirm( + "Using Debug profile to build a Live specification. Should Release be used instead ?", + release, + ); + } + } + let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; + if changes && no_flags_used { + assert_eq!(build_spec.id, para_id); + assert_eq!(build_spec.release, release); + assert_eq!(build_spec.default_bootnode, default_bootnode); + assert_eq!(build_spec.chain_type, chain_type); + assert_eq!(build_spec.relay, relay); + assert_eq!(build_spec.protocol_id, protocol_id); + assert_eq!(build_spec.genesis_state, genesis_state); + assert_eq!(build_spec.genesis_code, genesis_code); + } + // Assert that the chain spec file is correctly detected and used + assert_eq!(build_spec.chain, chain_spec_path.to_string_lossy()); + assert_eq!(build_spec.output_file, chain_spec_path); + cli.verify()?; + } + } + Ok(()) + } #[test] fn prepare_output_path_works() -> anyhow::Result<()> { @@ -545,7 +769,13 @@ mod tests { let temp_dir = TempDir::new()?; let temp_dir_path = temp_dir.path(); - // Existing Directory Path. + // No directory path. + let file = temp_dir_path.join("chain-spec.json"); + let result = prepare_output_path(&file)?; + // Expected path: chain-spec.json + assert_eq!(result, file); + + // Existing directory Path. for dir in ["existing_dir", "existing_dir/", "existing_dir_json"] { let existing_dir = temp_dir_path.join(dir); create_dir_all(&existing_dir)?; @@ -555,7 +785,7 @@ mod tests { assert_eq!(result, expected_path); } - // Non-Existing Directory Path. + // Non-existing directory Path. for dir in ["non_existing_dir", "non_existing_dir/", "non_existing_dir_json"] { let non_existing_dir = temp_dir_path.join(dir); let result = prepare_output_path(&non_existing_dir)?; @@ -568,4 +798,28 @@ mod tests { Ok(()) } -} \ No newline at end of file + + fn relays() -> Vec<(String, String)> { + RelayChain::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } + + fn chain_types() -> Vec<(String, String)> { + ChainType::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } +} diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index 1794aea2..95f37453 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -93,10 +93,12 @@ pub fn generate_plain_chain_spec( // Run the command and redirect output to the temporary file. cmd(binary_path, args).stdout_path(temp_file.path()).stderr_null().run()?; // Atomically replace the chain spec file with the temporary file. - temp_file - .persist(plain_chain_spec) - .map_err(|e| AnyhowError(anyhow!("Failed to replace the chain spec file with the temporary file: {}", - e.to_string())))?; + temp_file.persist(plain_chain_spec).map_err(|e| { + AnyhowError(anyhow!( + "Failed to replace the chain spec file with the temporary file: {}", + e.to_string() + )) + })?; Ok(()) } From faf303899de341d2dd3f90bedcac85bbc9ce10e6 Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:16:45 +0100 Subject: [PATCH 23/35] chore: refactor --- crates/pop-cli/src/commands/build/spec.rs | 80 +++++++++++++--------- crates/pop-parachains/src/utils/onboard.rs | 0 2 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 crates/pop-parachains/src/utils/onboard.rs diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index eaa425f2..1857abe6 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -536,32 +536,46 @@ fn ensure_binary_exists( } // Prepare the output path provided. + fn prepare_output_path(output_path: impl AsRef) -> anyhow::Result { let mut output_path = output_path.as_ref().to_path_buf(); + // Convert to string to check for trailing slash. + let output_path_str = output_path.to_string_lossy(); + let ends_with_slash = output_path_str.ends_with(std::path::MAIN_SEPARATOR); + // Check if the path has an extension. + let has_extension = output_path.extension().is_some(); // Check if the path ends with '.json' let is_json_file = output_path .extension() .and_then(|ext| ext.to_str()) .map(|ext| ext.eq_ignore_ascii_case("json")) .unwrap_or(false); - - if !is_json_file { + if ends_with_slash { // Treat as directory. if !output_path.exists() { create_dir_all(&output_path)?; } output_path.push(DEFAULT_SPEC_NAME); - } else { - // Treat as file. - if let Some(parent_dir) = output_path.parent() { - if !parent_dir.exists() { - create_dir_all(parent_dir)?; - } + } else if !has_extension { + // No extension, treat as file, set extension to '.json'. + output_path.set_extension("json"); + } else if !is_json_file { + // Has an extension but not '.json', change extension to '.json'. + output_path.set_extension("json"); + } + // Else: Ends with '.json', treat as file (no changes needed). + + // After modifications, create the parent directory if it doesn't exist. + if let Some(parent_dir) = output_path.parent() { + if !parent_dir.exists() { + create_dir_all(parent_dir)?; } } Ok(output_path) } + + #[cfg(test)] mod tests { use super::{ChainType::*, RelayChain::*, *}; @@ -769,31 +783,33 @@ mod tests { let temp_dir = TempDir::new()?; let temp_dir_path = temp_dir.path(); - // No directory path. - let file = temp_dir_path.join("chain-spec.json"); - let result = prepare_output_path(&file)?; - // Expected path: chain-spec.json - assert_eq!(result, file); - - // Existing directory Path. - for dir in ["existing_dir", "existing_dir/", "existing_dir_json"] { - let existing_dir = temp_dir_path.join(dir); - create_dir_all(&existing_dir)?; - let result = prepare_output_path(&existing_dir)?; - // Expected path: existing_dir/chain-spec.json - let expected_path = existing_dir.join(DEFAULT_SPEC_NAME); - assert_eq!(result, expected_path); - } - - // Non-existing directory Path. - for dir in ["non_existing_dir", "non_existing_dir/", "non_existing_dir_json"] { - let non_existing_dir = temp_dir_path.join(dir); - let result = prepare_output_path(&non_existing_dir)?; - // Expected path: non_existing_dir/chain-spec.json - let expected_path = non_existing_dir.join(DEFAULT_SPEC_NAME); + let test_cases = [ + ("chain-spec.json", "chain-spec.json"), + ("existing_dir", "existing_dir.json"), + ("existing_dir/", "existing_dir/chain-spec.json"), + ("non_existing_dir", "non_existing_dir.json"), + ("non_existing_dir/", "non_existing_dir/chain-spec.json"), + ("some_file ", "some_file.json"), + ("some_dir/some_file", "some_dir/some_file.json"), + ]; + for (input_str, expected_str) in &test_cases { + let input_path = temp_dir_path.join(input_str); + let expected_path = temp_dir_path.join(expected_str); + // Create directories for existing paths + if input_str.starts_with("existing_dir") { + let dir_to_create = if input_str.ends_with('/') { + input_path.clone() + } else { + input_path.parent().unwrap_or(&input_path).to_path_buf() + }; + create_dir_all(&dir_to_create)?; + } + let result = prepare_output_path(&input_path)?; assert_eq!(result, expected_path); - // The directory should now exist. - assert!(result.parent().unwrap().exists()); + // Ensure the parent directory exists + if let Some(parent_dir) = result.parent() { + assert!(parent_dir.exists()); + } } Ok(()) diff --git a/crates/pop-parachains/src/utils/onboard.rs b/crates/pop-parachains/src/utils/onboard.rs new file mode 100644 index 00000000..e69de29b From b80b5bbe69206f819f2279d3e345e194fe346224 Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Date: Fri, 29 Nov 2024 12:24:00 +0100 Subject: [PATCH 24/35] chore: amend test --- crates/pop-cli/src/commands/build/spec.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 1857abe6..32ff7425 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -9,7 +9,7 @@ use crate::{ style::style, }; use clap::{Args, ValueEnum}; -use cliclack::{spinner}; +use cliclack::spinner; use pop_common::Profile; use pop_parachains::{ binary_path, build_parachain, export_wasm_file, generate_genesis_state_file, @@ -536,7 +536,6 @@ fn ensure_binary_exists( } // Prepare the output path provided. - fn prepare_output_path(output_path: impl AsRef) -> anyhow::Result { let mut output_path = output_path.as_ref().to_path_buf(); // Convert to string to check for trailing slash. @@ -574,8 +573,6 @@ fn prepare_output_path(output_path: impl AsRef) -> anyhow::Result Ok(output_path) } - - #[cfg(test)] mod tests { use super::{ChainType::*, RelayChain::*, *}; @@ -789,8 +786,9 @@ mod tests { ("existing_dir/", "existing_dir/chain-spec.json"), ("non_existing_dir", "non_existing_dir.json"), ("non_existing_dir/", "non_existing_dir/chain-spec.json"), - ("some_file ", "some_file.json"), + ("some_file", "some_file.json"), ("some_dir/some_file", "some_dir/some_file.json"), + ("existing_dir/subdir/", "existing_dir/subdir/chain-spec.json"), ]; for (input_str, expected_str) in &test_cases { let input_path = temp_dir_path.join(input_str); From fab05843cb06ad7243f483781988828fb7bc1b28 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Tue, 26 Nov 2024 15:07:44 +0100 Subject: [PATCH 25/35] feat: production profile --- crates/pop-cli/src/commands/build/mod.rs | 54 ++- .../pop-cli/src/commands/build/parachain.rs | 47 ++- crates/pop-cli/src/commands/build/spec.rs | 348 +++--------------- crates/pop-common/Cargo.toml | 4 +- crates/pop-common/src/build.rs | 49 ++- crates/pop-parachains/src/build.rs | 68 ++-- 6 files changed, 201 insertions(+), 369 deletions(-) diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index 08af079b..5d752980 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -8,6 +8,7 @@ use duct::cmd; use std::path::PathBuf; #[cfg(feature = "parachain")] use {parachain::BuildParachainCommand, spec::BuildSpecCommand}; +use pop_common::Profile; #[cfg(feature = "contract")] pub(crate) mod contract; @@ -28,9 +29,9 @@ pub(crate) struct BuildArgs { /// The package to be built. #[arg(short = 'p', long)] pub(crate) package: Option, - /// For production, always build in release mode to exclude debug features. - #[clap(short, long)] - pub(crate) release: bool, + /// Build profile [default: debug]. + #[clap(long, value_enum)] + pub(crate) profile: Option, /// Parachain ID to be used when generating the chain spec files. #[arg(short = 'i', long = "id")] #[cfg(feature = "parachain")] @@ -62,7 +63,11 @@ impl Command { #[cfg(feature = "contract")] if pop_contracts::is_supported(args.path.as_deref())? { // All commands originating from root command are valid - BuildContractCommand { path: args.path, release: args.release, valid: true } + let release = match args.profile.unwrap_or(Profile::Debug) { + Profile::Debug => false, + _ => true, + }; + BuildContractCommand { path: args.path, release, valid: true } .execute()?; return Ok("contract"); } @@ -74,7 +79,7 @@ impl Command { BuildParachainCommand { path: args.path, package: args.package, - release: args.release, + profile: args.profile, id: args.id, valid: true, } @@ -101,13 +106,15 @@ impl Command { _args.push("--package"); _args.push(package) } - if args.release { + let profile = args.profile.unwrap_or(Profile::Debug); + if profile == Profile::Release { _args.push("--release"); + } else if profile == Profile::Production { + _args.push("--profile=production"); } cmd("cargo", _args).dir(args.path.unwrap_or_else(|| "./".into())).run()?; - let mode = if args.release { "RELEASE" } else { "DEBUG" }; - cli.info(format!("The {project} was built in {mode} mode."))?; + cli.info(format!("The {project} was built in {} mode.", profile))?; cli.outro("Build completed successfully!")?; Ok(project) } @@ -115,32 +122,53 @@ impl Command { #[cfg(test)] mod tests { + use std::fs; + use std::fs::write; + use std::path::Path; use super::*; use cli::MockCli; + use strum::VariantArray; + + fn add_production_profile(project: &Path) -> anyhow::Result<()> { + let root_toml_path = project.join("Cargo.toml"); + let mut root_toml_content = fs::read_to_string(&root_toml_path)?; + root_toml_content.push_str( + r#" + [profile.production] + codegen-units = 1 + inherits = "release" + lto = true + "#, + ); + // Write the updated content back to the file + write(&root_toml_path, root_toml_content)?; + Ok(()) + } #[test] fn build_works() -> anyhow::Result<()> { let name = "hello_world"; let temp_dir = tempfile::tempdir()?; let path = temp_dir.path(); + let project_path = path.join(name); cmd("cargo", ["new", name, "--bin"]).dir(&path).run()?; + add_production_profile(&project_path)?; for package in [None, Some(name.to_string())] { - for release in [false, true] { + for profile in Profile::VARIANTS { let project = if package.is_some() { "package" } else { "project" }; - let mode = if release { "RELEASE" } else { "DEBUG" }; let mut cli = MockCli::new() .expect_intro(format!("Building your {project}")) - .expect_info(format!("The {project} was built in {mode} mode.")) + .expect_info(format!("The {project} was built in {profile} mode.")) .expect_outro("Build completed successfully!"); assert_eq!( Command::build( BuildArgs { command: None, - path: Some(path.join(name)), + path: Some(project_path.clone()), package: package.clone(), - release, + profile: Some(profile.clone()), id: None, }, &mut cli, diff --git a/crates/pop-cli/src/commands/build/parachain.rs b/crates/pop-cli/src/commands/build/parachain.rs index a6e47c67..cb58d3b3 100644 --- a/crates/pop-cli/src/commands/build/parachain.rs +++ b/crates/pop-cli/src/commands/build/parachain.rs @@ -16,9 +16,9 @@ pub struct BuildParachainCommand { /// The package to be built. #[arg(short = 'p', long)] pub(crate) package: Option, - /// For production, always build in release mode to exclude debug features. - #[clap(short, long, default_value = "true")] - pub(crate) release: bool, + /// Build profile [default: debug]. + #[clap(long, value_enum)] + pub(crate) profile: Option, /// Parachain ID to be used when generating the chain spec files. #[arg(short = 'i', long = "id")] pub(crate) id: Option, @@ -41,12 +41,13 @@ impl BuildParachainCommand { let project = if self.package.is_some() { "package" } else { "parachain" }; cli.intro(format!("Building your {project}"))?; + let profile = self.profile.unwrap_or(Profile::Debug); // Show warning if specified as deprecated. if !self.valid { cli.warning("NOTE: this command is deprecated. Please use `pop build` (or simply `pop b`) in future...")?; #[cfg(not(test))] sleep(Duration::from_secs(3)) - } else if !self.release { + } else if profile == Profile::Debug { cli.warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build...")?; #[cfg(not(test))] sleep(Duration::from_secs(3)) @@ -55,9 +56,8 @@ impl BuildParachainCommand { // Build parachain. cli.warning("NOTE: this may take some time...")?; let project_path = self.path.unwrap_or_else(|| PathBuf::from("./")); - let mode: Profile = self.release.into(); - let binary = build_parachain(&project_path, self.package, &mode, None)?; - cli.info(format!("The {project} was built in {mode} mode."))?; + let binary = build_parachain(&project_path, self.package, &profile, None)?; + cli.info(format!("The {project} was built in {} mode.", profile))?; cli.outro("Build completed successfully!")?; let generated_files = [format!("Binary generated at: {}", binary.display())]; let generated_files: Vec<_> = generated_files @@ -80,6 +80,8 @@ mod tests { use cli::MockCli; use duct::cmd; use std::{fs, io::Write, path::Path}; + use std::fs::write; + use strum::VariantArray; // Function that generates a Cargo.toml inside node directory for testing. fn generate_mock_node(temp_dir: &Path) -> anyhow::Result<()> { @@ -101,38 +103,55 @@ mod tests { Ok(()) } + fn add_production_profile(project: &Path) -> anyhow::Result<()> { + let root_toml_path = project.join("Cargo.toml"); + let mut root_toml_content = fs::read_to_string(&root_toml_path)?; + root_toml_content.push_str( + r#" + [profile.production] + codegen-units = 1 + inherits = "release" + lto = true + "#, + ); + // Write the updated content back to the file + write(&root_toml_path, root_toml_content)?; + Ok(()) + } + #[test] fn build_works() -> anyhow::Result<()> { let name = "hello_world"; let temp_dir = tempfile::tempdir()?; let path = temp_dir.path(); + let project_path = path.join(name); cmd("cargo", ["new", name, "--bin"]).dir(&path).run()?; - generate_mock_node(&temp_dir.path().join(name))?; + add_production_profile(&project_path)?; + generate_mock_node(&project_path)?; for package in [None, Some(name.to_string())] { - for release in [false, true] { + for profile in Profile::VARIANTS { for valid in [false, true] { let project = if package.is_some() { "package" } else { "parachain" }; - let mode = if release { Profile::Release } else { Profile::Debug }; let mut cli = MockCli::new() .expect_intro(format!("Building your {project}")) .expect_warning("NOTE: this may take some time...") - .expect_info(format!("The {project} was built in {mode} mode.")) + .expect_info(format!("The {project} was built in {profile} mode.")) .expect_outro("Build completed successfully!"); if !valid { cli = cli.expect_warning("NOTE: this command is deprecated. Please use `pop build` (or simply `pop b`) in future..."); } else { - if !release { + if profile == &Profile::Debug { cli = cli.expect_warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build..."); } } assert_eq!( BuildParachainCommand { - path: Some(path.join(name)), + path: Some(project_path.clone()), package: package.clone(), - release, + profile: Some(profile.clone()), id: None, valid, } diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 32ff7425..e5741151 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -20,8 +20,6 @@ use std::{ fs::create_dir_all, path::{Path, PathBuf}, }; -#[cfg(not(test))] -use std::{thread::sleep, time::Duration}; use strum::{EnumMessage, VariantArray}; use strum_macros::{AsRefStr, Display, EnumString}; @@ -138,10 +136,9 @@ pub struct BuildSpecCommand { /// the necessary directories will be created #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, - /// This command builds the node if it has not been built already. - /// For production, always build in release mode to exclude debug features. - #[arg(short = 'r', long)] - pub(crate) release: bool, + /// Build profile for the binary to generate the chain specification. + #[arg(short = 'r', long, value_enum)] + pub(crate) profile: Option, /// Parachain ID to be used when generating the chain spec files. #[arg(short = 'i', long)] pub(crate) id: Option, @@ -197,7 +194,7 @@ impl BuildSpecCommand { ) -> anyhow::Result { let BuildSpecCommand { output_file, - release, + profile, id, default_bootnode, chain_type, @@ -355,6 +352,28 @@ impl BuildSpecCommand { }, }; + // Prompt user for build profile. + let profile = match profile { + Some(profile) => profile, + None => { + let default = Profile::Release; + if prompt { + // Prompt for build profile. + let mut prompt = cliclack::select( + "Choose the build profile of the binary that should be used: ".to_string(), + ).initial_value(&default); + for profile in Profile::VARIANTS { + prompt = prompt.item( + profile, + profile.get_message().unwrap_or(profile.as_ref()), + profile.get_detailed_message().unwrap_or_default(), + ); + } + prompt.interact()?.clone() + } else { default } + }, + }; + // Prompt for default bootnode if not provided and chain type is Local or Live. let default_bootnode = if !default_bootnode { match chain_type { @@ -385,18 +404,9 @@ impl BuildSpecCommand { true }; - // Only prompt user for build profile if a live spec is being built on debug mode. - let release = if !release && matches!(chain_type, ChainType::Live) { - cli.confirm("Using Debug profile to build a Live specification. Should Release be used instead ?") - .initial_value(true) - .interact()? - } else { - release - }; - Ok(BuildSpec { output_file, - release, + profile, id, default_bootnode, chain_type, @@ -413,7 +423,7 @@ impl BuildSpecCommand { #[derive(Debug)] struct BuildSpec { output_file: PathBuf, - release: bool, + profile: Profile, id: u32, default_bootnode: bool, chain_type: ChainType, @@ -430,64 +440,59 @@ impl BuildSpec { // This function generates plain and raw chain spec files based on the provided configuration, // optionally including genesis state and runtime artifacts. If the node binary is missing, // it triggers a build process. - fn build(&self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { + fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { cli.intro("Building your chain spec")?; let mut generated_files = vec![]; + let BuildSpec { + ref output_file, ref profile, id, default_bootnode, ref chain, genesis_state, genesis_code, + .. } = self; + // Ensure binary is built. - let mode: Profile = self.release.into(); - let binary_path = ensure_binary_exists(cli, &mode)?; - if !self.release { - cli.warning( - "NOTE: this command defaults to DEBUG builds for development chain types. Please use `--release` (or simply `-r` for a release build...)", - )?; - #[cfg(not(test))] - sleep(Duration::from_secs(3)) - } + let binary_path = ensure_binary_exists(cli, &profile)?; let spinner = spinner(); spinner.start("Generating chain specification..."); // Generate chain spec. generate_plain_chain_spec( &binary_path, - &self.output_file, - self.default_bootnode, - &self.chain, + &output_file, + default_bootnode, + &chain, )?; // Customize spec based on input. self.customize()?; generated_files.push(format!( "Plain text chain specification file generated at: {}", - self.output_file.display() + &output_file.display() )); // Generate raw spec. spinner.set_message("Generating raw chain specification..."); - let spec_name = self - .output_file + let spec_name = &output_file .file_name() .and_then(|s| s.to_str()) .unwrap_or(DEFAULT_SPEC_NAME) .trim_end_matches(".json"); let raw_spec_name = format!("{spec_name}-raw.json"); let raw_chain_spec = - generate_raw_chain_spec(&binary_path, &self.output_file, &raw_spec_name)?; + generate_raw_chain_spec(&binary_path, &output_file, &raw_spec_name)?; generated_files.push(format!( "Raw chain specification file generated at: {}", raw_chain_spec.display() )); // Generate genesis artifacts. - if self.genesis_code { + if genesis_code { spinner.set_message("Generating genesis code..."); - let wasm_file_name = format!("para-{}.wasm", self.id); + let wasm_file_name = format!("para-{}.wasm", id); let wasm_file = export_wasm_file(&binary_path, &raw_chain_spec, &wasm_file_name)?; generated_files .push(format!("WebAssembly runtime file exported at: {}", wasm_file.display())); } - if self.genesis_state { + if genesis_state { spinner.set_message("Generating genesis state..."); - let genesis_file_name = format!("para-{}-genesis-state", self.id); + let genesis_file_name = format!("para-{}-genesis-state", id); let genesis_state_file = generate_genesis_state_file(&binary_path, &raw_chain_spec, &genesis_file_name)?; generated_files @@ -572,268 +577,3 @@ fn prepare_output_path(output_path: impl AsRef) -> anyhow::Result } Ok(output_path) } - -#[cfg(test)] -mod tests { - use super::{ChainType::*, RelayChain::*, *}; - use crate::cli::MockCli; - use std::{fs::create_dir_all, path::PathBuf}; - use tempfile::{tempdir, TempDir}; - - #[tokio::test] - async fn configure_build_spec_works() -> anyhow::Result<()> { - let chain = "local"; - let chain_type = Live; - let default_bootnode = true; - let genesis_code = true; - let genesis_state = true; - let output_file = "artifacts/chain-spec.json"; - let para_id = 4242; - let protocol_id = "pop"; - let relay = Polkadot; - let release = true; - - for build_spec_cmd in [ - // No flags used. - BuildSpecCommand::default(), - // All flags used. - BuildSpecCommand { - output_file: Some(PathBuf::from(output_file)), - release, - id: Some(para_id), - default_bootnode, - chain_type: Some(chain_type.clone()), - chain: Some(chain.to_string()), - relay: Some(relay.clone()), - protocol_id: Some(protocol_id.to_string()), - genesis_state, - genesis_code, - }, - ] { - let mut cli = MockCli::new(); - // If no flags are provided. - if build_spec_cmd.chain.is_none() { - cli = cli - .expect_input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)", chain.to_string()) - .expect_input( - "Name or path for the plain chain spec file:", output_file.to_string()) - .expect_input( - "What parachain ID should be used?", para_id.to_string()) - .expect_input( - "Enter the protocol ID that will identify your network:", protocol_id.to_string()) - .expect_select( - "Choose the chain type: ", - Some(false), - true, - Some(chain_types()), - chain_type.clone() as usize, - ).expect_select( - "Choose the relay your chain will be connecting to: ", - Some(false), - true, - Some(relays()), - relay.clone() as usize, - ).expect_confirm("Would you like to use local host as a bootnode ?", default_bootnode).expect_confirm("Should the genesis state file be generated ?", genesis_state).expect_confirm("Should the genesis code file be generated ?", genesis_code).expect_confirm( - "Using Debug profile to build a Live specification. Should Release be used instead ?", - release, - ); - } - let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; - assert_eq!(build_spec.chain, chain); - assert_eq!(build_spec.output_file, PathBuf::from(output_file)); - assert_eq!(build_spec.id, para_id); - assert_eq!(build_spec.release, release); - assert_eq!(build_spec.default_bootnode, default_bootnode); - assert_eq!(build_spec.chain_type, chain_type); - assert_eq!(build_spec.relay, relay); - assert_eq!(build_spec.protocol_id, protocol_id); - assert_eq!(build_spec.genesis_state, genesis_state); - assert_eq!(build_spec.genesis_code, genesis_code); - cli.verify()?; - } - Ok(()) - } - - #[tokio::test] - async fn configure_build_spec_with_existing_chain_file() -> anyhow::Result<()> { - let chain_type = Live; - let default_bootnode = true; - let genesis_code = true; - let genesis_state = true; - let output_file = "artifacts/chain-spec.json"; - let para_id = 4242; - let protocol_id = "pop"; - let relay = Polkadot; - let release = true; - - // Create a temporary file to act as the existing chain spec file. - let temp_dir = tempdir()?; - let chain_spec_path = temp_dir.path().join("existing-chain-spec.json"); - std::fs::write(&chain_spec_path, "{}")?; // Write a dummy JSON to the file - - // Whether to make changes to the provided chain spec file. - for changes in [true, false] { - for build_spec_cmd in [ - // No flags used except the provided chain spec file. - BuildSpecCommand { - chain: Some(chain_spec_path.to_string_lossy().to_string()), - ..Default::default() - }, - // All flags used. - BuildSpecCommand { - output_file: Some(PathBuf::from(output_file)), - release, - id: Some(para_id), - default_bootnode, - chain_type: Some(chain_type.clone()), - chain: Some(chain_spec_path.to_string_lossy().to_string()), - relay: Some(relay.clone()), - protocol_id: Some(protocol_id.to_string()), - genesis_state, - genesis_code, - }, - ] { - let mut cli = MockCli::new().expect_confirm( - "An existing chain spec file is provided. Do you want to make additional changes to it?", - changes, - ); - // When user wants to make changes to chain spec file via prompts and no flags - // provided. - let no_flags_used = build_spec_cmd.relay.is_none(); - if changes && no_flags_used { - if build_spec_cmd.id.is_none() { - cli = cli - .expect_input("What parachain ID should be used?", para_id.to_string()); - } - if build_spec_cmd.protocol_id.is_none() { - cli = cli.expect_input( - "Enter the protocol ID that will identify your network:", - protocol_id.to_string(), - ); - } - if build_spec_cmd.chain_type.is_none() { - cli = cli.expect_select( - "Choose the chain type: ", - Some(false), - true, - Some(chain_types()), - chain_type.clone() as usize, - ); - } - if build_spec_cmd.relay.is_none() { - cli = cli.expect_select( - "Choose the relay your chain will be connecting to: ", - Some(false), - true, - Some(relays()), - relay.clone() as usize, - ); - } - if !build_spec_cmd.default_bootnode { - cli = cli.expect_confirm( - "Would you like to use local host as a bootnode ?", - default_bootnode, - ); - } - if !build_spec_cmd.genesis_state { - cli = cli.expect_confirm( - "Should the genesis state file be generated ?", - genesis_state, - ); - } - if !build_spec_cmd.genesis_code { - cli = cli.expect_confirm( - "Should the genesis code file be generated ?", - genesis_code, - ); - } - if !build_spec_cmd.release { - cli = cli.expect_confirm( - "Using Debug profile to build a Live specification. Should Release be used instead ?", - release, - ); - } - } - let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; - if changes && no_flags_used { - assert_eq!(build_spec.id, para_id); - assert_eq!(build_spec.release, release); - assert_eq!(build_spec.default_bootnode, default_bootnode); - assert_eq!(build_spec.chain_type, chain_type); - assert_eq!(build_spec.relay, relay); - assert_eq!(build_spec.protocol_id, protocol_id); - assert_eq!(build_spec.genesis_state, genesis_state); - assert_eq!(build_spec.genesis_code, genesis_code); - } - // Assert that the chain spec file is correctly detected and used - assert_eq!(build_spec.chain, chain_spec_path.to_string_lossy()); - assert_eq!(build_spec.output_file, chain_spec_path); - cli.verify()?; - } - } - Ok(()) - } - - #[test] - fn prepare_output_path_works() -> anyhow::Result<()> { - // Create a temporary directory for testing - let temp_dir = TempDir::new()?; - let temp_dir_path = temp_dir.path(); - - let test_cases = [ - ("chain-spec.json", "chain-spec.json"), - ("existing_dir", "existing_dir.json"), - ("existing_dir/", "existing_dir/chain-spec.json"), - ("non_existing_dir", "non_existing_dir.json"), - ("non_existing_dir/", "non_existing_dir/chain-spec.json"), - ("some_file", "some_file.json"), - ("some_dir/some_file", "some_dir/some_file.json"), - ("existing_dir/subdir/", "existing_dir/subdir/chain-spec.json"), - ]; - for (input_str, expected_str) in &test_cases { - let input_path = temp_dir_path.join(input_str); - let expected_path = temp_dir_path.join(expected_str); - // Create directories for existing paths - if input_str.starts_with("existing_dir") { - let dir_to_create = if input_str.ends_with('/') { - input_path.clone() - } else { - input_path.parent().unwrap_or(&input_path).to_path_buf() - }; - create_dir_all(&dir_to_create)?; - } - let result = prepare_output_path(&input_path)?; - assert_eq!(result, expected_path); - // Ensure the parent directory exists - if let Some(parent_dir) = result.parent() { - assert!(parent_dir.exists()); - } - } - - Ok(()) - } - - fn relays() -> Vec<(String, String)> { - RelayChain::VARIANTS - .iter() - .map(|variant| { - ( - variant.get_message().unwrap_or(variant.as_ref()).into(), - variant.get_detailed_message().unwrap_or_default().into(), - ) - }) - .collect() - } - - fn chain_types() -> Vec<(String, String)> { - ChainType::VARIANTS - .iter() - .map(|variant| { - ( - variant.get_message().unwrap_or(variant.as_ref()).into(), - variant.get_detailed_message().unwrap_or_default().into(), - ) - }) - .collect() - } -} diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 108399db..1954d720 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -19,6 +19,7 @@ reqwest.workspace = true serde_json.workspace = true serde.workspace = true strum.workspace = true +strum_macros.workspace = true tar.workspace = true tempfile.workspace = true thiserror.workspace = true @@ -28,5 +29,4 @@ url.workspace = true [dev-dependencies] mockito.workspace = true -strum_macros.workspace = true -tempfile.workspace = true +tempfile.workspace = true \ No newline at end of file diff --git a/crates/pop-common/src/build.rs b/crates/pop-common/src/build.rs index bfe658b9..1169d247 100644 --- a/crates/pop-common/src/build.rs +++ b/crates/pop-common/src/build.rs @@ -1,15 +1,41 @@ -use std::{ - fmt, - path::{Path, PathBuf}, -}; +use std::{fmt, path::{Path, PathBuf}}; +use strum_macros::{VariantArray, AsRefStr, EnumMessage, EnumString}; /// Enum representing a build profile. -#[derive(Debug, PartialEq)] +#[derive( + AsRefStr, + Clone, + Default, + Debug, + EnumString, + EnumMessage, + VariantArray, + Eq, + PartialEq, +)] pub enum Profile { /// Debug profile, optimized for debugging. + #[strum( + serialize = "Debug", + message = "Debug", + detailed_message = "Optimized for debugging." + )] Debug, /// Release profile, optimized without any debugging functionality. + #[default] + #[strum( + serialize = "Release", + message = "Release", + detailed_message = "Optimized without any debugging functionality." + )] Release, + /// Production profile, optimized for ultimate performance. + #[strum( + serialize = "Production", + message = "Production", + detailed_message = "Optimized for ultimate performance." + )] + Production, } impl Profile { @@ -18,16 +44,7 @@ impl Profile { match self { Profile::Release => path.join("target/release"), Profile::Debug => path.join("target/debug"), - } - } -} - -impl From for Profile { - fn from(release: bool) -> Self { - if release { - Profile::Release - } else { - Profile::Debug + Profile::Production => path.join("target/production"), } } } @@ -37,6 +54,8 @@ impl fmt::Display for Profile { match self { Self::Debug => write!(f, "DEBUG"), Self::Release => write!(f, "RELEASE"), + Self::Production => write!(f, "PRODUCTION"), } } } + diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index 95f37453..1a3158f4 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -336,9 +336,10 @@ mod tests { use anyhow::Result; use pop_common::{manifest::Dependency, set_executable_permission}; use std::{fs, fs::write, io::Write, path::Path}; - use tempfile::{tempdir, Builder}; + use tempfile::{tempdir, Builder, TempDir}; + use strum::VariantArray; - fn setup_template_and_instantiate() -> Result { + fn setup_template_and_instantiate() -> Result { let temp_dir = tempdir().expect("Failed to create temp dir"); let config = Config { symbol: "DOT".to_string(), @@ -361,9 +362,9 @@ mod tests { } // Function that generates a Cargo.toml inside node directory for testing. - fn generate_mock_node(temp_dir: &Path) -> Result<(), Error> { + fn generate_mock_node(temp_dir: &Path, name: Option<&str>) -> Result { // Create a node directory - let target_dir = temp_dir.join("node"); + let target_dir = temp_dir.join(name.unwrap_or("node")); fs::create_dir(&target_dir)?; // Create a Cargo.toml file let mut toml_file = fs::File::create(target_dir.join("Cargo.toml"))?; @@ -378,7 +379,7 @@ mod tests { "# )?; - Ok(()) + Ok(target_dir) } // Function that fetch a binary from pop network @@ -387,13 +388,13 @@ mod tests { writeln!( config.as_file(), r#" -[relaychain] -chain = "paseo-local" + [relaychain] + chain = "paseo-local" -[[parachains]] -id = 4385 -default_command = "pop-node" -"# + [[parachains]] + id = 4385 + default_command = "pop-node" + "# )?; let mut zombienet = Zombienet::new(&cache, config.path().to_str().unwrap(), None, None, None, None, None) @@ -416,20 +417,45 @@ default_command = "pop-node" Ok(binary_path) } + fn add_production_profile(project: &Path) -> Result<()> { + let root_toml_path = project.join("Cargo.toml"); + let mut root_toml_content = fs::read_to_string(&root_toml_path)?; + root_toml_content.push_str( + r#" + [profile.production] + codegen-units = 1 + inherits = "release" + lto = true + "#, + ); + // Write the updated content back to the file + write(&root_toml_path, root_toml_content)?; + Ok(()) + } + #[test] fn build_parachain_works() -> Result<()> { - let temp_dir = tempdir()?; let name = "parachain_template_node"; + let temp_dir = tempdir()?; cmd("cargo", ["new", name, "--bin"]).dir(temp_dir.path()).run()?; - generate_mock_node(&temp_dir.path().join(name))?; - let binary = build_parachain(&temp_dir.path().join(name), None, &Profile::Release, None)?; - let target_directory = temp_dir.path().join(name).join("target/release"); - assert!(target_directory.exists()); - assert!(target_directory.join("parachain_template_node").exists()); - assert_eq!( - binary.display().to_string(), - target_directory.join("parachain_template_node").display().to_string() - ); + let project = temp_dir.path().join(name); + add_production_profile(&project)?; + for node in vec![None, Some("custom_node")] { + let node_path = generate_mock_node(&project, node)?; + for package in vec![None, Some(String::from("parachain_template_node"))] { + for profile in Profile::VARIANTS { + let node_path = node.map(|_| node_path.as_path()); + let binary = build_parachain(&project, package.clone(), &profile, node_path)?; + let target_directory = profile.target_directory(&project); + assert!(target_directory.exists()); + assert!(target_directory.join("parachain_template_node").exists()); + assert_eq!( + binary.display().to_string(), + target_directory.join("parachain_template_node").display().to_string() + ); + } + } + } Ok(()) } From 91fa45e60467cab0be59f87c90f089c8306ccc77 Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 27 Nov 2024 20:29:32 +0100 Subject: [PATCH 26/35] refactor: improve profile experience --- crates/pop-cli/src/commands/build/spec.rs | 17 +++++++---------- crates/pop-common/src/build.rs | 12 ++++++------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index e5741151..1c420194 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -46,20 +46,20 @@ pub(crate) enum ChainType { // A development chain that runs mainly on one node. #[default] #[strum( - serialize = "Development", + serialize = "development", message = "Development", detailed_message = "Meant for a development chain that runs mainly on one node." )] Development, // A local chain that runs locally on multiple nodes for testing purposes. #[strum( - serialize = "Local", + serialize = "local", message = "Local", detailed_message = "Meant for a local chain that runs locally on multiple nodes for testing purposes." )] Local, // A live chain. - #[strum(serialize = "Live", message = "Live", detailed_message = "Meant for a live chain.")] + #[strum(serialize = "live", message = "Live", detailed_message = "Meant for a live chain.")] Live, } @@ -137,7 +137,7 @@ pub struct BuildSpecCommand { #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, /// Build profile for the binary to generate the chain specification. - #[arg(short = 'r', long, value_enum)] + #[arg(long, value_enum)] pub(crate) profile: Option, /// Parachain ID to be used when generating the chain spec files. #[arg(short = 'i', long)] @@ -346,9 +346,7 @@ impl BuildSpecCommand { .placeholder(&default) .default_input(&default) .interact()? - } else { - default - } + } else { default } }, }; @@ -442,16 +440,15 @@ impl BuildSpec { // it triggers a build process. fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { cli.intro("Building your chain spec")?; - let mut generated_files = vec![]; - let BuildSpec { ref output_file, ref profile, id, default_bootnode, ref chain, genesis_state, genesis_code, .. } = self; - // Ensure binary is built. let binary_path = ensure_binary_exists(cli, &profile)?; + let spinner = spinner(); spinner.start("Generating chain specification..."); + let mut generated_files = vec![]; // Generate chain spec. generate_plain_chain_spec( diff --git a/crates/pop-common/src/build.rs b/crates/pop-common/src/build.rs index 1169d247..3e5a4b56 100644 --- a/crates/pop-common/src/build.rs +++ b/crates/pop-common/src/build.rs @@ -16,7 +16,7 @@ use strum_macros::{VariantArray, AsRefStr, EnumMessage, EnumString}; pub enum Profile { /// Debug profile, optimized for debugging. #[strum( - serialize = "Debug", + serialize = "debug", message = "Debug", detailed_message = "Optimized for debugging." )] @@ -24,14 +24,14 @@ pub enum Profile { /// Release profile, optimized without any debugging functionality. #[default] #[strum( - serialize = "Release", + serialize = "release", message = "Release", detailed_message = "Optimized without any debugging functionality." )] Release, /// Production profile, optimized for ultimate performance. #[strum( - serialize = "Production", + serialize = "production", message = "Production", detailed_message = "Optimized for ultimate performance." )] @@ -52,9 +52,9 @@ impl Profile { impl fmt::Display for Profile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Debug => write!(f, "DEBUG"), - Self::Release => write!(f, "RELEASE"), - Self::Production => write!(f, "PRODUCTION"), + Self::Debug => write!(f, "debug"), + Self::Release => write!(f, "release"), + Self::Production => write!(f, "production"), } } } From c3898b05c58ce63a0ba6601b9c5adf4a47d03671 Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:57:38 +0100 Subject: [PATCH 27/35] chore: feedback and rebase --- Cargo.toml | 1 + crates/pop-cli/src/commands/build/mod.rs | 88 ++-- .../pop-cli/src/commands/build/parachain.rs | 18 +- crates/pop-cli/src/commands/build/spec.rs | 431 +++++++++++++++--- crates/pop-common/Cargo.toml | 3 +- crates/pop-common/src/build.rs | 46 +- crates/pop-common/src/manifest.rs | 82 +++- crates/pop-parachains/src/build.rs | 6 +- 8 files changed, 530 insertions(+), 145 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a515c6b..a5a8c7c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ tar = "0.4.40" tempfile = "3.10" thiserror = "1.0.58" tokio-test = "0.4.4" +toml = "0.5.0" # networking reqwest = { version = "0.12", features = ["json"] } diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index 5d752980..6efaaf43 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -5,10 +5,10 @@ use clap::{Args, Subcommand}; #[cfg(feature = "contract")] use contract::BuildContractCommand; use duct::cmd; +use pop_common::Profile; use std::path::PathBuf; #[cfg(feature = "parachain")] use {parachain::BuildParachainCommand, spec::BuildSpecCommand}; -use pop_common::Profile; #[cfg(feature = "contract")] pub(crate) mod contract; @@ -29,6 +29,9 @@ pub(crate) struct BuildArgs { /// The package to be built. #[arg(short = 'p', long)] pub(crate) package: Option, + /// For production, always build in release mode to exclude debug features. + #[clap(short = 'r', long, conflicts_with = "profile")] + pub(crate) release: bool, /// Build profile [default: debug]. #[clap(long, value_enum)] pub(crate) profile: Option, @@ -63,23 +66,26 @@ impl Command { #[cfg(feature = "contract")] if pop_contracts::is_supported(args.path.as_deref())? { // All commands originating from root command are valid - let release = match args.profile.unwrap_or(Profile::Debug) { - Profile::Debug => false, - _ => true, + let release = match args.profile { + Some(profile) => profile.into(), + None => args.release, }; - BuildContractCommand { path: args.path, release, valid: true } - .execute()?; + BuildContractCommand { path: args.path, release, valid: true }.execute()?; return Ok("contract"); } // If only parachain feature enabled, build as parachain #[cfg(feature = "parachain")] if pop_parachains::is_supported(args.path.as_deref())? { + let profile = match args.profile { + Some(profile) => profile, + None => args.release.into(), + }; // All commands originating from root command are valid BuildParachainCommand { path: args.path, package: args.package, - profile: args.profile, + profile: Some(profile), id: args.id, valid: true, } @@ -122,29 +128,11 @@ impl Command { #[cfg(test)] mod tests { - use std::fs; - use std::fs::write; - use std::path::Path; use super::*; use cli::MockCli; + use pop_common::manifest::add_production_profile; use strum::VariantArray; - fn add_production_profile(project: &Path) -> anyhow::Result<()> { - let root_toml_path = project.join("Cargo.toml"); - let mut root_toml_content = fs::read_to_string(&root_toml_path)?; - root_toml_content.push_str( - r#" - [profile.production] - codegen-units = 1 - inherits = "release" - lto = true - "#, - ); - // Write the updated content back to the file - write(&root_toml_path, root_toml_content)?; - Ok(()) - } - #[test] fn build_works() -> anyhow::Result<()> { let name = "hello_world"; @@ -155,31 +143,33 @@ mod tests { add_production_profile(&project_path)?; for package in [None, Some(name.to_string())] { - for profile in Profile::VARIANTS { - let project = if package.is_some() { "package" } else { "project" }; - let mut cli = MockCli::new() - .expect_intro(format!("Building your {project}")) - .expect_info(format!("The {project} was built in {profile} mode.")) - .expect_outro("Build completed successfully!"); - - assert_eq!( - Command::build( - BuildArgs { - command: None, - path: Some(project_path.clone()), - package: package.clone(), - profile: Some(profile.clone()), - id: None, - }, - &mut cli, - )?, - project - ); - - cli.verify()?; + for release in [true, false] { + for profile in Profile::VARIANTS { + let profile = if release { Profile::Release } else { Profile::Debug }; + let project = if package.is_some() { "package" } else { "project" }; + let mut cli = MockCli::new() + .expect_intro(format!("Building your {project}")) + .expect_info(format!("The {project} was built in {profile} mode.")) + .expect_outro("Build completed successfully!"); + + assert_eq!( + Command::build( + BuildArgs { + command: None, + path: Some(project_path.clone()), + package: package.clone(), + release, + profile: Some(profile.clone()), + id: None, + }, + &mut cli, + )?, + project + ); + cli.verify()?; + } } } - Ok(()) } } diff --git a/crates/pop-cli/src/commands/build/parachain.rs b/crates/pop-cli/src/commands/build/parachain.rs index cb58d3b3..67000e62 100644 --- a/crates/pop-cli/src/commands/build/parachain.rs +++ b/crates/pop-cli/src/commands/build/parachain.rs @@ -79,8 +79,8 @@ mod tests { use super::*; use cli::MockCli; use duct::cmd; + use pop_common::manifest::add_production_profile; use std::{fs, io::Write, path::Path}; - use std::fs::write; use strum::VariantArray; // Function that generates a Cargo.toml inside node directory for testing. @@ -103,22 +103,6 @@ mod tests { Ok(()) } - fn add_production_profile(project: &Path) -> anyhow::Result<()> { - let root_toml_path = project.join("Cargo.toml"); - let mut root_toml_content = fs::read_to_string(&root_toml_path)?; - root_toml_content.push_str( - r#" - [profile.production] - codegen-units = 1 - inherits = "release" - lto = true - "#, - ); - // Write the updated content back to the file - write(&root_toml_path, root_toml_content)?; - Ok(()) - } - #[test] fn build_works() -> anyhow::Result<()> { let name = "hello_world"; diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 1c420194..3b5cd8d6 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -19,6 +19,8 @@ use std::{ env::current_dir, fs::create_dir_all, path::{Path, PathBuf}, + thread::sleep, + time::Duration, }; use strum::{EnumMessage, VariantArray}; use strum_macros::{AsRefStr, Display, EnumString}; @@ -46,20 +48,20 @@ pub(crate) enum ChainType { // A development chain that runs mainly on one node. #[default] #[strum( - serialize = "development", + serialize = "Development", message = "Development", detailed_message = "Meant for a development chain that runs mainly on one node." )] Development, // A local chain that runs locally on multiple nodes for testing purposes. #[strum( - serialize = "local", + serialize = "Local", message = "Local", detailed_message = "Meant for a local chain that runs locally on multiple nodes for testing purposes." )] Local, // A live chain. - #[strum(serialize = "live", message = "Live", detailed_message = "Meant for a live chain.")] + #[strum(serialize = "Live", message = "Live", detailed_message = "Meant for a live chain.")] Live, } @@ -136,6 +138,10 @@ pub struct BuildSpecCommand { /// the necessary directories will be created #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, + // TODO: remove at release ... + /// [DEPRECATED] use `profile`. + #[arg(short = 'r', long, conflicts_with = "profile")] + pub(crate) release: bool, /// Build profile for the binary to generate the chain specification. #[arg(long, value_enum)] pub(crate) profile: Option, @@ -195,6 +201,7 @@ impl BuildSpecCommand { let BuildSpecCommand { output_file, profile, + release, id, default_bootnode, chain_type, @@ -331,6 +338,33 @@ impl BuildSpecCommand { }, }; + // Prompt user for build profile. + let mut profile = match profile { + Some(profile) => profile, + None => { + let default = Profile::Release; + if prompt && !release { + // Prompt for build profile. + let mut prompt = cli + .select( + "Choose the build profile of the binary that should be used: " + .to_string(), + ) + .initial_value(&default); + for profile in Profile::VARIANTS { + prompt = prompt.item( + profile, + profile.get_message().unwrap_or(profile.as_ref()), + profile.get_detailed_message().unwrap_or_default(), + ); + } + prompt.interact()?.clone() + } else { + default + } + }, + }; + // Protocol id. let protocol_id = match protocol_id { Some(protocol_id) => protocol_id, @@ -346,29 +380,9 @@ impl BuildSpecCommand { .placeholder(&default) .default_input(&default) .interact()? - } else { default } - }, - }; - - // Prompt user for build profile. - let profile = match profile { - Some(profile) => profile, - None => { - let default = Profile::Release; - if prompt { - // Prompt for build profile. - let mut prompt = cliclack::select( - "Choose the build profile of the binary that should be used: ".to_string(), - ).initial_value(&default); - for profile in Profile::VARIANTS { - prompt = prompt.item( - profile, - profile.get_message().unwrap_or(profile.as_ref()), - profile.get_detailed_message().unwrap_or_default(), - ); - } - prompt.interact()?.clone() - } else { default } + } else { + default + } }, }; @@ -402,6 +416,13 @@ impl BuildSpecCommand { true }; + if release { + cli.warning("NOTE: release flag is deprecated. Use `--profile` instead.")?; + #[cfg(not(test))] + sleep(Duration::from_secs(3)); + profile = Profile::Release; + } + Ok(BuildSpec { output_file, profile, @@ -440,23 +461,24 @@ impl BuildSpec { // it triggers a build process. fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { cli.intro("Building your chain spec")?; + let mut generated_files = vec![]; let BuildSpec { - ref output_file, ref profile, id, default_bootnode, ref chain, genesis_state, genesis_code, - .. } = self; + ref output_file, + ref profile, + id, + default_bootnode, + ref chain, + genesis_state, + genesis_code, + .. + } = self; // Ensure binary is built. let binary_path = ensure_binary_exists(cli, &profile)?; - let spinner = spinner(); spinner.start("Generating chain specification..."); - let mut generated_files = vec![]; // Generate chain spec. - generate_plain_chain_spec( - &binary_path, - &output_file, - default_bootnode, - &chain, - )?; + generate_plain_chain_spec(&binary_path, &output_file, default_bootnode, &chain)?; // Customize spec based on input. self.customize()?; generated_files.push(format!( @@ -472,8 +494,7 @@ impl BuildSpec { .unwrap_or(DEFAULT_SPEC_NAME) .trim_end_matches(".json"); let raw_spec_name = format!("{spec_name}-raw.json"); - let raw_chain_spec = - generate_raw_chain_spec(&binary_path, &output_file, &raw_spec_name)?; + let raw_chain_spec = generate_raw_chain_spec(&binary_path, &output_file, &raw_spec_name)?; generated_files.push(format!( "Raw chain specification file generated at: {}", raw_chain_spec.display() @@ -540,37 +561,335 @@ fn ensure_binary_exists( // Prepare the output path provided. fn prepare_output_path(output_path: impl AsRef) -> anyhow::Result { let mut output_path = output_path.as_ref().to_path_buf(); - // Convert to string to check for trailing slash. - let output_path_str = output_path.to_string_lossy(); - let ends_with_slash = output_path_str.ends_with(std::path::MAIN_SEPARATOR); - // Check if the path has an extension. - let has_extension = output_path.extension().is_some(); // Check if the path ends with '.json' let is_json_file = output_path .extension() .and_then(|ext| ext.to_str()) .map(|ext| ext.eq_ignore_ascii_case("json")) .unwrap_or(false); - if ends_with_slash { + + if !is_json_file { // Treat as directory. if !output_path.exists() { create_dir_all(&output_path)?; } output_path.push(DEFAULT_SPEC_NAME); - } else if !has_extension { - // No extension, treat as file, set extension to '.json'. - output_path.set_extension("json"); - } else if !is_json_file { - // Has an extension but not '.json', change extension to '.json'. - output_path.set_extension("json"); + } else { + // Treat as file. + if let Some(parent_dir) = output_path.parent() { + if !parent_dir.exists() { + create_dir_all(parent_dir)?; + } + } } - // Else: Ends with '.json', treat as file (no changes needed). + Ok(output_path) +} + +#[cfg(test)] +mod tests { + use super::{ChainType::*, RelayChain::*, *}; + use crate::cli::MockCli; + use std::{fs::create_dir_all, path::PathBuf}; + use tempfile::{tempdir, TempDir}; - // After modifications, create the parent directory if it doesn't exist. - if let Some(parent_dir) = output_path.parent() { - if !parent_dir.exists() { - create_dir_all(parent_dir)?; + #[tokio::test] + async fn configure_build_spec_works() -> anyhow::Result<()> { + let chain = "local"; + let chain_type = Live; + let default_bootnode = true; + let genesis_code = true; + let genesis_state = true; + let output_file = "artifacts/chain-spec.json"; + let para_id = 4242; + let protocol_id = "pop"; + let relay = Polkadot; + let release = false; + let profile = Profile::Production; + + for build_spec_cmd in [ + // No flags used. + BuildSpecCommand::default(), + // All flags used. + BuildSpecCommand { + output_file: Some(PathBuf::from(output_file)), + profile: Some(profile.clone()), + release, + id: Some(para_id), + default_bootnode, + chain_type: Some(chain_type.clone()), + chain: Some(chain.to_string()), + relay: Some(relay.clone()), + protocol_id: Some(protocol_id.to_string()), + genesis_state, + genesis_code, + }, + ] { + let mut cli = MockCli::new(); + // If no flags are provided. + if build_spec_cmd.chain.is_none() { + cli = cli + .expect_input("Provide the chain specification to use (e.g. dev, local, custom or a path to an existing file)", chain.to_string()) + .expect_input( + "Name or path for the plain chain spec file:", output_file.to_string()) + .expect_input( + "What parachain ID should be used?", para_id.to_string()) + .expect_input( + "Enter the protocol ID that will identify your network:", protocol_id.to_string()) + .expect_select( + "Choose the chain type: ", + Some(false), + true, + Some(chain_types()), + chain_type.clone() as usize, + ).expect_select( + "Choose the relay your chain will be connecting to: ", + Some(false), + true, + Some(relays()), + relay.clone() as usize, + ).expect_select( + "Choose the build profile of the binary that should be used: ", + Some(false), + true, + Some(profiles()), + profile.clone() as usize + ).expect_confirm("Would you like to use local host as a bootnode ?", default_bootnode + ).expect_confirm("Should the genesis state file be generated ?", genesis_state + ).expect_confirm("Should the genesis code file be generated ?", genesis_code); + } + let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; + assert_eq!(build_spec.chain, chain); + assert_eq!(build_spec.output_file, PathBuf::from(output_file)); + assert_eq!(build_spec.id, para_id); + assert_eq!(build_spec.profile, profile); + assert_eq!(build_spec.default_bootnode, default_bootnode); + assert_eq!(build_spec.chain_type, chain_type); + assert_eq!(build_spec.relay, relay); + assert_eq!(build_spec.protocol_id, protocol_id); + assert_eq!(build_spec.genesis_state, genesis_state); + assert_eq!(build_spec.genesis_code, genesis_code); + cli.verify()?; } + Ok(()) + } + + #[tokio::test] + async fn configure_build_spec_with_existing_chain_file() -> anyhow::Result<()> { + let chain_type = Live; + let default_bootnode = true; + let genesis_code = true; + let genesis_state = true; + let output_file = "artifacts/chain-spec.json"; + let para_id = 4242; + let protocol_id = "pop"; + let relay = Polkadot; + let release = false; + let profile = Profile::Production; + + // Create a temporary file to act as the existing chain spec file. + let temp_dir = tempdir()?; + let chain_spec_path = temp_dir.path().join("existing-chain-spec.json"); + std::fs::write(&chain_spec_path, "{}")?; // Write a dummy JSON to the file. + + // Whether to make changes to the provided chain spec file. + for changes in [true, false] { + for build_spec_cmd in [ + // No flags used except the provided chain spec file. + BuildSpecCommand { + chain: Some(chain_spec_path.to_string_lossy().to_string()), + ..Default::default() + }, + // All flags used. + BuildSpecCommand { + output_file: Some(PathBuf::from(output_file)), + profile: Some(profile.clone()), + release, + id: Some(para_id), + default_bootnode, + chain_type: Some(chain_type.clone()), + chain: Some(chain_spec_path.to_string_lossy().to_string()), + relay: Some(relay.clone()), + protocol_id: Some(protocol_id.to_string()), + genesis_state, + genesis_code, + }, + ] { + let mut cli = MockCli::new().expect_confirm( + "An existing chain spec file is provided. Do you want to make additional changes to it?", + changes, + ); + // When user wants to make changes to chain spec file via prompts and no flags + // provided. + let no_flags_used = build_spec_cmd.relay.is_none(); + if changes && no_flags_used { + if build_spec_cmd.id.is_none() { + cli = cli + .expect_input("What parachain ID should be used?", para_id.to_string()); + } + if build_spec_cmd.protocol_id.is_none() { + cli = cli.expect_input( + "Enter the protocol ID that will identify your network:", + protocol_id.to_string(), + ); + } + if build_spec_cmd.chain_type.is_none() { + cli = cli.expect_select( + "Choose the chain type: ", + Some(false), + true, + Some(chain_types()), + chain_type.clone() as usize, + ); + } + if build_spec_cmd.relay.is_none() { + cli = cli.expect_select( + "Choose the relay your chain will be connecting to: ", + Some(false), + true, + Some(relays()), + relay.clone() as usize, + ); + } + if build_spec_cmd.profile.is_none() { + cli = cli.expect_select( + "Choose the build profile of the binary that should be used: ", + Some(false), + true, + Some(profiles()), + profile.clone() as usize, + ); + } + if !build_spec_cmd.default_bootnode { + cli = cli.expect_confirm( + "Would you like to use local host as a bootnode ?", + default_bootnode, + ); + } + if !build_spec_cmd.genesis_state { + cli = cli.expect_confirm( + "Should the genesis state file be generated ?", + genesis_state, + ); + } + if !build_spec_cmd.genesis_code { + cli = cli.expect_confirm( + "Should the genesis code file be generated ?", + genesis_code, + ); + } + } + let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; + if changes && no_flags_used { + assert_eq!(build_spec.id, para_id); + assert_eq!(build_spec.profile, profile); + assert_eq!(build_spec.default_bootnode, default_bootnode); + assert_eq!(build_spec.chain_type, chain_type); + assert_eq!(build_spec.relay, relay); + assert_eq!(build_spec.protocol_id, protocol_id); + assert_eq!(build_spec.genesis_state, genesis_state); + assert_eq!(build_spec.genesis_code, genesis_code); + } + // Assert that the chain spec file is correctly detected and used. + assert_eq!(build_spec.chain, chain_spec_path.to_string_lossy()); + assert_eq!(build_spec.output_file, chain_spec_path); + cli.verify()?; + } + } + Ok(()) + } + + #[tokio::test] + async fn configure_build_spec_release_deprecated_works() -> anyhow::Result<()> { + // Create a temporary file to act as the existing chain spec file. + let temp_dir = tempdir()?; + let chain_spec_path = temp_dir.path().join("existing-chain-spec.json"); + std::fs::write(&chain_spec_path, "{}")?; // Write a dummy JSON to the file. + // Use the deprcrated release flag. + let release = true; + let build_spec_cmd = BuildSpecCommand { + release, + chain: Some(chain_spec_path.to_string_lossy().to_string()), + ..Default::default() + }; + let mut cli = + MockCli::new().expect_confirm( + "An existing chain spec file is provided. Do you want to make additional changes to it?", + false, + ).expect_warning("NOTE: release flag is deprecated. Use `--profile` instead."); + let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; + cli.verify()?; + Ok(()) + } + + #[test] + fn prepare_output_path_works() -> anyhow::Result<()> { + // Create a temporary directory for testing. + let temp_dir = TempDir::new()?; + let temp_dir_path = temp_dir.path(); + + // No directory path. + let file = temp_dir_path.join("chain-spec.json"); + let result = prepare_output_path(&file)?; + // Expected path: chain-spec.json + assert_eq!(result, file); + + // Existing directory Path. + for dir in ["existing_dir", "existing_dir/", "existing_dir_json"] { + let existing_dir = temp_dir_path.join(dir); + create_dir_all(&existing_dir)?; + let result = prepare_output_path(&existing_dir)?; + // Expected path: existing_dir/chain-spec.json + let expected_path = existing_dir.join(DEFAULT_SPEC_NAME); + assert_eq!(result, expected_path); + } + + // Non-existing directory Path. + for dir in ["non_existing_dir", "non_existing_dir/", "non_existing_dir_json"] { + let non_existing_dir = temp_dir_path.join(dir); + let result = prepare_output_path(&non_existing_dir)?; + // Expected path: non_existing_dir/chain-spec.json + let expected_path = non_existing_dir.join(DEFAULT_SPEC_NAME); + assert_eq!(result, expected_path); + // The directory should now exist. + assert!(result.parent().unwrap().exists()); + } + + Ok(()) + } + + fn relays() -> Vec<(String, String)> { + RelayChain::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } + + fn chain_types() -> Vec<(String, String)> { + ChainType::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() + } + + fn profiles() -> Vec<(String, String)> { + Profile::VARIANTS + .iter() + .map(|variant| { + ( + variant.get_message().unwrap_or(variant.as_ref()).into(), + variant.get_detailed_message().unwrap_or_default().into(), + ) + }) + .collect() } - Ok(output_path) } diff --git a/crates/pop-common/Cargo.toml b/crates/pop-common/Cargo.toml index 1954d720..87074a60 100644 --- a/crates/pop-common/Cargo.toml +++ b/crates/pop-common/Cargo.toml @@ -26,7 +26,8 @@ thiserror.workspace = true tokio.workspace = true toml_edit.workspace = true url.workspace = true +toml.workspace = true [dev-dependencies] mockito.workspace = true -tempfile.workspace = true \ No newline at end of file +tempfile.workspace = true diff --git a/crates/pop-common/src/build.rs b/crates/pop-common/src/build.rs index 3e5a4b56..6384b9e6 100644 --- a/crates/pop-common/src/build.rs +++ b/crates/pop-common/src/build.rs @@ -1,25 +1,14 @@ -use std::{fmt, path::{Path, PathBuf}}; -use strum_macros::{VariantArray, AsRefStr, EnumMessage, EnumString}; +use std::{ + fmt, + path::{Path, PathBuf}, +}; +use strum_macros::{AsRefStr, EnumMessage, EnumString, VariantArray}; /// Enum representing a build profile. -#[derive( - AsRefStr, - Clone, - Default, - Debug, - EnumString, - EnumMessage, - VariantArray, - Eq, - PartialEq, -)] +#[derive(AsRefStr, Clone, Default, Debug, EnumString, EnumMessage, VariantArray, Eq, PartialEq)] pub enum Profile { /// Debug profile, optimized for debugging. - #[strum( - serialize = "debug", - message = "Debug", - detailed_message = "Optimized for debugging." - )] + #[strum(serialize = "debug", message = "Debug", detailed_message = "Optimized for debugging.")] Debug, /// Release profile, optimized without any debugging functionality. #[default] @@ -49,6 +38,26 @@ impl Profile { } } +impl From for bool { + fn from(value: Profile) -> Self { + if value == Profile::Debug { + false + } else { + true + } + } +} + +impl From for Profile { + fn from(value: bool) -> Self { + if value { + Profile::Release + } else { + Profile::Debug + } + } +} + impl fmt::Display for Profile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -58,4 +67,3 @@ impl fmt::Display for Profile { } } } - diff --git a/crates/pop-common/src/manifest.rs b/crates/pop-common/src/manifest.rs index ff84c1be..afe3b576 100644 --- a/crates/pop-common/src/manifest.rs +++ b/crates/pop-common/src/manifest.rs @@ -2,7 +2,7 @@ use crate::Error; use anyhow; -pub use cargo_toml::{Dependency, Manifest}; +pub use cargo_toml::{Dependency, LtoSetting, Manifest, Profile, Profiles}; use std::{ fs::{read_to_string, write}, path::{Path, PathBuf}, @@ -58,6 +58,7 @@ pub fn find_workspace_toml(target_dir: &Path) -> Option { /// This function is used to add a crate to a workspace. /// # Arguments +/// /// * `workspace_toml` - The path to the workspace `Cargo.toml` /// * `crate_path`: The path to the crate that should be added to the workspace pub fn add_crate_to_workspace(workspace_toml: &Path, crate_path: &Path) -> anyhow::Result<()> { @@ -97,6 +98,46 @@ pub fn add_crate_to_workspace(workspace_toml: &Path, crate_path: &Path) -> anyho Ok(()) } +use std::fs; + +/// Adds a "production" profile to the Cargo.toml manifest if it doesn't already exist. +/// +/// # Arguments +/// * `project` - The path to the root of the Cargo project containing the Cargo.toml. +pub fn add_production_profile(project: &Path) -> anyhow::Result<()> { + let root_toml_path = project.join("Cargo.toml"); + let mut manifest = Manifest::from_path(&root_toml_path)?; + // Check if the `production` profile already exists. + if manifest.profile.custom.get("production").is_some() { + return Ok(()); + } + // Create the production profile with required fields. + let production_profile = Profile { + opt_level: None, + debug: None, + split_debuginfo: None, + rpath: None, + lto: Some(LtoSetting::Fat), + debug_assertions: None, + codegen_units: Some(1), + panic: None, + incremental: None, + overflow_checks: None, + strip: None, + package: std::collections::BTreeMap::new(), + build_override: None, + inherits: Some("release".to_string()), + }; + // Insert the new profile into the custom profiles + manifest.profile.custom.insert("production".to_string(), production_profile); + + // Serialize the updated manifest and write it back to the file + let toml_string = toml::to_string(&manifest)?; + write(&root_toml_path, toml_string)?; + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -419,4 +460,43 @@ mod tests { ); assert!(add_crate.is_err()); } + + #[test] + fn add_production_profile_works() { + let test_builder = TestBuilder::default().add_workspace().add_workspace_cargo_toml( + r#"[profile.release] + opt-level = 3 + "#, + ); + + let binding = test_builder.workspace.expect("Workspace should exist"); + let project_path = binding.path(); + let cargo_toml_path = project_path.join("Cargo.toml"); + + // Call the function to add the production profile + let result = add_production_profile(project_path); + println!("{:?}", result); + assert!(result.is_ok()); + + // Verify the production profile is added + let manifest = + Manifest::from_path(&cargo_toml_path).expect("Should parse updated Cargo.toml"); + let production_profile = manifest + .profile + .custom + .get("production") + .expect("Production profile should exist"); + assert_eq!(production_profile.codegen_units, Some(1)); + assert_eq!(production_profile.inherits.as_deref(), Some("release")); + assert_eq!(production_profile.lto, Some(LtoSetting::Fat)); + + // Test idempotency: Running the function again should not modify the manifest + let initial_toml_content = + read_to_string(&cargo_toml_path).expect("Cargo.toml should be readable"); + let second_result = add_production_profile(project_path); + assert!(second_result.is_ok()); + let final_toml_content = + read_to_string(&cargo_toml_path).expect("Cargo.toml should be readable"); + assert_eq!(initial_toml_content, final_toml_content); + } } diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index 1a3158f4..3746f5c0 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -31,8 +31,10 @@ pub fn build_parachain( args.push("--package"); args.push(package) } - if matches!(profile, &Profile::Release) { + if profile == &Profile::Release { args.push("--release"); + } else if profile == &Profile::Production { + args.push("--profile=production"); } cmd("cargo", args).dir(path).run()?; binary_path(&profile.target_directory(path), node_path.unwrap_or(&path.join("node"))) @@ -336,8 +338,8 @@ mod tests { use anyhow::Result; use pop_common::{manifest::Dependency, set_executable_permission}; use std::{fs, fs::write, io::Write, path::Path}; - use tempfile::{tempdir, Builder, TempDir}; use strum::VariantArray; + use tempfile::{tempdir, Builder, TempDir}; fn setup_template_and_instantiate() -> Result { let temp_dir = tempdir().expect("Failed to create temp dir"); From 7deea0da16477f0f850b29ee6e1a75ee7877ccb4 Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Date: Fri, 29 Nov 2024 19:03:08 +0100 Subject: [PATCH 28/35] chore: add profile tests --- crates/pop-common/src/build.rs | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/crates/pop-common/src/build.rs b/crates/pop-common/src/build.rs index 6384b9e6..9464fc6f 100644 --- a/crates/pop-common/src/build.rs +++ b/crates/pop-common/src/build.rs @@ -67,3 +67,71 @@ impl fmt::Display for Profile { } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::path::Path; + use strum::{EnumMessage}; + + #[test] + fn profile_from_string() { + assert_eq!("debug".parse::().unwrap(), Profile::Debug); + assert_eq!("release".parse::().unwrap(), Profile::Release); + assert_eq!("production".parse::().unwrap(), Profile::Production); + } + + #[test] + fn profile_detailed_message() { + assert_eq!( + Profile::Debug.get_detailed_message(), + Some("Optimized for debugging.") + ); + assert_eq!( + Profile::Release.get_detailed_message(), + Some("Optimized without any debugging functionality.") + ); + assert_eq!( + Profile::Production.get_detailed_message(), + Some("Optimized for ultimate performance.") + ); + } + + #[test] + fn profile_target_directory() { + let base_path = Path::new("/example/path"); + + assert_eq!( + Profile::Debug.target_directory(base_path), + Path::new("/example/path/target/debug") + ); + assert_eq!( + Profile::Release.target_directory(base_path), + Path::new("/example/path/target/release") + ); + assert_eq!( + Profile::Production.target_directory(base_path), + Path::new("/example/path/target/production") + ); + } + + #[test] + fn profile_default() { + let default_profile = Profile::default(); + assert_eq!(default_profile, Profile::Release); + } + + #[test] + fn profile_from_bool() { + assert_eq!(Profile::from(true), Profile::Release); + assert_eq!(Profile::from(false), Profile::Debug); + } + + #[test] + fn profile_into_bool() { + assert_eq!(bool::from(Profile::Debug), false); + assert_eq!(bool::from(Profile::Release), true); + assert_eq!(bool::from(Profile::Production), true); + } +} + From 05c0fcc61822c75046eb947f56f12642581945f1 Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:49:29 +0100 Subject: [PATCH 29/35] fix(test): parachain_lifecycle --- crates/pop-cli/tests/parachain.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/pop-cli/tests/parachain.rs b/crates/pop-cli/tests/parachain.rs index 57a49187..b9baaac5 100644 --- a/crates/pop-cli/tests/parachain.rs +++ b/crates/pop-cli/tests/parachain.rs @@ -65,6 +65,8 @@ async fn parachain_lifecycle() -> Result<()> { "local", "--relay", "paseo-local", + "--profile", + "release", "--genesis-state", "--genesis-code", "--protocol-id", From 0fca3d98c6814790ac762b8084604d3ba274023f Mon Sep 17 00:00:00 2001 From: Alejandro Martinez Andres <11448715+al3mart@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:50:11 +0100 Subject: [PATCH 30/35] style: fmt --- crates/pop-common/src/build.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/pop-common/src/build.rs b/crates/pop-common/src/build.rs index 9464fc6f..8d4e79bf 100644 --- a/crates/pop-common/src/build.rs +++ b/crates/pop-common/src/build.rs @@ -72,7 +72,7 @@ impl fmt::Display for Profile { mod tests { use super::*; use std::path::Path; - use strum::{EnumMessage}; + use strum::EnumMessage; #[test] fn profile_from_string() { @@ -83,10 +83,7 @@ mod tests { #[test] fn profile_detailed_message() { - assert_eq!( - Profile::Debug.get_detailed_message(), - Some("Optimized for debugging.") - ); + assert_eq!(Profile::Debug.get_detailed_message(), Some("Optimized for debugging.")); assert_eq!( Profile::Release.get_detailed_message(), Some("Optimized without any debugging functionality.") @@ -134,4 +131,3 @@ mod tests { assert_eq!(bool::from(Profile::Production), true); } } - From 39b26edc957220a8839da597b3e4c2de1e110f3b Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Wed, 4 Dec 2024 13:38:34 +0100 Subject: [PATCH 31/35] fix: clippy --- Cargo.lock | 10 ++++++++++ crates/pop-cli/src/commands/build/mod.rs | 4 ++-- crates/pop-cli/src/commands/build/spec.rs | 17 +++++++++-------- crates/pop-common/src/build.rs | 6 +----- crates/pop-common/src/manifest.rs | 5 +---- crates/pop-parachains/src/build.rs | 2 +- 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d273e058..22004204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4716,6 +4716,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", + "toml 0.5.11", "toml_edit 0.22.20", "url", ] @@ -7317,6 +7318,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.7.8" diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index 6efaaf43..f1a69c9c 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -30,7 +30,7 @@ pub(crate) struct BuildArgs { #[arg(short = 'p', long)] pub(crate) package: Option, /// For production, always build in release mode to exclude debug features. - #[clap(short = 'r', long, conflicts_with = "profile")] + #[clap(short, long, conflicts_with = "profile")] pub(crate) release: bool, /// Build profile [default: debug]. #[clap(long, value_enum)] @@ -145,7 +145,7 @@ mod tests { for package in [None, Some(name.to_string())] { for release in [true, false] { for profile in Profile::VARIANTS { - let profile = if release { Profile::Release } else { Profile::Debug }; + let profile = if release { Profile::Release } else { profile.clone() }; let project = if package.is_some() { "package" } else { "project" }; let mut cli = MockCli::new() .expect_intro(format!("Building your {project}")) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 3b5cd8d6..541e6624 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -19,9 +19,9 @@ use std::{ env::current_dir, fs::create_dir_all, path::{Path, PathBuf}, - thread::sleep, - time::Duration, }; +#[cfg(not(test))] +use std::{thread::sleep, time::Duration}; use strum::{EnumMessage, VariantArray}; use strum_macros::{AsRefStr, Display, EnumString}; @@ -473,12 +473,12 @@ impl BuildSpec { .. } = self; // Ensure binary is built. - let binary_path = ensure_binary_exists(cli, &profile)?; + let binary_path = ensure_binary_exists(cli, profile)?; let spinner = spinner(); spinner.start("Generating chain specification..."); // Generate chain spec. - generate_plain_chain_spec(&binary_path, &output_file, default_bootnode, &chain)?; + generate_plain_chain_spec(&binary_path, output_file, default_bootnode, chain)?; // Customize spec based on input. self.customize()?; generated_files.push(format!( @@ -494,7 +494,7 @@ impl BuildSpec { .unwrap_or(DEFAULT_SPEC_NAME) .trim_end_matches(".json"); let raw_spec_name = format!("{spec_name}-raw.json"); - let raw_chain_spec = generate_raw_chain_spec(&binary_path, &output_file, &raw_spec_name)?; + let raw_chain_spec = generate_raw_chain_spec(&binary_path, output_file, &raw_spec_name)?; generated_files.push(format!( "Raw chain specification file generated at: {}", raw_chain_spec.display() @@ -534,7 +534,7 @@ impl BuildSpec { fn customize(&self) -> anyhow::Result<()> { let mut chain_spec = ChainSpec::from(&self.output_file)?; chain_spec.replace_para_id(self.id)?; - chain_spec.replace_relay_chain(&self.relay.to_string())?; + chain_spec.replace_relay_chain(&self.relay.as_ref())?; chain_spec.replace_chain_type(&self.chain_type.to_string())?; chain_spec.replace_protocol_id(&self.protocol_id)?; chain_spec.to_file(&self.output_file)?; @@ -803,8 +803,8 @@ mod tests { // Create a temporary file to act as the existing chain spec file. let temp_dir = tempdir()?; let chain_spec_path = temp_dir.path().join("existing-chain-spec.json"); - std::fs::write(&chain_spec_path, "{}")?; // Write a dummy JSON to the file. - // Use the deprcrated release flag. + std::fs::write(&chain_spec_path, "{}")?; + // Use the deprcrated release flag. let release = true; let build_spec_cmd = BuildSpecCommand { release, @@ -817,6 +817,7 @@ mod tests { false, ).expect_warning("NOTE: release flag is deprecated. Use `--profile` instead."); let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; + assert_eq!(build_spec.profile, release.into()); cli.verify()?; Ok(()) } diff --git a/crates/pop-common/src/build.rs b/crates/pop-common/src/build.rs index 8d4e79bf..f871078a 100644 --- a/crates/pop-common/src/build.rs +++ b/crates/pop-common/src/build.rs @@ -40,11 +40,7 @@ impl Profile { impl From for bool { fn from(value: Profile) -> Self { - if value == Profile::Debug { - false - } else { - true - } + value != Profile::Debug } } diff --git a/crates/pop-common/src/manifest.rs b/crates/pop-common/src/manifest.rs index afe3b576..16db4f0d 100644 --- a/crates/pop-common/src/manifest.rs +++ b/crates/pop-common/src/manifest.rs @@ -98,8 +98,6 @@ pub fn add_crate_to_workspace(workspace_toml: &Path, crate_path: &Path) -> anyho Ok(()) } -use std::fs; - /// Adds a "production" profile to the Cargo.toml manifest if it doesn't already exist. /// /// # Arguments @@ -108,7 +106,7 @@ pub fn add_production_profile(project: &Path) -> anyhow::Result<()> { let root_toml_path = project.join("Cargo.toml"); let mut manifest = Manifest::from_path(&root_toml_path)?; // Check if the `production` profile already exists. - if manifest.profile.custom.get("production").is_some() { + if manifest.profile.custom.contains_key("production") { return Ok(()); } // Create the production profile with required fields. @@ -475,7 +473,6 @@ mod tests { // Call the function to add the production profile let result = add_production_profile(project_path); - println!("{:?}", result); assert!(result.is_ok()); // Verify the production profile is added diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index 3746f5c0..965e5328 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -78,7 +78,7 @@ pub fn binary_path(target_path: &Path, node_path: &Path) -> Result Date: Wed, 4 Dec 2024 15:19:00 +0100 Subject: [PATCH 32/35] fix: cli required changes introduced by PR --- crates/pop-cli/src/cli.rs | 66 +------------------- crates/pop-cli/src/commands/call/contract.rs | 63 ++++++++++--------- crates/pop-parachains/src/build.rs | 2 +- 3 files changed, 36 insertions(+), 95 deletions(-) diff --git a/crates/pop-cli/src/cli.rs b/crates/pop-cli/src/cli.rs index afcef292..6c6bb3bc 100644 --- a/crates/pop-cli/src/cli.rs +++ b/crates/pop-cli/src/cli.rs @@ -135,6 +135,7 @@ impl traits::Cli for Cli { /// A confirmation prompt using cliclack. struct Confirm(cliclack::Confirm); + impl traits::Confirm for Confirm { /// Sets the initially selected value. fn initial_value(mut self, initial_value: bool) -> Self { @@ -145,47 +146,11 @@ impl traits::Confirm for Confirm { fn interact(&mut self) -> Result { self.0.interact() } - /// Sets the initially selected value. - fn initial_value(mut self, initial_value: bool) -> Self { - self.0 = self.0.initial_value(initial_value); - self - } } /// A input prompt using cliclack. struct Input(cliclack::Input); -impl traits::Input for Input { - /// Sets the default value for the input. - fn default_input(mut self, value: &str) -> Self { - self.0 = self.0.default_input(value); - self - } - /// Starts the prompt interaction. - fn interact(&mut self) -> Result { - self.0.interact() - } - /// Sets the placeholder (hint) text for the input. - fn placeholder(mut self, placeholder: &str) -> Self { - self.0 = self.0.placeholder(placeholder); - self - } - /// Sets whether the input is required. - fn required(mut self, required: bool) -> Self { - self.0 = self.0.required(required); - self - } - /// Sets a validation callback for the input that is called when the user submits. - fn validate( - mut self, - validator: impl Fn(&String) -> std::result::Result<(), &'static str> + 'static, - ) -> Self { - self.0 = self.0.validate(validator); - self - } -} -/// A input prompt using cliclack. -struct Input(cliclack::Input); impl traits::Input for Input { /// Sets the default value for the input. fn default_input(mut self, value: &str) -> Self { @@ -242,22 +207,7 @@ impl traits::MultiSelect for MultiSelect { struct Select(cliclack::Select); impl traits::Select for Select { - /// Starts the prompt interaction. - fn interact(&mut self) -> Result { - self.0.interact() - } - - /// Adds an item to the selection prompt. - fn item(mut self, value: T, label: impl Display, hint: impl Display) -> Self { - self.0 = self.0.item(value, label, hint); - self - } -} - -/// A select prompt using cliclack. -struct Select(cliclack::Select); - -impl traits::Select for Select { + /// Sets the initially selected value. fn initial_value(mut self, initial_value: T) -> Self { self.0 = self.0.initial_value(initial_value); self @@ -355,18 +305,6 @@ pub(crate) mod tests { self } - pub(crate) fn expect_select( - mut self, - prompt: impl Display, - required: Option, - collect: bool, - items: Option>, - item: usize, - ) -> Self { - self.select_expectation = Some((prompt.to_string(), required, collect, items, item)); - self - } - pub(crate) fn expect_success(mut self, message: impl Display) -> Self { self.success_expectations.push(message.to_string()); self diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 4e00e37b..0bf5ec03 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -612,7 +612,7 @@ mod tests { "Do you want to perform another call using the existing smart contract?", true, ) - .expect_select::( + .expect_select( "Select the message to call:", Some(false), true, @@ -669,8 +669,7 @@ mod tests { ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_input("Signer calling the contract:", "//Alice".into()) - .expect_select::( + .expect_select( "Select the message to call:", Some(false), true, @@ -678,17 +677,19 @@ mod tests { 1, // "get" message ) .expect_input( - "Provide the on-chain contract address:", - "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + "Where is your project or contract artifact located?", + temp_dir.path().join("testing").display().to_string(), ) .expect_input( "Where is your contract deployed?", "wss://rpc1.paseo.popnetwork.xyz".into(), ) .expect_input( - "Where is your project or contract artifact located?", - temp_dir.path().join("testing").display().to_string(), - ).expect_info(format!( + "Provide the on-chain contract address:", + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + ) + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_info(format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message get --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice", temp_dir.path().join("testing").display().to_string(), )); @@ -750,13 +751,7 @@ mod tests { // The inputs are processed in reverse order. let mut cli = MockCli::new() .expect_confirm("Do you want to execute the call? (Selecting 'No' will perform a dry run)", true) - .expect_input("Signer calling the contract:", "//Alice".into()) - .expect_input("Enter the proof size limit:", "".into()) // Only if call - .expect_input("Enter the gas limit:", "".into()) // Only if call - .expect_input("Value to transfer to the call:", "50".into()) // Only if payable - .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip - .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip - .expect_select::( + .expect_select( "Select the message to call:", Some(false), true, @@ -764,17 +759,24 @@ mod tests { 2, // "specific_flip" message ) .expect_input( - "Provide the on-chain contract address:", - "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + "Where is your project or contract artifact located?", + temp_dir.path().join("testing").display().to_string(), ) .expect_input( "Where is your contract deployed?", "wss://rpc1.paseo.popnetwork.xyz".into(), ) .expect_input( - "Where is your project or contract artifact located?", - temp_dir.path().join("testing").display().to_string(), - ).expect_info(format!( + "Provide the on-chain contract address:", + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + ) + .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip + .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Enter the gas limit:", "".into()) // Only if call + .expect_input("Enter the proof size limit:", "".into()) // Only if call + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_info(format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); @@ -837,11 +839,7 @@ mod tests { ]; // The inputs are processed in reverse order. let mut cli = MockCli::new() - .expect_input("Signer calling the contract:", "//Alice".into()) - .expect_input("Value to transfer to the call:", "50".into()) // Only if payable - .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip - .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip - .expect_select::( + .expect_select( "Select the message to call:", Some(false), true, @@ -849,17 +847,22 @@ mod tests { 2, // "specific_flip" message ) .expect_input( - "Provide the on-chain contract address:", - "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + "Where is your project or contract artifact located?", + temp_dir.path().join("testing").display().to_string(), ) .expect_input( "Where is your contract deployed?", "wss://rpc1.paseo.popnetwork.xyz".into(), ) .expect_input( - "Where is your project or contract artifact located?", - temp_dir.path().join("testing").display().to_string(), - ).expect_info(format!( + "Provide the on-chain contract address:", + "15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm".into(), + ) + .expect_input("Enter the value for the parameter: new_value", "true".into()) // Args for specific_flip + .expect_input("Enter the value for the parameter: number", "2".into()) // Args for specific_flip + .expect_input("Value to transfer to the call:", "50".into()) // Only if payable + .expect_input("Signer calling the contract:", "//Alice".into()) + .expect_info(format!( "pop call contract --path {} --contract 15XausWjFLBBFLDXUSBRfSfZk25warm4wZRV4ZxhZbfvjrJm --message specific_flip --args \"true\", \"2\" --value 50 --url wss://rpc1.paseo.popnetwork.xyz/ --suri //Alice --execute", temp_dir.path().join("testing").display().to_string(), )); diff --git a/crates/pop-parachains/src/build.rs b/crates/pop-parachains/src/build.rs index 965e5328..1e369f37 100644 --- a/crates/pop-parachains/src/build.rs +++ b/crates/pop-parachains/src/build.rs @@ -78,7 +78,7 @@ pub fn binary_path(target_path: &Path, node_path: &Path) -> Result Date: Wed, 4 Dec 2024 16:20:40 +0100 Subject: [PATCH 33/35] fix: test --- crates/pop-cli/src/commands/call/contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 0bf5ec03..0b2815a8 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -606,11 +606,11 @@ mod tests { .expect_warning("Your call has not been executed.") .expect_confirm( "Do you want to perform another call using the existing smart contract?", - false, + true, ) .expect_confirm( "Do you want to perform another call using the existing smart contract?", - true, + false, ) .expect_select( "Select the message to call:", From 68a9065f7dc79b6bb8b23ee05ff6d10a42750aaa Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 5 Dec 2024 17:23:14 +0100 Subject: [PATCH 34/35] fix: clippy --- crates/pop-cli/src/commands/build/spec.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 541e6624..00913e46 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -139,7 +139,7 @@ pub struct BuildSpecCommand { #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, // TODO: remove at release ... - /// [DEPRECATED] use `profile`. + /// [DEPRECATED] since v0.6.0, use `profile`. #[arg(short = 'r', long, conflicts_with = "profile")] pub(crate) release: bool, /// Build profile for the binary to generate the chain specification. @@ -534,8 +534,8 @@ impl BuildSpec { fn customize(&self) -> anyhow::Result<()> { let mut chain_spec = ChainSpec::from(&self.output_file)?; chain_spec.replace_para_id(self.id)?; - chain_spec.replace_relay_chain(&self.relay.as_ref())?; - chain_spec.replace_chain_type(&self.chain_type.to_string())?; + chain_spec.replace_relay_chain(self.relay.as_ref())?; + chain_spec.replace_chain_type(self.chain_type.as_ref())?; chain_spec.replace_protocol_id(&self.protocol_id)?; chain_spec.to_file(&self.output_file)?; Ok(()) From 3cd8194b165693f06d9001c8b543a968c920d3ce Mon Sep 17 00:00:00 2001 From: Daanvdplas Date: Thu, 5 Dec 2024 17:24:57 +0100 Subject: [PATCH 35/35] docs: deprecation message --- crates/pop-cli/src/commands/build/spec.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 00913e46..3cde4c61 100644 --- a/crates/pop-cli/src/commands/build/spec.rs +++ b/crates/pop-cli/src/commands/build/spec.rs @@ -138,8 +138,7 @@ pub struct BuildSpecCommand { /// the necessary directories will be created #[arg(short = 'o', long = "output")] pub(crate) output_file: Option, - // TODO: remove at release ... - /// [DEPRECATED] since v0.6.0, use `profile`. + /// [DEPRECATED] and will be removed in v0.7.0, use `profile`. #[arg(short = 'r', long, conflicts_with = "profile")] pub(crate) release: bool, /// Build profile for the binary to generate the chain specification.