diff --git a/CHANGELOG.md b/CHANGELOG.md index e6b6227b..6db1a9d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ incremented for features. * cli: Programs embedded into genesis during tests will produce program logs. +### Fixes + +* cli: Allows Cargo.lock to exist in workspace subdirectories when publishing ([#593](https://github.com/project-serum/anchor/pull/593)). + ## [0.13.0] - 2021-08-08 ### Features diff --git a/Cargo.lock b/Cargo.lock index b0f60caa..faf0fe5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,7 @@ dependencies = [ "tar", "tokio 1.4.0", "toml", + "walkdir", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index faf2f27f..6b8f5d2a 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -36,3 +36,4 @@ reqwest = { version = "0.11.4", features = ["multipart", "blocking"] } tokio = "1.0" pathdiff = "0.2.0" cargo_toml = "0.9.2" +walkdir = "2" \ No newline at end of file diff --git a/cli/src/config.rs b/cli/src/config.rs index b8d730ca..69dbd518 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -9,6 +9,7 @@ use std::collections::BTreeMap; use std::convert::TryFrom; use std::fs::{self, File}; use std::io::prelude::*; +use std::ops::Deref; use std::path::Path; use std::path::PathBuf; use std::str::FromStr; @@ -48,6 +49,68 @@ impl std::convert::AsRef for WithPath { } } +#[derive(Debug, Clone, PartialEq)] +pub struct Manifest(cargo_toml::Manifest); + +impl Manifest { + pub fn from_path(p: impl AsRef) -> Result { + cargo_toml::Manifest::from_path(p) + .map(Manifest) + .map_err(Into::into) + } + + pub fn lib_name(&self) -> Result { + if self.lib.is_some() && self.lib.as_ref().unwrap().name.is_some() { + Ok(self + .lib + .as_ref() + .unwrap() + .name + .as_ref() + .unwrap() + .to_string()) + } else { + Ok(self + .package + .as_ref() + .ok_or_else(|| anyhow!("package section not provided"))? + .name + .to_string()) + } + } + + // Climbs each parent directory until we find a Cargo.toml. + pub fn discover() -> Result>> { + let _cwd = std::env::current_dir()?; + let mut cwd_opt = Some(_cwd.as_path()); + + while let Some(cwd) = cwd_opt { + for f in fs::read_dir(cwd)? { + let p = f?.path(); + if let Some(filename) = p.file_name() { + if filename.to_str() == Some("Cargo.toml") { + let m = WithPath::new(Manifest::from_path(&p)?, p); + return Ok(Some(m)); + } + } + } + + // Not found. Go up a directory level. + cwd_opt = cwd.parent(); + } + + Ok(None) + } +} + +impl Deref for Manifest { + type Target = cargo_toml::Manifest; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl WithPath { pub fn get_program_list(&self) -> Result> { // Canonicalize the workspace filepaths to compare with relative paths. @@ -82,7 +145,7 @@ impl WithPath { let mut r = vec![]; for path in self.get_program_list()? { let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"))?; - let lib_name = extract_lib_name(&path.join("Cargo.toml"))?; + let lib_name = Manifest::from_path(&path.join("Cargo.toml"))?.lib_name()?; r.push(Program { lib_name, path, @@ -97,16 +160,53 @@ impl WithPath { .workspace .members .iter() - .map(|m| PathBuf::from(m).canonicalize().unwrap()) + .map(|m| { + self.path() + .parent() + .unwrap() + .join(m) + .canonicalize() + .unwrap() + }) .collect(); let exclude = self .workspace .exclude .iter() - .map(|m| PathBuf::from(m).canonicalize().unwrap()) + .map(|m| { + self.path() + .parent() + .unwrap() + .join(m) + .canonicalize() + .unwrap() + }) .collect(); Ok((members, exclude)) } + + pub fn get_program(&self, name: &str) -> Result>> { + for program in self.read_all_programs()? { + let cargo_toml = program.path.join("Cargo.toml"); + if !cargo_toml.exists() { + return Err(anyhow!( + "Did not find Cargo.toml at the path: {}", + program.path.display() + )); + } + let p_lib_name = Manifest::from_path(&cargo_toml)?.lib_name()?; + if name == p_lib_name { + let path = self + .path() + .parent() + .unwrap() + .canonicalize()? + .join(&program.path); + return Ok(Some(WithPath::new(program, path))); + } + } + Ok(None) + } } impl std::ops::Deref for WithPath { @@ -174,71 +274,51 @@ impl Config { format!("projectserum/build:v{}", ver) } - pub fn discover( - cfg_override: &ConfigOverride, - ) -> Result, Option)>> { + pub fn discover(cfg_override: &ConfigOverride) -> Result>> { Config::_discover().map(|opt| { - opt.map(|(mut cfg, cargo_toml)| { + opt.map(|mut cfg| { if let Some(cluster) = cfg_override.cluster.clone() { cfg.provider.cluster = cluster; } - if let Some(wallet) = cfg_override.wallet.clone() { cfg.provider.wallet = wallet; } - (cfg, cargo_toml) + cfg }) }) } - // Searches all parent directories for an Anchor.toml file. - fn _discover() -> Result, Option)>> { - // Set to true if we ever see a Cargo.toml file when traversing the - // parent directories. - let mut cargo_toml = None; - + // Climbs each parent directory until we find an Anchor.toml. + fn _discover() -> Result>> { let _cwd = std::env::current_dir()?; let mut cwd_opt = Some(_cwd.as_path()); while let Some(cwd) = cwd_opt { - let files = fs::read_dir(cwd)?; - // Cargo.toml file for this directory level. - let mut cargo_toml_level = None; - let mut anchor_toml = None; - for f in files { + for f in fs::read_dir(cwd)? { let p = f?.path(); if let Some(filename) = p.file_name() { - if filename.to_str() == Some("Cargo.toml") { - cargo_toml_level = Some(p); - } else if filename.to_str() == Some("Anchor.toml") { - let mut cfg_file = File::open(&p)?; - let mut cfg_contents = String::new(); - cfg_file.read_to_string(&mut cfg_contents)?; - let cfg = cfg_contents.parse()?; - anchor_toml = Some((cfg, p)); + if filename.to_str() == Some("Anchor.toml") { + let cfg = Config::from_path(&p)?; + return Ok(Some(WithPath::new(cfg, p))); } } } - // Set the Cargo.toml if it's for a single package, i.e., not the - // root workspace Cargo.toml. - if cargo_toml.is_none() && cargo_toml_level.is_some() { - let toml = cargo_toml::Manifest::from_path(cargo_toml_level.as_ref().unwrap())?; - if toml.workspace.is_none() { - cargo_toml = cargo_toml_level; - } - } - - if let Some((cfg, parent)) = anchor_toml { - return Ok(Some((WithPath::new(cfg, parent), cargo_toml))); - } - cwd_opt = cwd.parent(); } Ok(None) } + fn from_path(p: impl AsRef) -> Result { + let mut cfg_file = File::open(&p)?; + let mut cfg_contents = String::new(); + cfg_file.read_to_string(&mut cfg_contents)?; + let cfg = cfg_contents.parse()?; + + Ok(cfg) + } + pub fn wallet_kp(&self) -> Result { solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string()) .map_err(|_| anyhow!("Unable to read keypair file")) @@ -382,21 +462,10 @@ pub struct GenesisEntry { pub program: String, } -pub fn extract_lib_name(cargo_toml: impl AsRef) -> Result { - let cargo_toml = cargo_toml::Manifest::from_path(cargo_toml)?; - if cargo_toml.lib.is_some() && cargo_toml.lib.as_ref().unwrap().name.is_some() { - Ok(cargo_toml.lib.unwrap().name.unwrap()) - } else { - Ok(cargo_toml - .package - .ok_or_else(|| anyhow!("Package section not provided"))? - .name) - } -} - #[derive(Debug, Clone)] pub struct Program { pub lib_name: String, + // Canonicalized path to the program directory. pub path: PathBuf, pub idl: Option, } @@ -463,7 +532,6 @@ pub struct ProgramWorkspace { pub struct AnchorPackage { pub name: String, pub address: String, - pub path: String, pub idl: Option, } @@ -479,19 +547,9 @@ impl AnchorPackage { .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))? .get(&name) .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?; - let path = program_details - .path - .clone() - // TODO: use a default path if one isn't provided? - .ok_or_else(|| anyhow!("Path to program binary not provided"))?; let idl = program_details.idl.clone(); let address = program_details.address.to_string(); - Ok(Self { - name, - path, - address, - idl, - }) + Ok(Self { name, address, idl }) } } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 7bf0b699..306c5d30 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,4 +1,6 @@ -use crate::config::{AnchorPackage, Config, ConfigOverride, Program, ProgramWorkspace, WithPath}; +use crate::config::{ + AnchorPackage, Config, ConfigOverride, Manifest, Program, ProgramWorkspace, WithPath, +}; use anchor_client::Cluster; use anchor_lang::idl::{IdlAccount, IdlInstruction}; use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize}; @@ -297,11 +299,8 @@ pub fn entry(opts: Opts) -> Result<()> { } fn init(cfg_override: &ConfigOverride, name: String, typescript: bool) -> Result<()> { - let cfg = Config::discover(cfg_override)?; - - if cfg.is_some() { - println!("Anchor workspace already initialized"); - } + let _ = + Config::discover(cfg_override)?.ok_or_else(|| anyhow!("Workspace already initialized"))?; fs::create_dir(name.clone())?; std::env::set_current_dir(&name)?; @@ -364,7 +363,7 @@ fn init(cfg_override: &ConfigOverride, name: String, typescript: bool) -> Result // Creates a new program crate in the `programs/` directory. fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { match cfg.path().parent() { None => { println!("Unable to make new program"); @@ -401,41 +400,14 @@ pub fn build( stdout: Option, // Used for the package registry server. stderr: Option, // Used for the package registry server. ) -> Result<()> { - // Change directories to the given `program_name`, if given. - let (cfg, _cargo) = Config::discover(cfg_override)?.expect("Not in workspace."); - - let mut did_find_program = false; + // Change to the workspace member directory, if needed. if let Some(program_name) = program_name.as_ref() { - for program in cfg.read_all_programs()? { - let cargo_toml = program.path.join("Cargo.toml"); - if !cargo_toml.exists() { - return Err(anyhow!( - "Did not find Cargo.toml at the path: {}", - program.path.display() - )); - } - let p_lib_name = config::extract_lib_name(&cargo_toml)?; - if program_name.as_str() == p_lib_name { - let program_path = cfg - .path() - .parent() - .unwrap() - .canonicalize()? - .join(program.path); - std::env::set_current_dir(&program_path)?; - did_find_program = true; - break; - } - } - } - if !did_find_program && program_name.is_some() { - return Err(anyhow!( - "{} is not part of the workspace", - program_name.as_ref().unwrap() - )); + cd_member(cfg_override, program_name)?; } - let (cfg, cargo) = Config::discover(cfg_override)?.expect("Not in workspace."); + let cfg = Config::discover(cfg_override)?.expect("Not in workspace."); + let cargo = Manifest::discover()?; + let idl_out = match idl { Some(idl) => Some(PathBuf::from(idl)), None => { @@ -454,17 +426,37 @@ pub fn build( }; match cargo { - None => build_all(&cfg, cfg.path(), idl_out, verifiable, solana_version)?, - Some(ct) => build_cwd( + // No Cargo.toml so build the entire workspace. + None => build_all( &cfg, - ct, + cfg.path(), idl_out, verifiable, solana_version, stdout, stderr, )?, - }; + // If the Cargo.toml is at the root, build the entire workspace. + Some(cargo) if cargo.path().parent() == cfg.path().parent() => build_all( + &cfg, + cfg.path(), + idl_out, + verifiable, + solana_version, + stdout, + stderr, + )?, + // Cargo.toml represents a single package. Build it. + Some(cargo) => build_cwd( + &cfg, + cargo.path().to_path_buf(), + idl_out, + verifiable, + solana_version, + stdout, + stderr, + )?, + } set_workspace_dir_or_exit(); @@ -477,6 +469,8 @@ fn build_all( idl_out: Option, verifiable: bool, solana_version: Option, + stdout: Option, // Used for the package registry server. + stderr: Option, // Used for the package registry server. ) -> Result<()> { let cur_dir = std::env::current_dir()?; let r = match cfg_path.parent() { @@ -489,8 +483,8 @@ fn build_all( idl_out.clone(), verifiable, solana_version.clone(), - None, - None, + stdout.as_ref().map(|f| f.try_clone()).transpose()?, + stderr.as_ref().map(|f| f.try_clone()).transpose()?, )?; } Ok(()) @@ -531,7 +525,7 @@ fn build_cwd_verifiable( ) -> Result<()> { // Create output dirs. let workspace_dir = cfg.path().parent().unwrap().canonicalize()?; - fs::create_dir_all(workspace_dir.join("target/deploy"))?; + fs::create_dir_all(workspace_dir.join("target/verifiable"))?; fs::create_dir_all(workspace_dir.join("target/idl"))?; let container_name = "anchor-program"; @@ -595,7 +589,7 @@ fn docker_build( stdout: Option, stderr: Option, ) -> Result<()> { - let binary_name = config::extract_lib_name(&cargo_toml)?; + let binary_name = Manifest::from_path(&cargo_toml)?.lib_name()?; // Docker vars. let image_name = cfg.docker(); @@ -718,7 +712,7 @@ fn docker_build( .parent() .unwrap() .canonicalize()? - .join(format!("target/deploy/{}.so", binary_name)) + .join(format!("target/verifiable/{}.so", binary_name)) .display() .to_string(); @@ -774,45 +768,14 @@ fn verify( program_name: Option, solana_version: Option, ) -> Result<()> { - // Change directories to the given `program_name`, if given. - let (cfg, _cargo) = Config::discover(cfg_override)?.expect("Not in workspace."); - let mut did_find_program = false; + // Change to the workspace member directory, if needed. if let Some(program_name) = program_name.as_ref() { - for program in cfg.read_all_programs()? { - let cargo_toml = program.path.join("Cargo.toml"); - if !cargo_toml.exists() { - return Err(anyhow!( - "Did not find Cargo.toml at the path: {}", - program.path.display() - )); - } - let p_lib_name = config::extract_lib_name(&cargo_toml)?; - if program_name.as_str() == p_lib_name { - let program_path = cfg - .path() - .parent() - .unwrap() - .canonicalize()? - .join(program.path); - std::env::set_current_dir(&program_path)?; - did_find_program = true; - break; - } - } - } - if !did_find_program && program_name.is_some() { - return Err(anyhow!( - "{} is not part of the workspace", - program_name.as_ref().unwrap() - )); + cd_member(cfg_override, program_name)?; } // Proceed with the command. - let (cfg, cargo) = Config::discover(cfg_override)?.expect("Not in workspace."); - let cargo = cargo.ok_or_else(|| { - anyhow!("Must be inside program subdirectory if no program name is given.") - })?; - let program_dir = cargo.parent().unwrap(); + let cfg = Config::discover(cfg_override)?.expect("Not in workspace."); + let cargo = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?; // Build the program we want to verify. let cur_dir = std::env::current_dir()?; @@ -831,23 +794,12 @@ fn verify( std::env::set_current_dir(&cur_dir)?; // Verify binary. - let binary_name = { - let cargo_toml = cargo_toml::Manifest::from_path(&cargo)?; - match cargo_toml.lib { - None => { - cargo_toml - .package - .ok_or_else(|| anyhow!("Package section not provided"))? - .name - } - Some(lib) => lib.name.ok_or_else(|| anyhow!("Name not provided"))?, - } - }; + let binary_name = cargo.lib_name()?; let bin_path = cfg .path() .parent() .ok_or_else(|| anyhow!("Unable to find workspace root"))? - .join("target/deploy/") + .join("target/verifiable/") .join(format!("{}.so", binary_name)); let bin_ver = verify_bin(program_id, &bin_path, cfg.provider.cluster.url())?; @@ -859,7 +811,6 @@ fn verify( // Verify IDL (only if it's not a buffer account). if let Some(local_idl) = extract_idl("src/lib.rs")? { if bin_ver.state != BinVerificationState::Buffer { - std::env::set_current_dir(program_dir)?; let deployed_idl = fetch_idl(cfg_override, program_id)?; if local_idl != deployed_idl { println!("Error: IDLs don't match"); @@ -873,6 +824,27 @@ fn verify( Ok(()) } +fn cd_member(cfg_override: &ConfigOverride, program_name: &str) -> Result<()> { + // Change directories to the given `program_name`, if given. + let cfg = Config::discover(cfg_override)?.expect("Not in workspace."); + + for program in cfg.read_all_programs()? { + let cargo_toml = program.path.join("Cargo.toml"); + if !cargo_toml.exists() { + return Err(anyhow!( + "Did not find Cargo.toml at the path: {}", + program.path.display() + )); + } + let p_lib_name = Manifest::from_path(&cargo_toml)?.lib_name()?; + if program_name == p_lib_name { + std::env::set_current_dir(&program.path)?; + return Ok(()); + } + } + return Err(anyhow!("{} is not part of the workspace", program_name,)); +} + pub fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result { let client = RpcClient::new(cluster.to_string()); @@ -954,9 +926,7 @@ pub enum BinVerificationState { // Fetches an IDL for the given program_id. fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result { - let cfg = Config::discover(cfg_override)? - .expect("Inside a workspace") - .0; + let cfg = Config::discover(cfg_override)?.expect("Inside a workspace"); let client = RpcClient::new(cfg.provider.cluster.url().to_string()); let mut account = client @@ -1017,7 +987,7 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> { } fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let keypair = cfg.provider.wallet.to_string(); let bytes = std::fs::read(idl_filepath)?; @@ -1035,7 +1005,7 @@ fn idl_write_buffer( program_id: Pubkey, idl_filepath: String, ) -> Result { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let keypair = cfg.provider.wallet.to_string(); let bytes = std::fs::read(idl_filepath)?; @@ -1051,7 +1021,7 @@ fn idl_write_buffer( } fn idl_set_buffer(cfg_override: &ConfigOverride, program_id: Pubkey, buffer: Pubkey) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string()) .map_err(|_| anyhow!("Unable to read keypair file"))?; let client = RpcClient::new(cfg.provider.cluster.url().to_string()); @@ -1105,7 +1075,7 @@ fn idl_upgrade( } fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let client = RpcClient::new(cfg.provider.cluster.url().to_string()); let idl_address = { let account = client @@ -1135,7 +1105,7 @@ fn idl_set_authority( address: Option, new_authority: Pubkey, ) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { // Misc. let idl_address = match address { None => IdlAccount::address(&program_id), @@ -1306,7 +1276,7 @@ fn test( skip_build: bool, extra_args: Vec, ) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { // Build if needed. if !skip_build { build(cfg_override, None, false, None, None, None, None)?; @@ -1534,7 +1504,7 @@ fn _deploy( cfg_override: &ConfigOverride, program_str: Option, ) -> Result> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let url = cfg.provider.cluster.url().to_string(); let keypair = cfg.provider.wallet.to_string(); @@ -1615,7 +1585,7 @@ fn upgrade( let path: PathBuf = program_filepath.parse().unwrap(); let program_filepath = path.canonicalize()?.display().to_string(); - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let exit = std::process::Command::new("solana") .arg("program") .arg("deploy") @@ -1655,7 +1625,7 @@ fn launch( )?; let programs = _deploy(cfg_override, program_name)?; - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let keypair = cfg.provider.wallet.to_string(); // Add metadata to all IDLs. @@ -1680,10 +1650,7 @@ fn launch( // The Solana CLI doesn't redeploy a program if this file exists. // So remove it to make all commands explicit. fn clear_program_keys(cfg_override: &ConfigOverride) -> Result<()> { - let config = Config::discover(cfg_override) - .unwrap_or_default() - .unwrap() - .0; + let config = Config::discover(cfg_override).unwrap_or_default().unwrap(); for program in config.read_all_programs()? { let anchor_keypair_path = program.anchor_keypair_path(); @@ -1827,7 +1794,7 @@ fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { println!("Running migration deploy script"); let url = cfg.provider.cluster.url().to_string(); @@ -1874,10 +1841,7 @@ fn migrate(cfg_override: &ConfigOverride) -> Result<()> { } fn set_workspace_dir_or_exit() { - let d = match Config::discover(&ConfigOverride { - cluster: None, - wallet: None, - }) { + let d = match Config::discover(&ConfigOverride::default()) { Err(_) => { println!("Not in anchor workspace."); std::process::exit(1); @@ -1889,7 +1853,7 @@ fn set_workspace_dir_or_exit() { println!("Not in anchor workspace."); std::process::exit(1); } - Some((cfg, _inside_cargo)) => { + Some(cfg) => { match cfg.path().parent() { None => { println!("Unable to make new program"); @@ -1938,7 +1902,7 @@ fn cluster(_cmd: ClusterCommand) -> Result<()> { } fn shell(cfg_override: &ConfigOverride) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let programs = { // Create idl map from all workspace programs. let mut idls: HashMap = cfg @@ -2005,7 +1969,7 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> { } fn run(cfg_override: &ConfigOverride, script: String) -> Result<()> { - with_workspace(cfg_override, |cfg, _cargo| { + with_workspace(cfg_override, |cfg| { let script = cfg .scripts .get(&script) @@ -2040,9 +2004,21 @@ fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> { fn publish(cfg_override: &ConfigOverride, program_name: String) -> Result<()> { // Discover the various workspace configs. - let (cfg, _cargo_path) = Config::discover(cfg_override)?.expect("Not in workspace."); + let cfg = Config::discover(cfg_override)?.expect("Not in workspace."); - if !Path::new("Cargo.lock").exists() { + let program = cfg + .get_program(&program_name)? + .ok_or_else(|| anyhow!("Workspace member not found"))?; + + let program_cargo_lock = pathdiff::diff_paths( + program.path().join("Cargo.lock"), + cfg.path().parent().unwrap(), + ) + .ok_or_else(|| anyhow!("Unable to diff Cargo.lock path"))?; + let cargo_lock = Path::new("Cargo.lock"); + + // There must be a Cargo.lock + if !program_cargo_lock.exists() && !cargo_lock.exists() { return Err(anyhow!("Cargo.lock must exist for a verifiable build")); } @@ -2081,29 +2057,59 @@ fn publish(cfg_override: &ConfigOverride, program_name: String) -> Result<()> { let mut tar = tar::Builder::new(enc); // Files that will always be included if they exist. + println!("PACKING: Anchor.toml"); tar.append_path("Anchor.toml")?; - tar.append_path("Cargo.lock")?; + if cargo_lock.exists() { + println!("PACKING: Cargo.lock"); + tar.append_path(cargo_lock)?; + } if Path::new("Cargo.toml").exists() { + println!("PACKING: Cargo.toml"); tar.append_path("Cargo.toml")?; } if Path::new("LICENSE").exists() { + println!("PACKING: LICENSE"); tar.append_path("LICENSE")?; } if Path::new("README.md").exists() { + println!("PACKING: README.md"); tar.append_path("README.md")?; } // All workspace programs. for path in cfg.get_program_list()? { - let mut relative_path = pathdiff::diff_paths(path, cfg.path().parent().unwrap()) - .ok_or_else(|| anyhow!("Unable to diff paths"))?; + let mut dirs = walkdir::WalkDir::new(&path) + .into_iter() + .filter_entry(|e| !is_hidden(e)); - // HACK for workspaces wtih single programs. Change this. - if relative_path.display().to_string() == *"" { - relative_path = "src".into(); + // Skip the parent dir. + let _ = dirs.next().unwrap()?; + + for entry in dirs { + let e = entry.map_err(|e| anyhow!("{:?}", e))?; + + let e = pathdiff::diff_paths(e.path(), cfg.path().parent().unwrap()) + .ok_or_else(|| anyhow!("Unable to diff paths"))?; + + let path_str = e.display().to_string(); + + // Skip target dir. + if !path_str.contains("target/") && !path_str.contains("/target") { + // Only add the file if it's not empty. + let metadata = std::fs::File::open(&e)?.metadata()?; + if metadata.len() > 0 { + println!("PACKING: {}", e.display().to_string()); + if e.is_dir() { + tar.append_dir_all(&e, &e)?; + } else { + tar.append_path(&e)?; + } + } + } } - tar.append_dir_all(relative_path.clone(), relative_path)?; } + + // Tar pack complete. tar.into_inner()?; // Upload the tarball to the server. @@ -2158,22 +2164,27 @@ fn registry_api_token(_cfg_override: &ConfigOverride) -> Result { // // The closure passed into this function must never change the working directory // to be outside the workspace. Doing so will have undefined behavior. -fn with_workspace( - cfg_override: &ConfigOverride, - f: impl FnOnce(&WithPath, Option) -> R, -) -> R { +fn with_workspace(cfg_override: &ConfigOverride, f: impl FnOnce(&WithPath) -> R) -> R { set_workspace_dir_or_exit(); clear_program_keys(cfg_override).unwrap(); - let (cfg, cargo_toml) = Config::discover(cfg_override) + let cfg = Config::discover(cfg_override) .expect("Previously set the workspace dir") .expect("Anchor.toml must always exist"); - let r = f(&cfg, cargo_toml); + let r = f(&cfg); set_workspace_dir_or_exit(); clear_program_keys(cfg_override).unwrap(); r } + +fn is_hidden(entry: &walkdir::DirEntry) -> bool { + entry + .file_name() + .to_str() + .map(|s| s == "." || s.starts_with('.') || s == "target") + .unwrap_or(false) +} diff --git a/docs/src/getting-started/publishing.md b/docs/src/getting-started/publishing.md index 85db2ac5..43e7f6db 100644 --- a/docs/src/getting-started/publishing.md +++ b/docs/src/getting-started/publishing.md @@ -40,7 +40,7 @@ cluster = "mainnet" wallet = "~/.config/solana/id.json" [programs.mainnet] -multisig = { address = "A9HAbnCwoD6f2NkZobKFf6buJoN9gUVVvX5PoUnDHS6u", path = "./target/deploy/multisig.so", idl = "./target/idl/multisig.json" } +multisig = "A9HAbnCwoD6f2NkZobKFf6buJoN9gUVVvX5PoUnDHS6u" ``` Here there are four sections. @@ -53,8 +53,8 @@ Here there are four sections. standard Anchor workflow, this can be ommitted. For programs not written in Anchor but still want to publish, this should be added. 3. `[provider]` - configures the wallet and cluster settings. Here, `mainnet` is used because the registry only supports `mainnet` binary verification at the moment. -3. `[programs.mainnet]` - configures each program in the workpace. Here the - `address` of the program to verify and the `path` to it's binary build artifact. For Anchor programs with an **IDL**, an `idl = ""` field should also be provided. +3. `[programs.mainnet]` - configures each program in the workpace, providing + the `address` of the program to verify. ::: tip When defining program in `[programs.mainnet]`, make sure the name provided