diff --git a/Cargo.lock b/Cargo.lock index 0f70f564d..82c848bc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1814,6 +1814,16 @@ dependencies = [ "toml 0.8.20", ] +[[package]] +name = "cargo_toml" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472" +dependencies = [ + "serde", + "toml 0.8.20", +] + [[package]] name = "cc" version = "1.2.17" @@ -8958,6 +8968,7 @@ dependencies = [ "indexmap 2.9.0", "parity-scale-codec", "pop-common", + "rustilities", "sc-chain-spec", "sc-cli", "scale-info", @@ -9008,6 +9019,7 @@ dependencies = [ "pop-telemetry", "regex", "reqwest", + "rustilities", "scale-value", "serde", "serde_json", @@ -9031,7 +9043,7 @@ dependencies = [ "anyhow", "assert_cmd", "bytes", - "cargo_toml", + "cargo_toml 0.20.5", "contract-build 5.0.3", "contract-extrinsics 5.0.3", "derivative", @@ -10135,6 +10147,17 @@ dependencies = [ "nom", ] +[[package]] +name = "rustilities" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647edbdbbd488fa193058afbb7232161ca3696fe5619580ce6d354001c301cbf" +dependencies = [ + "cargo_toml 0.21.0", + "thiserror 2.0.12", + "toml_edit", +] + [[package]] name = "rustix" version = "0.36.17" diff --git a/Cargo.toml b/Cargo.toml index 003cdb4c7..d9d13d06b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ git2 = { version = "0.18", default-features = true, features = ["vendored-openss glob = { version = "0.3.1", default-features = false } log = { version = "0.4.20", default-features = false } mockito = { version = "1.4.0", default-features = false } +rustilities = "3.0.0" tar = { version = "0.4.40", default-features = false } tempfile = { version = "3.10", default-features = false } thiserror = { version = "1.0.58", default-features = false } diff --git a/crates/pop-chains/Cargo.toml b/crates/pop-chains/Cargo.toml index 01d063577..d90d9dd84 100644 --- a/crates/pop-chains/Cargo.toml +++ b/crates/pop-chains/Cargo.toml @@ -25,6 +25,7 @@ url.workspace = true askama.workspace = true indexmap.workspace = true +rustilities = { workspace = true, features = ["manifest"] } scale.workspace = true scale-info.workspace = true scale-value.workspace = true diff --git a/crates/pop-chains/README.md b/crates/pop-chains/README.md index dc3a06c5b..46edc2cfb 100644 --- a/crates/pop-chains/README.md +++ b/crates/pop-chains/README.md @@ -48,11 +48,13 @@ use std::path::Path; let path = Path::new("./"); // Location of the parachain project. let builder = ChainSpecBuilder::Node { node_path: path.join("node"), default_bootnode: false, profile: Profile::Release }; +let spec_name = "MySpec"; +let spec_id = "my_spec"; // Build the node binary first builder.build(&[]).unwrap(); // Generate a plain chain specification file of a parachain let plain_chain_spec_path = path.join("plain-parachain-chainspec.json"); -builder.generate_plain_chain_spec("dev", &plain_chain_spec_path).unwrap(); +builder.generate_plain_chain_spec("dev", &plain_chain_spec_path, Some(spec_name), Some(spec_id)).unwrap(); // Customize your chain specification let mut chain_spec = ChainSpec::from(&plain_chain_spec_path).unwrap(); chain_spec.replace_para_id(2002); @@ -72,11 +74,13 @@ use std::path::Path; let path = Path::new("./"); // Location of the parachain project. let builder = ChainSpecBuilder::Node { node_path: path.join("node"), default_bootnode: false, profile: Profile::Release }; +let spec_name = "MySpec"; +let spec_id = "my_spec"; // Build the node binary first let binary_path = builder.build(&[]).unwrap(); // Generate a plain chain specification file of a parachain let plain_chain_spec_path = path.join("plain-parachain-chainspec.json"); -builder.generate_plain_chain_spec("dev", &plain_chain_spec_path).unwrap(); +builder.generate_plain_chain_spec("dev", &plain_chain_spec_path, Some(spec_name), Some(spec_id)).unwrap(); // Generate a raw chain specification file of a parachain let chain_spec = builder.generate_raw_chain_spec(&plain_chain_spec_path, "raw-parachain-chainspec.json").unwrap(); // Export the WebAssembly runtime for the parachain. diff --git a/crates/pop-chains/src/build/mod.rs b/crates/pop-chains/src/build/mod.rs index fded0d80b..c502330c6 100644 --- a/crates/pop-chains/src/build/mod.rs +++ b/crates/pop-chains/src/build/mod.rs @@ -3,9 +3,7 @@ use crate::errors::{Error, handle_command_error}; use anyhow::{Result, anyhow}; use duct::cmd; -use pop_common::{ - Profile, account_id::convert_to_evm_accounts, find_workspace_toml, manifest::from_path, -}; +use pop_common::{Profile, account_id::convert_to_evm_accounts, manifest::from_path}; use sc_chain_spec::{GenericChainSpec, NoExtension}; use serde_json::{Value, json}; use sp_core::bytes::to_hex; @@ -87,7 +85,7 @@ impl ChainSpecBuilder { pub fn artifact_path(&self) -> Result { let manifest = from_path(&self.path())?; let package = manifest.package().name(); - let root_folder = find_workspace_toml(&self.path()) + let root_folder = rustilities::manifest::find_workspace_manifest(self.path()) .ok_or(anyhow::anyhow!("Not inside a workspace"))? .parent() .expect("Path to Cargo.toml workspace root folder must exist") @@ -117,10 +115,14 @@ impl ChainSpecBuilder { /// # Arguments /// * `chain_or_preset` - The chain (when using a node) or preset (when using a runtime) name. /// * `output_file` - The path where the chain spec should be written. + /// * `name` - The name to be used on the chain spec if specified. + /// * `id` - The ID to be used on the chain spec if specified. pub fn generate_plain_chain_spec( &self, chain_or_preset: &str, output_file: &Path, + name: Option<&str>, + id: Option<&str>, ) -> Result<(), Error> { match self { ChainSpecBuilder::Node { default_bootnode, .. } => generate_plain_chain_spec_with_node( @@ -133,6 +135,8 @@ impl ChainSpecBuilder { fs::read(self.artifact_path()?)?, output_file, chain_or_preset, + name, + id, ), } } @@ -329,16 +333,27 @@ pub fn generate_raw_chain_spec_with_runtime( /// * `wasm` - The WebAssembly runtime bytes. /// * `plain_chain_spec` - The path where the plain chain specification should be written. /// * `preset` - Preset name for genesis configuration. +/// * `name` - The name to be used on the chain spec if specified. +/// * `id` - The ID to be used on the chain spec if specified. pub fn generate_plain_chain_spec_with_runtime( wasm: Vec, plain_chain_spec: &Path, preset: &str, + name: Option<&str>, + id: Option<&str>, ) -> Result<(), Error> { - let chain_spec = GenericChainSpec::::builder(&wasm[..], None) - .with_genesis_config_preset_name(preset.trim()) - .build() - .as_json(false) - .map_err(|e| anyhow::anyhow!(e))?; + let mut chain_spec = GenericChainSpec::::builder(&wasm[..], None) + .with_genesis_config_preset_name(preset.trim()); + + if let Some(name) = name { + chain_spec = chain_spec.with_name(name); + } + + if let Some(id) = id { + chain_spec = chain_spec.with_id(id); + } + + let chain_spec = chain_spec.build().as_json(false).map_err(|e| anyhow::anyhow!(e))?; fs::write(plain_chain_spec, chain_spec)?; Ok(()) @@ -781,6 +796,8 @@ mod tests { use strum::VariantArray; use tempfile::{Builder, TempDir, tempdir}; + static MOCK_WASM: &[u8] = include_bytes!("../../../../tests/runtimes/base_parachain.wasm"); + fn setup_template_and_instantiate() -> Result { let temp_dir = tempdir().expect("Failed to create temp dir"); let config = Config { @@ -1055,6 +1072,104 @@ mod tests { Ok(()) } + #[tokio::test] + async fn generate_plain_chain_spec_with_runtime_works_with_name_and_id_override() -> Result<()> + { + let temp_dir = + setup_template_and_instantiate().expect("Failed to setup template and instantiate"); + // Test generate chain spec + let plain_chain_spec = &temp_dir.path().join("plain-parachain-chainspec.json"); + generate_plain_chain_spec_with_runtime( + Vec::from(MOCK_WASM), + plain_chain_spec, + "local_testnet", + Some("POP Chain Spec"), + Some("pop-chain-spec"), + )?; + assert!(plain_chain_spec.exists()); + let raw_chain_spec = + generate_raw_chain_spec_with_runtime(plain_chain_spec, "raw-parachain-chainspec.json")?; + assert!(raw_chain_spec.exists()); + let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file"); + assert!(content.contains("\"name\": \"POP Chain Spec\"")); + assert!(content.contains("\"id\": \"pop-chain-spec\"")); + assert!(content.contains("\"bootNodes\": []")); + Ok(()) + } + + #[tokio::test] + async fn generate_plain_chain_spec_with_runtime_works_with_name_override() -> Result<()> { + let temp_dir = + setup_template_and_instantiate().expect("Failed to setup template and instantiate"); + // Test generate chain spec + let plain_chain_spec = &temp_dir.path().join("plain-parachain-chainspec.json"); + generate_plain_chain_spec_with_runtime( + Vec::from(MOCK_WASM), + plain_chain_spec, + "local_testnet", + Some("POP Chain Spec"), + None, + )?; + assert!(plain_chain_spec.exists()); + let raw_chain_spec = + generate_raw_chain_spec_with_runtime(plain_chain_spec, "raw-parachain-chainspec.json")?; + assert!(raw_chain_spec.exists()); + let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file"); + assert!(content.contains("\"name\": \"POP Chain Spec\"")); + assert!(content.contains("\"id\": \"dev\"")); + assert!(content.contains("\"bootNodes\": []")); + Ok(()) + } + + #[tokio::test] + async fn generate_plain_chain_spec_with_runtime_works_with_id_override() -> Result<()> { + let temp_dir = + setup_template_and_instantiate().expect("Failed to setup template and instantiate"); + // Test generate chain spec + let plain_chain_spec = &temp_dir.path().join("plain-parachain-chainspec.json"); + generate_plain_chain_spec_with_runtime( + Vec::from(MOCK_WASM), + plain_chain_spec, + "local_testnet", + None, + Some("pop-chain-spec"), + )?; + assert!(plain_chain_spec.exists()); + let raw_chain_spec = + generate_raw_chain_spec_with_runtime(plain_chain_spec, "raw-parachain-chainspec.json")?; + assert!(raw_chain_spec.exists()); + let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file"); + assert!(content.contains("\"name\": \"Development\"")); + assert!(content.contains("\"id\": \"pop-chain-spec\"")); + assert!(content.contains("\"bootNodes\": []")); + Ok(()) + } + + #[tokio::test] + async fn generate_plain_chain_spec_with_runtime_works_without_name_and_id_override() + -> Result<()> { + let temp_dir = + setup_template_and_instantiate().expect("Failed to setup template and instantiate"); + // Test generate chain spec + let plain_chain_spec = &temp_dir.path().join("plain-parachain-chainspec.json"); + generate_plain_chain_spec_with_runtime( + Vec::from(MOCK_WASM), + plain_chain_spec, + "local_testnet", + None, + None, + )?; + assert!(plain_chain_spec.exists()); + let raw_chain_spec = + generate_raw_chain_spec_with_runtime(plain_chain_spec, "raw-parachain-chainspec.json")?; + assert!(raw_chain_spec.exists()); + let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file"); + assert!(content.contains("\"name\": \"Development\"")); + assert!(content.contains("\"id\": \"dev\"")); + assert!(content.contains("\"bootNodes\": []")); + Ok(()) + } + #[tokio::test] async fn fails_to_generate_plain_chain_spec_when_file_missing() -> Result<()> { let temp_dir = diff --git a/crates/pop-cli/Cargo.toml b/crates/pop-cli/Cargo.toml index e21885c77..ef81151dd 100644 --- a/crates/pop-cli/Cargo.toml +++ b/crates/pop-cli/Cargo.toml @@ -26,6 +26,7 @@ duct.workspace = true env_logger.workspace = true os_info.workspace = true reqwest.workspace = true +rustilities = { workspace = true, features = ["manifest"]} serde = { workspace = true, features = ["derive"] } serde_json.workspace = true strum.workspace = true diff --git a/crates/pop-cli/src/commands/build/chain.rs b/crates/pop-cli/src/commands/build/chain.rs index 5eafcd40b..a0105e64a 100644 --- a/crates/pop-cli/src/commands/build/chain.rs +++ b/crates/pop-cli/src/commands/build/chain.rs @@ -4,7 +4,7 @@ use super::{PACKAGE, PARACHAIN}; use crate::{ cli, common::{ - builds::create_chain_spec_builder, + builds::{ChainPath, create_chain_spec_builder}, runtime::Feature::{Benchmark, TryRuntime}, }, style::style, @@ -65,7 +65,12 @@ impl BuildChain { // Build parachain. cli.warning("NOTE: this may take some time...")?; - let builder = create_chain_spec_builder(&self.path, &self.profile, false, cli)?; + let builder = create_chain_spec_builder( + ChainPath::Base(self.path.to_path_buf()), + &self.profile, + false, + cli, + )?; let features_arr: Vec<_> = features.into_iter().map(|s| s.to_string()).collect(); let binary = builder.build(features_arr.as_slice())?; cli.info(format!("The {project} was built in {} mode.", self.profile))?; diff --git a/crates/pop-cli/src/commands/build/runtime.rs b/crates/pop-cli/src/commands/build/runtime.rs index 86a08d6f8..fb4e61700 100644 --- a/crates/pop-cli/src/commands/build/runtime.rs +++ b/crates/pop-cli/src/commands/build/runtime.rs @@ -8,7 +8,7 @@ use crate::{ runtime::{build_runtime, ensure_runtime_binary_exists}, }, }; -use pop_common::{Profile, find_workspace_toml}; +use pop_common::Profile; use std::{ collections::HashSet, path::{Path, PathBuf}, @@ -89,7 +89,7 @@ impl BuildRuntime { if self.profile == Profile::Debug { cli.warning("NOTE: this command now defaults to DEBUG builds. Please use `--release` (or simply `-r`) for a release build...")?; } - let workspace_root = find_workspace_toml(&self.path); + let workspace_root = rustilities::manifest::find_workspace_manifest(&self.path); let target_path = self .profile .target_directory(&workspace_root.unwrap_or(self.path.to_path_buf())) diff --git a/crates/pop-cli/src/commands/build/spec.rs b/crates/pop-cli/src/commands/build/spec.rs index 325ad3cc0..255b7b7e6 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::{ traits::{Cli as _, *}, }, common::{ - builds::{create_chain_spec_builder, guide_user_to_select_profile}, + builds::{ChainPath, create_chain_spec_builder, guide_user_to_select_profile}, omni_node::source_polkadot_omni_node_binary, runtime::build_deterministic_runtime, }, @@ -151,8 +151,8 @@ pub struct BuildSpecCommand { #[arg(long, value_enum)] pub(crate) profile: Option, /// Parachain ID to be used when generating the chain spec files. - #[arg(short, long)] - pub(crate) id: Option, + #[arg(short = 'i', long = "para-id")] + pub(crate) para_id: Option, /// Whether to keep localhost as a bootnode. #[arg(short = 'b', long)] pub(crate) default_bootnode: Option, @@ -173,6 +173,12 @@ pub struct BuildSpecCommand { /// Relay chain this parachain will connect to. #[arg(short = 'r', long, value_enum)] pub(crate) relay: Option, + /// Name to be used in the specification. + #[arg(short, long)] + pub(crate) name: Option, + /// Id to be used in the specification. + #[arg(long)] + pub(crate) id: Option, /// Protocol-id to use in the specification. #[arg(short = 'P', long = "protocol-id")] pub(crate) protocol_id: Option, @@ -231,11 +237,13 @@ impl BuildSpecCommand { path, output_file, profile, - id, + para_id, default_bootnode, chain_type, chain, relay, + name, + id, protocol_id, properties, features, @@ -288,7 +296,7 @@ impl BuildSpecCommand { let chain_spec = ChainSpec::from(&output_file).ok(); // Para id. - let id = match id { + let para_id = match para_id { Some(id) => id, None => { let default = chain_spec @@ -431,16 +439,16 @@ impl BuildSpecCommand { // If deterministic build is selected, use the provided runtime path or prompt the user if // missing. let runtime_dir = if deterministic { - runtime_dir.unwrap_or_else(|| { + Some(runtime_dir.unwrap_or_else(|| { cli.input("Enter the directory path where the runtime is located:") .placeholder(DEFAULT_RUNTIME_DIR) .default_input(DEFAULT_RUNTIME_DIR) .interact() .map(PathBuf::from) .unwrap_or_else(|_| PathBuf::from(DEFAULT_RUNTIME_DIR)) - }) + })) } else { - DEFAULT_RUNTIME_DIR.into() + runtime_dir }; // If deterministic build is selected, extract package name from runtime path provided @@ -448,9 +456,13 @@ impl BuildSpecCommand { let package = if deterministic { package .or_else(|| { - from_path(&runtime_dir) - .ok() - .and_then(|manifest| manifest.package.map(|pkg| pkg.name)) + from_path( + runtime_dir + .as_ref() + .expect("Deterministic builds always have a runtime_dir"), + ) + .ok() + .and_then(|manifest| manifest.package.map(|pkg| pkg.name)) }) .unwrap_or_else(|| { cli.input("Enter the runtime package name:") @@ -467,7 +479,7 @@ impl BuildSpecCommand { path, output_file, profile, - id, + para_id, default_bootnode, chain_type, chain, @@ -475,6 +487,8 @@ impl BuildSpecCommand { protocol_id, properties, features, + name, + id, skip_build, genesis_state, genesis_code, @@ -526,12 +540,14 @@ pub(crate) struct BuildSpec { path: PathBuf, output_file: PathBuf, profile: Profile, - id: u32, + para_id: u32, default_bootnode: bool, chain_type: ChainType, chain: Option, relay: RelayChain, protocol_id: String, + name: Option, + id: Option, properties: Option, features: Vec, skip_build: bool, @@ -539,7 +555,7 @@ pub(crate) struct BuildSpec { genesis_code: bool, deterministic: bool, package: String, - runtime_dir: PathBuf, + runtime_dir: Option, use_existing_plain_spec: bool, } @@ -554,8 +570,13 @@ impl BuildSpec { cli: &mut impl cli::traits::Cli, ) -> anyhow::Result { let mut generated_files = vec![]; + let builder_path = if let Some(runtime_dir) = &self.runtime_dir { + ChainPath::Exact(runtime_dir.to_path_buf()) + } else { + ChainPath::Base(self.path.to_path_buf()) + }; let builder = - create_chain_spec_builder(&self.path, &self.profile, self.default_bootnode, cli)?; + create_chain_spec_builder(builder_path, &self.profile, self.default_bootnode, cli)?; let is_runtime_build = matches!(builder, ChainSpecBuilder::Runtime { .. }); let artifact_exists = builder.artifact_path().is_ok(); if self.skip_build && builder.artifact_path().is_err() { @@ -585,7 +606,12 @@ impl BuildSpec { .interact()? }; spinner.start("Generating chain specification..."); - builder.generate_plain_chain_spec(&chain_or_preset, &self.output_file)?; + builder.generate_plain_chain_spec( + &chain_or_preset, + &self.output_file, + self.name.as_deref(), + self.id.as_deref(), + )?; // Customize spec based on input. self.customize(&self.output_file)?; // Deterministic build. @@ -595,7 +621,9 @@ impl BuildSpec { &spinner, &self.package, self.profile.clone(), - self.runtime_dir.clone(), + self.runtime_dir + .clone() + .expect("Deterministic builds always contains runtime_dir"), ) .map_err(|e| { anyhow::anyhow!("Failed to build the deterministic runtime: {}", e.to_string()) @@ -635,7 +663,7 @@ impl BuildSpec { // Generate genesis artifacts. let genesis_code_file = if self.genesis_code { spinner.set_message("Generating genesis code..."); - let wasm_file_name = format!("para-{}.wasm", self.id); + let wasm_file_name = format!("para-{}.wasm", self.para_id); let wasm_file = builder.export_wasm_file(&raw_chain_spec, &wasm_file_name)?; generated_files .push(format!("WebAssembly runtime file exported at: {}", wasm_file.display())); @@ -645,7 +673,7 @@ impl BuildSpec { }; let genesis_state_file = if self.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", self.para_id); let binary_path = match builder { ChainSpecBuilder::Runtime { .. } => source_polkadot_omni_node_binary(cli, &spinner, &crate::cache()?, true).await?, @@ -709,7 +737,7 @@ impl BuildSpec { // Customize a chain specification. fn customize(&self, path: &Path) -> anyhow::Result<()> { let mut chain_spec = ChainSpec::from(path)?; - chain_spec.replace_para_id(self.id)?; + chain_spec.replace_para_id(self.para_id)?; 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)?; @@ -776,6 +804,8 @@ mod tests { let genesis_state = true; let output_file = "artifacts/chain-spec.json"; let para_id = 4242; + let name = "POP Chain Spec"; + let id = "pop"; let protocol_id = "pop"; let relay = Polkadot; let profile = Profile::Production; @@ -785,6 +815,7 @@ mod tests { let path = PathBuf::from("./"); let properties = "tokenSymbol=UNIT,decimals=12,isEthereum=false"; + let mut flags_used = false; for (build_spec_cmd, chain) in [ // No flags used. (BuildSpecCommand::default(), None), @@ -794,7 +825,9 @@ mod tests { path, output_file: Some(PathBuf::from(output_file)), profile: Some(profile.clone()), - id: Some(para_id), + name: Some(name.to_string()), + id: Some(id.to_string()), + para_id: Some(para_id), default_bootnode: Some(default_bootnode), chain_type: Some(chain_type.clone()), features: "".to_string(), @@ -849,11 +882,13 @@ mod tests { .expect_confirm("Would you like to build the runtime deterministically? This requires a containerization solution (Docker/Podman) and is recommended for production builds.", deterministic) .expect_input("Enter the directory path where the runtime is located:", runtime_dir.display().to_string()) .expect_input("Enter the runtime package name:", package.to_string()); + } else { + flags_used = true; } 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.para_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); @@ -863,7 +898,15 @@ mod tests { assert_eq!(build_spec.genesis_code, genesis_code); assert_eq!(build_spec.deterministic, deterministic); assert_eq!(build_spec.package, package); - assert_eq!(build_spec.runtime_dir, runtime_dir); + assert_eq!(build_spec.runtime_dir, Some(runtime_dir.clone())); + if flags_used { + assert_eq!(build_spec.name, Some(name.to_string())); + assert_eq!(build_spec.id, Some(id.to_string())); + } else { + assert_eq!(build_spec.name, None); + assert_eq!(build_spec.id, None); + } + cli.verify()?; } Ok(()) @@ -877,6 +920,8 @@ mod tests { let genesis_state = true; let output_file = "artifacts/chain-spec.json"; let para_id = 4242; + let name = "POP Chain Spec"; + let id = "pop"; let protocol_id = "pop"; let relay = Polkadot; let profile = Profile::Production; @@ -904,10 +949,12 @@ mod tests { path: path.clone(), output_file: Some(PathBuf::from(output_file)), profile: Some(profile.clone()), - id: Some(para_id), + para_id: Some(para_id), default_bootnode: None, chain_type: Some(chain_type.clone()), features: "".to_string(), + name: Some(name.to_string()), + id: Some(id.to_string()), chain: Some(chain_spec_path.to_string_lossy().to_string()), relay: Some(relay.clone()), protocol_id: Some(protocol_id.to_string()), @@ -1001,27 +1048,45 @@ mod tests { } let build_spec = build_spec_cmd.configure_build_spec(&mut cli).await?; if !changes && no_flags_used { - assert_eq!(build_spec.id, 2000); + assert_eq!(build_spec.para_id, 2000); assert_eq!(build_spec.chain_type, Development); assert_eq!(build_spec.relay, PaseoLocal); assert_eq!(build_spec.protocol_id, "my-protocol"); + assert_eq!(build_spec.name, None); + assert_eq!(build_spec.id, None); assert_eq!(build_spec.genesis_state, genesis_state); assert_eq!(build_spec.genesis_code, genesis_code); assert!(!build_spec.deterministic); assert_eq!(build_spec.package, DEFAULT_PACKAGE); - assert_eq!(build_spec.runtime_dir, PathBuf::from(DEFAULT_RUNTIME_DIR)); + assert_eq!(build_spec.runtime_dir, None); } else if changes && no_flags_used { - assert_eq!(build_spec.id, para_id); + assert_eq!(build_spec.para_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.name, None); + assert_eq!(build_spec.id, None); 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_eq!(build_spec.deterministic, deterministic); assert_eq!(build_spec.package, package); - assert_eq!(build_spec.runtime_dir, runtime_dir); + assert_eq!(build_spec.runtime_dir, Some(runtime_dir.clone())); + } else if !no_flags_used { + assert_eq!(build_spec.para_id, para_id); + assert_eq!(build_spec.profile, profile); + assert!(!build_spec.default_bootnode); + assert_eq!(build_spec.chain_type, chain_type); + assert_eq!(build_spec.name, Some(name.to_string())); + assert_eq!(build_spec.id, Some(id.to_string())); + assert_eq!(build_spec.relay, relay); + assert_eq!(build_spec.protocol_id, protocol_id); + assert!(!build_spec.genesis_state); + assert!(!build_spec.genesis_code); + assert!(!build_spec.deterministic); + assert_eq!(build_spec.package, "parachain-template-runtime"); + assert_eq!(build_spec.runtime_dir, Some(runtime_dir.clone())); } // Assert that the chain spec file is correctly detected and used. assert_eq!(build_spec.chain, Some(chain_spec_path.to_string_lossy().to_string())); diff --git a/crates/pop-cli/src/commands/new/contract.rs b/crates/pop-cli/src/commands/new/contract.rs index 83cc41627..ac31227c8 100644 --- a/crates/pop-cli/src/commands/new/contract.rs +++ b/crates/pop-cli/src/commands/new/contract.rs @@ -7,7 +7,6 @@ use crate::{ }, common::helpers::check_destination_path, }; -use pop_common::manifest::{add_crate_to_workspace, find_workspace_toml}; use anyhow::Result; use clap::{ @@ -75,8 +74,8 @@ impl NewContractCommand { generate_contract_from_template(name, path, &template, &mut cli)?; // If the contract is part of a workspace, add it to that workspace - if let Some(workspace_toml) = find_workspace_toml(path) { - add_crate_to_workspace(&workspace_toml, path)?; + if let Some(workspace_toml) = rustilities::manifest::find_workspace_manifest(path) { + rustilities::manifest::add_crate_to_workspace(&workspace_toml, path)?; } Ok(template) diff --git a/crates/pop-cli/src/commands/new/pallet.rs b/crates/pop-cli/src/commands/new/pallet.rs index 2b18e2911..4e4653e24 100644 --- a/crates/pop-cli/src/commands/new/pallet.rs +++ b/crates/pop-cli/src/commands/new/pallet.rs @@ -12,7 +12,6 @@ use pop_chains::{ TemplatePalletConfig, TemplatePalletConfigCommonTypes, TemplatePalletOptions, TemplatePalletStorageTypes, create_pallet_template, }; -use pop_common::{add_crate_to_workspace, find_workspace_toml, prefix_with_current_dir_if_needed}; use std::{path::PathBuf, process::Command}; use strum::{EnumMessage, IntoEnumIterator}; @@ -154,13 +153,8 @@ impl NewPalletCommand { PathBuf::from(path) }; - // If the user has introduced something like pallets/my_pallet, probably it refers to - // ./pallets/my_pallet. We need to transform this path, as otherwise the Cargo.toml won't be - // detected and the pallet won't be added to the workspace. - let pallet_path = prefix_with_current_dir_if_needed(pallet_path); - // Determine if the pallet is being created inside a workspace - let workspace_toml = find_workspace_toml(&pallet_path); + let workspace_toml = rustilities::manifest::find_workspace_manifest(&pallet_path); check_destination_path(&pallet_path, cli)?; let spinner = cliclack::spinner(); @@ -182,7 +176,7 @@ impl NewPalletCommand { // If the pallet has been created inside a workspace, add it to that workspace if let Some(workspace_toml) = workspace_toml { - add_crate_to_workspace(&workspace_toml, &pallet_path)?; + rustilities::manifest::add_crate_to_workspace(&workspace_toml, &pallet_path)?; } // Format the dir. If this fails we do nothing, it's not a major failure diff --git a/crates/pop-cli/src/commands/up/rollup.rs b/crates/pop-cli/src/commands/up/rollup.rs index eab99b537..3140141cb 100644 --- a/crates/pop-cli/src/commands/up/rollup.rs +++ b/crates/pop-cli/src/commands/up/rollup.rs @@ -429,7 +429,7 @@ async fn generate_spec_files( } let mut build_spec = BuildSpecCommand { - id: Some(id), + para_id: Some(id), genesis_code: Some(true), genesis_state: Some(true), chain_type: Some(ChainType::Live), diff --git a/crates/pop-cli/src/common/builds.rs b/crates/pop-cli/src/common/builds.rs index 79e288f08..736d80a40 100644 --- a/crates/pop-cli/src/common/builds.rs +++ b/crates/pop-cli/src/common/builds.rs @@ -54,10 +54,19 @@ pub fn ensure_node_binary_exists( } } +#[cfg(feature = "chain")] +/// Represent how the contained Path should be used. +pub(crate) enum ChainPath { + /// The path's going to be used to search for something else inside it (eg, a runtime, node,...) + Base(PathBuf), + /// The path is the exact one that should be used + Exact(PathBuf), +} + /// Creates a chain specification builder based on project structure. /// /// # Arguments -/// * `path` - Path to the project. +/// * `path` - Path to the project. If `[ChainPath::Exact]` is used, it'll point to a runtime. /// * `profile` - Build profile to use. /// * `default_bootnode` - Whether to use default bootnode. /// * `cli` - Command line interface implementation. @@ -66,20 +75,33 @@ pub fn ensure_node_binary_exists( /// The chain spec builder for the node or the runtime. #[cfg(feature = "chain")] pub fn create_chain_spec_builder( - path: &Path, + path: ChainPath, profile: &Profile, default_bootnode: bool, cli: &mut impl Cli, ) -> anyhow::Result { - let default_node_path = path.join("node"); - if default_node_path.is_dir() { - let node_path = default_node_path.canonicalize()?; - cli.info(format!("Using node at {}", node_path.display()))?; - Ok(ChainSpecBuilder::Node { node_path, default_bootnode, profile: profile.clone() }) - } else { - let runtime_path = find_runtime_dir(path, cli)?; - cli.info(format!("Using runtime at {}", runtime_path.display()))?; - Ok(ChainSpecBuilder::Runtime { runtime_path, profile: profile.clone() }) + match path { + ChainPath::Base(path) => { + let default_node_path = path.join("node"); + if default_node_path.is_dir() { + let node_path = default_node_path.canonicalize()?; + cli.info(format!("Using node at {}", node_path.display()))?; + Ok(ChainSpecBuilder::Node { node_path, default_bootnode, profile: profile.clone() }) + } else { + let runtime_path = find_runtime_dir(&path, cli)?; + cli.info(format!("Using runtime at {}", runtime_path.display()))?; + Ok(ChainSpecBuilder::Runtime { runtime_path, profile: profile.clone() }) + } + }, + ChainPath::Exact(runtime_path) => { + let runtime_path = runtime_path.canonicalize()?; + cli.info(format!("Using runtime at {}", runtime_path.display()))?; + + Ok(ChainSpecBuilder::Runtime { + runtime_path: runtime_path.to_owned(), + profile: profile.clone(), + }) + }, } } @@ -389,7 +411,12 @@ version = "0.1.0" let mut cli = MockCli::new() .expect_info(format!("Using node at {}", node_dir.canonicalize()?.display())); - let result = create_chain_spec_builder(temp_dir.path(), &Profile::Release, true, &mut cli)?; + let result = create_chain_spec_builder( + ChainPath::Base(temp_dir.path().to_path_buf()), + &Profile::Release, + true, + &mut cli, + )?; // Verify it returns ChainSpecBuilder::Node variant match result { @@ -406,7 +433,7 @@ version = "0.1.0" #[test] #[cfg(feature = "chain")] - fn create_chain_spec_builder_with_runtime_works() -> anyhow::Result<()> { + fn create_chain_spec_builder_with_runtime_works_using_base_path() -> anyhow::Result<()> { let temp_dir = tempdir()?; // Create workspace structure @@ -435,8 +462,12 @@ version = "0.1.0" let mut cli = MockCli::new() .expect_info(format!("Using runtime at {}", runtime_dir.canonicalize()?.display())); - let result = - create_chain_spec_builder(temp_dir.path(), &Profile::Release, false, &mut cli)?; + let result = create_chain_spec_builder( + ChainPath::Base(temp_dir.path().to_path_buf()), + &Profile::Release, + false, + &mut cli, + )?; // Verify it returns ChainSpecBuilder::Runtime variant match result { @@ -449,4 +480,97 @@ version = "0.1.0" cli.verify() } + + #[test] + #[cfg(feature = "chain")] + fn create_chain_spec_builder_with_runtime_works_using_exact_path() -> anyhow::Result<()> { + let temp_dir = tempdir()?; + + // Create workspace structure + let workspace_toml = temp_dir.path().join("Cargo.toml"); + fs::write( + &workspace_toml, + r#"[workspace] +members = ["runtime"] + +[workspace.package] +name = "test-workspace" +"#, + )?; + + // Create runtime directory (no node directory) + let runtime_dir_not_called_runtime = temp_dir.path().join("something"); + fs::create_dir(&runtime_dir_not_called_runtime)?; + fs::write( + runtime_dir_not_called_runtime.join("Cargo.toml"), + r#"[package] +name = "runtime" +version = "0.1.0" +"#, + )?; + + let mut cli = MockCli::new().expect_info(format!( + "Using runtime at {}", + runtime_dir_not_called_runtime.canonicalize()?.display() + )); + + let result = create_chain_spec_builder( + ChainPath::Exact(runtime_dir_not_called_runtime.clone()), + &Profile::Release, + false, + &mut cli, + )?; + + // Verify it returns ChainSpecBuilder::Runtime variant + match result { + ChainSpecBuilder::Runtime { runtime_path, profile } => { + assert_eq!(runtime_path, runtime_dir_not_called_runtime.canonicalize()?); + assert_eq!(profile, Profile::Release); + }, + _ => panic!("Expected ChainSpecBuilder::Runtime variant"), + } + + cli.verify() + } + + #[test] + #[cfg(feature = "chain")] + fn create_chain_spec_builder_with_runtime_using_exact_path_fails_if_path_cannot_be_canonicalized() + -> anyhow::Result<()> { + let temp_dir = tempdir()?; + + // Create workspace structure + let workspace_toml = temp_dir.path().join("Cargo.toml"); + fs::write( + &workspace_toml, + r#"[workspace] +members = ["runtime"] + +[workspace.package] +name = "test-workspace" +"#, + )?; + + // Create runtime directory (no node directory) + let runtime_dir_not_called_runtime = temp_dir.path().join("something"); + let mut cli = MockCli::new().expect_info("nothing".to_string()); + + let result = create_chain_spec_builder( + ChainPath::Exact(runtime_dir_not_called_runtime), + &Profile::Release, + false, + &mut cli, + ); + + match result { + Err(err) if err.downcast_ref::().is_some() => { + assert_eq!( + err.downcast_ref::().unwrap().kind(), + std::io::ErrorKind::NotFound + ); + Ok(()) + }, + _ => panic!("The dir doesn't exist"), + } + } } diff --git a/crates/pop-cli/tests/chain.rs b/crates/pop-cli/tests/chain.rs index 691348c64..d3a66f0d5 100644 --- a/crates/pop-cli/tests/chain.rs +++ b/crates/pop-cli/tests/chain.rs @@ -116,7 +116,7 @@ async fn parachain_lifecycle() -> Result<()> { "spec", "--output", "./target/pop/test-spec.json", - "--id", + "--para-id", "2222", "--type", "development", diff --git a/crates/pop-common/src/helpers.rs b/crates/pop-common/src/helpers.rs index 369b15bd2..5dc33f379 100644 --- a/crates/pop-common/src/helpers.rs +++ b/crates/pop-common/src/helpers.rs @@ -5,7 +5,7 @@ use std::{ collections::HashMap, fs, io::{Read, Write}, - path::{Component, Path, PathBuf}, + path::{Path, PathBuf}, }; /// Replaces occurrences of specified strings in a file with new values. @@ -40,21 +40,6 @@ pub fn get_project_name_from_path<'a>(path: &'a Path, default: &'a str) -> &'a s path.file_name().and_then(|name| name.to_str()).unwrap_or(default) } -/// Transforms a path without prefix into a relative path starting at the current directory. -/// -/// # Arguments -/// * `path` - The path to be prefixed if needed. -pub fn prefix_with_current_dir_if_needed(path: PathBuf) -> PathBuf { - let components = &path.components().collect::>(); - if !components.is_empty() { - // If the first component is a normal component, we prefix the path with the current dir - if let Component::Normal(_) = components[0] { - return as AsRef>::as_ref(&Component::CurDir).join(path); - } - } - path -} - /// Returns the relative path from `base` to `full` if `full` is inside `base`. /// If `full` is outside `base`, returns the absolute path instead. /// @@ -104,33 +89,6 @@ mod tests { Ok(()) } - #[test] - fn prefix_with_current_dir_if_needed_works_well() { - let no_prefixed_path = PathBuf::from("my/path".to_string()); - let current_dir_prefixed_path = PathBuf::from("./my/path".to_string()); - let parent_dir_prefixed_path = PathBuf::from("../my/path".to_string()); - let root_dir_prefixed_path = PathBuf::from("/my/path".to_string()); - let empty_path = PathBuf::from("".to_string()); - - assert_eq!( - prefix_with_current_dir_if_needed(no_prefixed_path), - PathBuf::from("./my/path/".to_string()) - ); - assert_eq!( - prefix_with_current_dir_if_needed(current_dir_prefixed_path), - PathBuf::from("./my/path/".to_string()) - ); - assert_eq!( - prefix_with_current_dir_if_needed(parent_dir_prefixed_path), - PathBuf::from("../my/path/".to_string()) - ); - assert_eq!( - prefix_with_current_dir_if_needed(root_dir_prefixed_path), - PathBuf::from("/my/path/".to_string()) - ); - assert_eq!(prefix_with_current_dir_if_needed(empty_path), PathBuf::from("".to_string())); - } - #[test] fn get_relative_or_absolute_path_works() { [ diff --git a/crates/pop-common/src/lib.rs b/crates/pop-common/src/lib.rs index c1f82fa76..e040dc53b 100644 --- a/crates/pop-common/src/lib.rs +++ b/crates/pop-common/src/lib.rs @@ -8,11 +8,7 @@ use assert_cmd::cargo::cargo_bin; pub use build::Profile; pub use errors::Error; pub use git::{Git, GitHub, Release}; -pub use helpers::{ - get_project_name_from_path, get_relative_or_absolute_path, prefix_with_current_dir_if_needed, - replace_in_file, -}; -pub use manifest::{add_crate_to_workspace, find_workspace_toml}; +pub use helpers::{get_project_name_from_path, get_relative_or_absolute_path, replace_in_file}; pub use metadata::format_type; pub use signer::create_signer; pub use sourcing::set_executable_permission; diff --git a/crates/pop-common/src/manifest.rs b/crates/pop-common/src/manifest.rs index 9a71c23e0..849fd61af 100644 --- a/crates/pop-common/src/manifest.rs +++ b/crates/pop-common/src/manifest.rs @@ -5,10 +5,9 @@ use anyhow; pub use cargo_toml::{Dependency, LtoSetting, Manifest, Profile, Profiles}; use glob::glob; use std::{ - fs::{read_to_string, write}, + fs::write, path::{Path, PathBuf}, }; -use toml_edit::{Array, DocumentMut, Item, Value, value}; /// Parses the contents of a `Cargo.toml` manifest. /// @@ -27,74 +26,6 @@ pub fn from_path(path: &Path) -> Result { Ok(Manifest::from_path(path.canonicalize()?)?) } -/// This function is used to determine if a Path is contained inside a workspace, and returns a -/// PathBuf to the workspace Cargo.toml if found. -/// -/// # Arguments -/// * `target_dir` - A directory that may be contained inside a workspace -pub fn find_workspace_toml(target_dir: &Path) -> Option { - let mut dir = target_dir; - while let Some(parent) = dir.parent() { - // This condition is necessary to avoid that calling the function from a workspace using a - // path which isn't contained in a workspace returns `Some(Cargo.toml)` refering the - // workspace from where the function has been called instead of the expected `None`. - if parent.to_str() == Some("") { - return None; - } - let cargo_toml = parent.join("Cargo.toml"); - if cargo_toml.exists() && - let Ok(contents) = read_to_string(&cargo_toml) && - contents.contains("[workspace]") - { - return Some(cargo_toml); - } - dir = parent; - } - None -} - -/// 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<()> { - let toml_contents = read_to_string(workspace_toml)?; - let mut doc = toml_contents.parse::()?; - - // Find the workspace dir - let workspace_dir = workspace_toml.parent().expect("A file always lives inside a dir; qed"); - // Find the relative path to the crate from the workspace root - let crate_relative_path = crate_path.strip_prefix(workspace_dir)?; - - if let Some(Item::Table(workspace_table)) = doc.get_mut("workspace") { - if let Some(Item::Value(members_array)) = workspace_table.get_mut("members") { - if let Value::Array(array) = members_array { - let crate_relative_path = - crate_relative_path.to_str().expect("target's always a valid string; qed"); - let already_in_array = array - .iter() - .any(|member| matches!(member.as_str(), Some(s) if s == crate_relative_path)); - if !already_in_array { - array.push(crate_relative_path); - } - } else { - return Err(anyhow::anyhow!("Corrupted workspace")); - } - } else { - let mut toml_array = Array::new(); - toml_array - .push(crate_relative_path.to_str().expect("target's always a valid string; qed")); - workspace_table["members"] = value(toml_array); - } - } else { - return Err(anyhow::anyhow!("Corrupted workspace")); - } - - write(workspace_toml, doc.to_string())?; - Ok(()) -} - /// Get the names and paths of all cargo projects that are associated with a workspace manifest. /// /// # Arguments @@ -193,15 +124,13 @@ pub fn add_feature(project: &Path, (key, items): (String, Vec)) -> anyho #[cfg(test)] mod tests { use super::*; - use std::fs::{File, write}; + use std::fs::{File, read_to_string, write}; use tempfile::TempDir; struct TestBuilder { main_tempdir: TempDir, workspace: Option, - inside_workspace_dir: Option, workspace_cargo_toml: Option, - outside_workspace_dir: Option, } impl Default for TestBuilder { @@ -209,9 +138,7 @@ mod tests { Self { main_tempdir: TempDir::new().expect("Failed to create tempdir"), workspace: None, - inside_workspace_dir: None, workspace_cargo_toml: None, - outside_workspace_dir: None, } } } @@ -221,16 +148,6 @@ mod tests { Self { workspace: TempDir::new_in(self.main_tempdir.as_ref()).ok(), ..self } } - fn add_inside_workspace_dir(self) -> Self { - Self { - inside_workspace_dir: TempDir::new_in(self.workspace.as_ref().expect( - "add_inside_workspace_dir is only callable if workspace has been created", - )) - .ok(), - ..self - } - } - fn add_workspace_cargo_toml(self, cargo_toml_content: &str) -> Self { let workspace_cargo_toml = self .workspace @@ -242,10 +159,6 @@ mod tests { write(&workspace_cargo_toml, cargo_toml_content).expect("Failed to write Cargo.toml"); Self { workspace_cargo_toml: Some(workspace_cargo_toml.to_path_buf()), ..self } } - - fn add_outside_workspace_dir(self) -> Self { - Self { outside_workspace_dir: TempDir::new_in(self.main_tempdir.as_ref()).ok(), ..self } - } } #[test] @@ -267,251 +180,6 @@ mod tests { Ok(()) } - #[test] - fn find_workspace_toml_works_well() { - let test_builder = TestBuilder::default() - .add_workspace() - .add_inside_workspace_dir() - .add_workspace_cargo_toml( - r#"[workspace] - resolver = "2" - members = ["member1"] - "#, - ) - .add_outside_workspace_dir(); - assert!( - find_workspace_toml( - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace dir should exist") - .path() - ) - .is_some() - ); - assert_eq!( - find_workspace_toml( - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace dir should exist") - .path() - ) - .expect("The Cargo.toml should exist at this point"), - test_builder.workspace_cargo_toml.expect("Cargo.toml should exist") - ); - assert!( - find_workspace_toml( - test_builder - .outside_workspace_dir - .as_ref() - .expect("Outside workspace dir should exist") - .path() - ) - .is_none() - ); - // Calling the function from a relative path which parent is "" returns None - assert!(find_workspace_toml(&PathBuf::from("..")).is_none()); - } - - #[test] - fn add_crate_to_workspace_works_well_if_members_exists() { - let test_builder = TestBuilder::default() - .add_workspace() - .add_workspace_cargo_toml( - r#"[workspace] - resolver = "2" - members = ["member1"] - "#, - ) - .add_inside_workspace_dir(); - let add_crate = add_crate_to_workspace( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace dir should exist") - .path(), - ); - assert!(add_crate.is_ok()); - let content = read_to_string( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - ) - .expect("Cargo.toml should be readable"); - let doc = content.parse::().expect("This should work"); - if let Some(Item::Table(workspace_table)) = doc.get("workspace") { - if let Some(Item::Value(Value::Array(array))) = workspace_table.get("members") { - assert!(array.iter().any(|item| { - if let Value::String(item) = item { - // item is only the relative path from the Cargo.toml manifest, while - // test_buildder.insider_workspace_dir is the absolute path, so we can only - // test with contains - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace should exist") - .path() - .to_str() - .expect("Dir should be mapped to a str") - .contains(item.value()) - } else { - false - } - })); - } else { - panic!("This shouldn't be reached"); - } - } else { - panic!("This shouldn't be reached"); - } - - // Calling with a crate that's already in the workspace doesn't include it twice - let add_crate = add_crate_to_workspace( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace dir should exist") - .path(), - ); - assert!(add_crate.is_ok()); - let doc = content.parse::().expect("This should work"); - if let Some(Item::Table(workspace_table)) = doc.get("workspace") { - if let Some(Item::Value(Value::Array(array))) = workspace_table.get("members") { - assert_eq!( - array - .iter() - .filter(|item| { - if let Value::String(item) = item { - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace should exist") - .path() - .to_str() - .expect("Dir should be mapped to a str") - .contains(item.value()) - } else { - false - } - }) - .count(), - 1 - ); - } else { - panic!("This shouldn't be reached"); - } - } else { - panic!("This shouldn't be reached"); - } - } - - #[test] - fn add_crate_to_workspace_works_well_if_members_doesnt_exist() { - let test_builder = TestBuilder::default() - .add_workspace() - .add_workspace_cargo_toml( - r#"[workspace] - resolver = "2" - "#, - ) - .add_inside_workspace_dir(); - let add_crate = add_crate_to_workspace( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace dir should exist") - .path(), - ); - assert!(add_crate.is_ok()); - let content = read_to_string( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - ) - .expect("Cargo.toml should be readable"); - let doc = content.parse::().expect("This should work"); - if let Some(Item::Table(workspace_table)) = doc.get("workspace") { - if let Some(Item::Value(Value::Array(array))) = workspace_table.get("members") { - assert!(array.iter().any(|item| { - if let Value::String(item) = item { - test_builder - .inside_workspace_dir - .as_ref() - .expect("Inside workspace should exist") - .path() - .to_str() - .expect("Dir should be mapped to a str") - .contains(item.value()) - } else { - false - } - })); - } else { - panic!("This shouldn't be reached"); - } - } else { - panic!("This shouldn't be reached"); - } - } - - #[test] - fn add_crate_to_workspace_fails_if_crate_path_not_inside_workspace() { - let test_builder = TestBuilder::default() - .add_workspace() - .add_workspace_cargo_toml( - r#"[workspace] - resolver = "2" - members = ["member1"] - "#, - ) - .add_outside_workspace_dir(); - let add_crate = add_crate_to_workspace( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - test_builder - .outside_workspace_dir - .expect("Inside workspace dir should exist") - .path(), - ); - assert!(add_crate.is_err()); - } - - #[test] - fn add_crate_to_workspace_fails_if_members_not_an_array() { - let test_builder = TestBuilder::default() - .add_workspace() - .add_workspace_cargo_toml( - r#"[workspace] - resolver = "2" - members = "member1" - "#, - ) - .add_inside_workspace_dir(); - let add_crate = add_crate_to_workspace( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - test_builder - .inside_workspace_dir - .expect("Inside workspace dir should exist") - .path(), - ); - assert!(add_crate.is_err()); - } - - #[test] - fn add_crate_to_workspace_fails_if_workspace_isnt_workspace() { - let test_builder = TestBuilder::default() - .add_workspace() - .add_workspace_cargo_toml(r#""#) - .add_inside_workspace_dir(); - let add_crate = add_crate_to_workspace( - test_builder.workspace_cargo_toml.as_ref().expect("Workspace should exist"), - test_builder - .inside_workspace_dir - .expect("Inside workspace dir should exist") - .path(), - ); - assert!(add_crate.is_err()); - } - #[test] fn add_production_profile_works() { let test_builder = TestBuilder::default().add_workspace().add_workspace_cargo_toml( @@ -522,7 +190,7 @@ mod tests { let binding = test_builder.workspace.expect("Workspace should exist"); let project_path = binding.path(); - let cargo_toml_path = project_path.join("Cargo.toml"); + let cargo_toml_path = test_builder.workspace_cargo_toml.clone().unwrap(); // Call the function to add the production profile let result = add_production_profile(project_path); @@ -563,7 +231,7 @@ mod tests { vec!["feature-a".to_string(), "feature-b".to_string(), "feature-c".to_string()]; let binding = test_builder.workspace.expect("Workspace should exist"); let project_path = binding.path(); - let cargo_toml_path = project_path.join("Cargo.toml"); + let cargo_toml_path = test_builder.workspace_cargo_toml.clone().unwrap(); // Call the function to add the production profile let result = add_feature(