diff --git a/CHANGELOG.md b/CHANGELOG.md index c65f2cbc17..9ef4602b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ The minor version will be incremented upon a breaking change and the patch versi - lang: Fix using non-instruction composite accounts with `declare_program!` ([#3290](https://github.com/coral-xyz/anchor/pull/3290)). - idl: Fix instructions with tuple parameters not producing an error([#3294](https://github.com/coral-xyz/anchor/pull/3294)). - ts: Update `engines.node` to `>= 17` ([#3301](https://github.com/coral-xyz/anchor/pull/3301)). +- cli: Use OS-agnostic paths ([#3307](https://github.com/coral-xyz/anchor/pull/3307)). ### Breaking diff --git a/cli/src/config.rs b/cli/src/config.rs index 5eb6d416d2..b844dfc195 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -243,7 +243,10 @@ impl WithPath { let cargo = Manifest::from_path(path.join("Cargo.toml"))?; let lib_name = cargo.lib_name()?; - let idl_filepath = format!("target/idl/{lib_name}.json"); + let idl_filepath = Path::new("target") + .join("idl") + .join(&lib_name) + .with_extension("json"); let idl = fs::read(idl_filepath) .ok() .map(|bytes| serde_json::from_reader(&*bytes)) @@ -257,7 +260,10 @@ impl WithPath { }); } for (lib_name, path) in self.get_solidity_program_list()? { - let idl_filepath = format!("target/idl/{lib_name}.json"); + let idl_filepath = Path::new("target") + .join("idl") + .join(&lib_name) + .with_extension("json"); let idl = fs::read(idl_filepath) .ok() .map(|bytes| serde_json::from_reader(&*bytes)) @@ -1131,7 +1137,7 @@ impl From<_Validator> for Validator { url: _validator.url, ledger: _validator .ledger - .unwrap_or_else(|| DEFAULT_LEDGER_PATH.to_string()), + .unwrap_or_else(|| get_default_ledger_path().display().to_string()), limit_ledger_size: _validator.limit_ledger_size, rpc_port: _validator .rpc_port @@ -1169,7 +1175,10 @@ impl From for _Validator { } } -pub const DEFAULT_LEDGER_PATH: &str = ".anchor/test-ledger"; +pub fn get_default_ledger_path() -> PathBuf { + Path::new(".anchor").join("test-ledger") +} + const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0"; impl Merge for _Validator { @@ -1282,12 +1291,12 @@ impl Program { // Lazily initializes the keypair file with a new key if it doesn't exist. pub fn keypair_file(&self) -> Result> { - let deploy_dir_path = "target/deploy/"; - fs::create_dir_all(deploy_dir_path) - .with_context(|| format!("Error creating directory with path: {deploy_dir_path}"))?; + let deploy_dir_path = Path::new("target").join("deploy"); + fs::create_dir_all(&deploy_dir_path) + .with_context(|| format!("Error creating directory with path: {deploy_dir_path:?}"))?; let path = std::env::current_dir() .expect("Must have current dir") - .join(format!("target/deploy/{}-keypair.json", self.lib_name)); + .join(deploy_dir_path.join(format!("{}-keypair.json", self.lib_name))); if path.exists() { return Ok(WithPath::new( File::open(&path) @@ -1303,11 +1312,10 @@ impl Program { } pub fn binary_path(&self, verifiable: bool) -> PathBuf { - let path = if verifiable { - format!("target/verifiable/{}.so", self.lib_name) - } else { - format!("target/deploy/{}.so", self.lib_name) - }; + let path = Path::new("target") + .join(if verifiable { "verifiable" } else { "deploy" }) + .join(&self.lib_name) + .with_extension("so"); std::env::current_dir() .expect("Must have current dir") @@ -1389,7 +1397,13 @@ macro_rules! home_path { impl Default for $my_struct { fn default() -> Self { - $my_struct(home_dir().unwrap().join($path).display().to_string()) + $my_struct( + home_dir() + .unwrap() + .join($path.replace('/', std::path::MAIN_SEPARATOR_STR)) + .display() + .to_string(), + ) } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 808e43d4e2..51c280cc83 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,7 +1,7 @@ use crate::config::{ - AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, Manifest, ProgramArch, - ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator, WithPath, - DEFAULT_LEDGER_PATH, SHUTDOWN_WAIT, STARTUP_WAIT, + get_default_ledger_path, AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, + Manifest, ProgramArch, ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator, + WithPath, SHUTDOWN_WAIT, STARTUP_WAIT, }; use anchor_client::Cluster; use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY}; @@ -1028,7 +1028,8 @@ fn init( } // Build the migrations directory. - fs::create_dir_all("migrations")?; + let migrations_path = Path::new("migrations"); + fs::create_dir_all(migrations_path)?; let license = get_npm_init_license()?; @@ -1038,8 +1039,7 @@ fn init( let mut package_json = File::create("package.json")?; package_json.write_all(rust_template::package_json(jest, license).as_bytes())?; - let mut deploy = File::create("migrations/deploy.js")?; - + let mut deploy = File::create(migrations_path.join("deploy.js"))?; deploy.write_all(rust_template::deploy_script().as_bytes())?; } else { // Build typescript config @@ -1049,7 +1049,7 @@ fn init( let mut ts_package_json = File::create("package.json")?; ts_package_json.write_all(rust_template::ts_package_json(jest, license).as_bytes())?; - let mut deploy = File::create("migrations/deploy.ts")?; + let mut deploy = File::create(migrations_path.join("deploy.ts"))?; deploy.write_all(rust_template::ts_deploy_script().as_bytes())?; } @@ -1171,7 +1171,11 @@ pub type Files = Vec<(PathBuf, String)>; /// ``` pub fn create_files(files: &Files) -> Result<()> { for (path, content) in files { - let path = Path::new(path); + let path = path + .display() + .to_string() + .replace('/', std::path::MAIN_SEPARATOR_STR); + let path = Path::new(&path); if path.exists() { continue; } @@ -1228,7 +1232,7 @@ pub fn expand( let cfg_parent = workspace_cfg.path().parent().expect("Invalid Anchor.toml"); let cargo = Manifest::discover()?; - let expansions_path = cfg_parent.join(".anchor/expanded-macros"); + let expansions_path = cfg_parent.join(".anchor").join("expanded-macros"); fs::create_dir_all(&expansions_path)?; match cargo { @@ -1347,13 +1351,13 @@ pub fn build( let idl_out = match idl { Some(idl) => Some(PathBuf::from(idl)), - None => Some(cfg_parent.join("target/idl")), + None => Some(cfg_parent.join("target").join("idl")), }; fs::create_dir_all(idl_out.as_ref().unwrap())?; let idl_ts_out = match idl_ts { Some(idl_ts) => Some(PathBuf::from(idl_ts)), - None => Some(cfg_parent.join("target/types")), + None => Some(cfg_parent.join("target").join("types")), }; fs::create_dir_all(idl_ts_out.as_ref().unwrap())?; @@ -1563,9 +1567,10 @@ fn build_cwd_verifiable( ) -> Result<()> { // Create output dirs. let workspace_dir = cfg.path().parent().unwrap().canonicalize()?; - fs::create_dir_all(workspace_dir.join("target/verifiable"))?; - fs::create_dir_all(workspace_dir.join("target/idl"))?; - fs::create_dir_all(workspace_dir.join("target/types"))?; + let target_dir = workspace_dir.join("target"); + fs::create_dir_all(target_dir.join("verifiable"))?; + fs::create_dir_all(target_dir.join("idl"))?; + fs::create_dir_all(target_dir.join("types"))?; if !&cfg.workspace.types.is_empty() { fs::create_dir_all(workspace_dir.join(&cfg.workspace.types))?; } @@ -1595,12 +1600,20 @@ fn build_cwd_verifiable( let idl = generate_idl(cfg, skip_lint, no_docs, &cargo_args)?; // Write out the JSON file. println!("Writing the IDL file"); - let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.metadata.name)); + let out_file = workspace_dir + .join("target") + .join("idl") + .join(&idl.metadata.name) + .with_extension("json"); write_idl(&idl, OutFile::File(out_file))?; // Write out the TypeScript type. println!("Writing the .ts file"); - let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.metadata.name)); + let ts_file = workspace_dir + .join("target") + .join("types") + .join(&idl.metadata.name) + .with_extension("ts"); fs::write(&ts_file, idl_ts(&idl)?)?; // Copy out the TypeScript type. @@ -1802,7 +1815,12 @@ fn docker_build_bpf( println!("Copying out the build artifacts"); let out_file = cfg_parent .canonicalize()? - .join(format!("target/verifiable/{binary_name}.so")) + .join( + Path::new("target") + .join("verifiable") + .join(&binary_name) + .with_extension("so"), + ) .display() .to_string(); @@ -2053,8 +2071,10 @@ fn verify( .path() .parent() .ok_or_else(|| anyhow!("Unable to find workspace root"))? - .join("target/verifiable/") - .join(format!("{binary_name}.so")); + .join("target") + .join("verifiable") + .join(&binary_name) + .with_extension("so"); let url = cluster_url(&cfg, &cfg.test_validator); let bin_ver = verify_bin(program_id, &bin_path, &url)?; @@ -3417,7 +3437,8 @@ fn validator_flags( idl.address = address; // Persist it. - let idl_out = PathBuf::from("target/idl") + let idl_out = Path::new("target") + .join("idl") .join(&idl.metadata.name) .with_extension("json"); write_idl(idl, OutFile::File(idl_out))?; @@ -3547,20 +3568,24 @@ fn validator_flags( } fn stream_logs(config: &WithPath, rpc_url: &str) -> Result> { - let program_logs_dir = ".anchor/program-logs"; - if Path::new(program_logs_dir).exists() { - fs::remove_dir_all(program_logs_dir)?; + let program_logs_dir = Path::new(".anchor").join("program-logs"); + if program_logs_dir.exists() { + fs::remove_dir_all(&program_logs_dir)?; } - fs::create_dir_all(program_logs_dir)?; + fs::create_dir_all(&program_logs_dir)?; + let mut handles = vec![]; for program in config.read_all_programs()? { - let idl = fs::read(format!("target/idl/{}.json", program.lib_name))?; + let idl_path = Path::new("target") + .join("idl") + .join(&program.lib_name) + .with_extension("json"); + let idl = fs::read(idl_path)?; let idl = convert_idl(&idl)?; - let log_file = File::create(format!( - "{}/{}.{}.log", - program_logs_dir, idl.address, program.lib_name, - ))?; + let log_file = File::create( + program_logs_dir.join(format!("{}.{}.log", idl.address, program.lib_name)), + )?; let stdio = std::process::Stdio::from(log_file); let child = std::process::Command::new("solana") .arg("logs") @@ -3574,7 +3599,8 @@ fn stream_logs(config: &WithPath, rpc_url: &str) -> Result) -> Result<( Some(TestValidator { validator: Some(validator), .. - }) => &validator.ledger, - _ => DEFAULT_LEDGER_PATH, + }) => PathBuf::from(&validator.ledger), + _ => get_default_ledger_path(), }; - let ledger_path = Path::new(ledger_path); if !ledger_path.is_relative() { // Prevent absolute paths to avoid someone using / or similar, as the @@ -3704,15 +3729,13 @@ fn test_validator_file_paths(test_validator: &Option) -> Result<( std::process::exit(1); } if ledger_path.exists() { - fs::remove_dir_all(ledger_path)?; + fs::remove_dir_all(&ledger_path)?; } - fs::create_dir_all(ledger_path)?; + fs::create_dir_all(&ledger_path)?; - Ok(( - ledger_path.to_owned(), - ledger_path.join("test-ledger-log.txt"), - )) + let log_path = ledger_path.join("test-ledger-log.txt"); + Ok((ledger_path, log_path)) } fn cluster_url(cfg: &Config, test_validator: &Option) -> String { @@ -3825,7 +3848,8 @@ fn deploy( idl.address = program_id.to_string(); // Persist it. - let idl_out = PathBuf::from("target/idl") + let idl_out = Path::new("target") + .join("idl") .join(&idl.metadata.name) .with_extension("json"); write_idl(idl, OutFile::File(idl_out))?; @@ -4267,12 +4291,14 @@ fn run(cfg_override: &ConfigOverride, script: String, script_args: Vec) } fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> { - let dir = shellexpand::tilde("~/.config/anchor"); - if !Path::new(&dir.to_string()).exists() { - fs::create_dir(dir.to_string())?; + let anchor_dir = Path::new(&*shellexpand::tilde("~")) + .join(".config") + .join("anchor"); + if !anchor_dir.exists() { + fs::create_dir(&anchor_dir)?; } - std::env::set_current_dir(dir.to_string())?; + std::env::set_current_dir(&anchor_dir)?; // Freely overwrite the entire file since it's not used for anything else. let mut file = File::create("credentials")?; @@ -4467,8 +4493,11 @@ fn registry_api_token(_cfg_override: &ConfigOverride) -> Result { struct Credentials { registry: Registry, } - let filename = shellexpand::tilde("~/.config/anchor/credentials"); - let mut file = File::open(filename.to_string())?; + let filename = Path::new(&*shellexpand::tilde("~")) + .join(".config") + .join("anchor") + .join("credentials"); + let mut file = File::open(filename)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; @@ -4730,7 +4759,8 @@ fn get_node_dns_option() -> Result<&'static str> { // of spaces in keypair/binary paths, but this should be fixed in the Solana CLI // and removed here. fn strip_workspace_prefix(absolute_path: String) -> String { - let workspace_prefix = std::env::current_dir().unwrap().display().to_string() + "/"; + let workspace_prefix = + std::env::current_dir().unwrap().display().to_string() + std::path::MAIN_SEPARATOR_STR; absolute_path .strip_prefix(&workspace_prefix) .unwrap_or(&absolute_path)