瀏覽代碼

cli: Fix Cargo.lock in workspace subdirectories when publishing (#593)

Armani Ferrante 4 年之前
父節點
當前提交
bd68e28f15
共有 6 個文件被更改,包括 280 次插入205 次删除
  1. 4 0
      CHANGELOG.md
  2. 1 0
      Cargo.lock
  3. 1 0
      cli/Cargo.toml
  4. 123 65
      cli/src/config.rs
  5. 148 137
      cli/src/lib.rs
  6. 3 3
      docs/src/getting-started/publishing.md

+ 4 - 0
CHANGELOG.md

@@ -15,6 +15,10 @@ incremented for features.
 
 
 * cli: Programs embedded into genesis during tests will produce program logs.
 * 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
 ## [0.13.0] - 2021-08-08
 
 
 ### Features
 ### Features

+ 1 - 0
Cargo.lock

@@ -159,6 +159,7 @@ dependencies = [
  "tar",
  "tar",
  "tokio 1.4.0",
  "tokio 1.4.0",
  "toml",
  "toml",
+ "walkdir",
 ]
 ]
 
 
 [[package]]
 [[package]]

+ 1 - 0
cli/Cargo.toml

@@ -36,3 +36,4 @@ reqwest = { version = "0.11.4", features = ["multipart", "blocking"] }
 tokio = "1.0"
 tokio = "1.0"
 pathdiff = "0.2.0"
 pathdiff = "0.2.0"
 cargo_toml = "0.9.2"
 cargo_toml = "0.9.2"
+walkdir = "2"

+ 123 - 65
cli/src/config.rs

@@ -9,6 +9,7 @@ use std::collections::BTreeMap;
 use std::convert::TryFrom;
 use std::convert::TryFrom;
 use std::fs::{self, File};
 use std::fs::{self, File};
 use std::io::prelude::*;
 use std::io::prelude::*;
+use std::ops::Deref;
 use std::path::Path;
 use std::path::Path;
 use std::path::PathBuf;
 use std::path::PathBuf;
 use std::str::FromStr;
 use std::str::FromStr;
@@ -48,6 +49,68 @@ impl<T> std::convert::AsRef<T> for WithPath<T> {
     }
     }
 }
 }
 
 
+#[derive(Debug, Clone, PartialEq)]
+pub struct Manifest(cargo_toml::Manifest);
+
+impl Manifest {
+    pub fn from_path(p: impl AsRef<Path>) -> Result<Self> {
+        cargo_toml::Manifest::from_path(p)
+            .map(Manifest)
+            .map_err(Into::into)
+    }
+
+    pub fn lib_name(&self) -> Result<String> {
+        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<Option<WithPath<Manifest>>> {
+        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<Config> {
 impl WithPath<Config> {
     pub fn get_program_list(&self) -> Result<Vec<PathBuf>> {
     pub fn get_program_list(&self) -> Result<Vec<PathBuf>> {
         // Canonicalize the workspace filepaths to compare with relative paths.
         // Canonicalize the workspace filepaths to compare with relative paths.
@@ -82,7 +145,7 @@ impl WithPath<Config> {
         let mut r = vec![];
         let mut r = vec![];
         for path in self.get_program_list()? {
         for path in self.get_program_list()? {
             let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"))?;
             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 {
             r.push(Program {
                 lib_name,
                 lib_name,
                 path,
                 path,
@@ -97,16 +160,53 @@ impl WithPath<Config> {
             .workspace
             .workspace
             .members
             .members
             .iter()
             .iter()
-            .map(|m| PathBuf::from(m).canonicalize().unwrap())
+            .map(|m| {
+                self.path()
+                    .parent()
+                    .unwrap()
+                    .join(m)
+                    .canonicalize()
+                    .unwrap()
+            })
             .collect();
             .collect();
         let exclude = self
         let exclude = self
             .workspace
             .workspace
             .exclude
             .exclude
             .iter()
             .iter()
-            .map(|m| PathBuf::from(m).canonicalize().unwrap())
+            .map(|m| {
+                self.path()
+                    .parent()
+                    .unwrap()
+                    .join(m)
+                    .canonicalize()
+                    .unwrap()
+            })
             .collect();
             .collect();
         Ok((members, exclude))
         Ok((members, exclude))
     }
     }
+
+    pub fn get_program(&self, name: &str) -> Result<Option<WithPath<Program>>> {
+        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<T> std::ops::Deref for WithPath<T> {
 impl<T> std::ops::Deref for WithPath<T> {
@@ -174,71 +274,51 @@ impl Config {
         format!("projectserum/build:v{}", ver)
         format!("projectserum/build:v{}", ver)
     }
     }
 
 
-    pub fn discover(
-        cfg_override: &ConfigOverride,
-    ) -> Result<Option<(WithPath<Config>, Option<PathBuf>)>> {
+    pub fn discover(cfg_override: &ConfigOverride) -> Result<Option<WithPath<Config>>> {
         Config::_discover().map(|opt| {
         Config::_discover().map(|opt| {
-            opt.map(|(mut cfg, cargo_toml)| {
+            opt.map(|mut cfg| {
                 if let Some(cluster) = cfg_override.cluster.clone() {
                 if let Some(cluster) = cfg_override.cluster.clone() {
                     cfg.provider.cluster = cluster;
                     cfg.provider.cluster = cluster;
                 }
                 }
-
                 if let Some(wallet) = cfg_override.wallet.clone() {
                 if let Some(wallet) = cfg_override.wallet.clone() {
                     cfg.provider.wallet = wallet;
                     cfg.provider.wallet = wallet;
                 }
                 }
-                (cfg, cargo_toml)
+                cfg
             })
             })
         })
         })
     }
     }
 
 
-    // Searches all parent directories for an Anchor.toml file.
-    fn _discover() -> Result<Option<(WithPath<Config>, Option<PathBuf>)>> {
-        // 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<Option<WithPath<Config>>> {
         let _cwd = std::env::current_dir()?;
         let _cwd = std::env::current_dir()?;
         let mut cwd_opt = Some(_cwd.as_path());
         let mut cwd_opt = Some(_cwd.as_path());
 
 
         while let Some(cwd) = cwd_opt {
         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();
                 let p = f?.path();
                 if let Some(filename) = p.file_name() {
                 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();
             cwd_opt = cwd.parent();
         }
         }
 
 
         Ok(None)
         Ok(None)
     }
     }
 
 
+    fn from_path(p: impl AsRef<Path>) -> Result<Self> {
+        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<Keypair> {
     pub fn wallet_kp(&self) -> Result<Keypair> {
         solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
         solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
             .map_err(|_| anyhow!("Unable to read keypair file"))
             .map_err(|_| anyhow!("Unable to read keypair file"))
@@ -382,21 +462,10 @@ pub struct GenesisEntry {
     pub program: String,
     pub program: String,
 }
 }
 
 
-pub fn extract_lib_name(cargo_toml: impl AsRef<Path>) -> Result<String> {
-    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)]
 #[derive(Debug, Clone)]
 pub struct Program {
 pub struct Program {
     pub lib_name: String,
     pub lib_name: String,
+    // Canonicalized path to the program directory.
     pub path: PathBuf,
     pub path: PathBuf,
     pub idl: Option<Idl>,
     pub idl: Option<Idl>,
 }
 }
@@ -463,7 +532,6 @@ pub struct ProgramWorkspace {
 pub struct AnchorPackage {
 pub struct AnchorPackage {
     pub name: String,
     pub name: String,
     pub address: String,
     pub address: String,
-    pub path: String,
     pub idl: Option<String>,
     pub idl: Option<String>,
 }
 }
 
 
@@ -479,19 +547,9 @@ impl AnchorPackage {
             .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?
             .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?
             .get(&name)
             .get(&name)
             .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?;
             .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 idl = program_details.idl.clone();
         let address = program_details.address.to_string();
         let address = program_details.address.to_string();
-        Ok(Self {
-            name,
-            path,
-            address,
-            idl,
-        })
+        Ok(Self { name, address, idl })
     }
     }
 }
 }
 
 

+ 148 - 137
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_client::Cluster;
 use anchor_lang::idl::{IdlAccount, IdlInstruction};
 use anchor_lang::idl::{IdlAccount, IdlInstruction};
 use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
 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<()> {
 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())?;
     fs::create_dir(name.clone())?;
     std::env::set_current_dir(&name)?;
     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/<name>` directory.
 // Creates a new program crate in the `programs/<name>` directory.
 fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
 fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         match cfg.path().parent() {
         match cfg.path().parent() {
             None => {
             None => {
                 println!("Unable to make new program");
                 println!("Unable to make new program");
@@ -401,41 +400,14 @@ pub fn build(
     stdout: Option<File>, // Used for the package registry server.
     stdout: Option<File>, // Used for the package registry server.
     stderr: Option<File>, // Used for the package registry server.
     stderr: Option<File>, // Used for the package registry server.
 ) -> Result<()> {
 ) -> 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() {
     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 {
     let idl_out = match idl {
         Some(idl) => Some(PathBuf::from(idl)),
         Some(idl) => Some(PathBuf::from(idl)),
         None => {
         None => {
@@ -454,17 +426,37 @@ pub fn build(
     };
     };
 
 
     match cargo {
     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,
             &cfg,
-            ct,
+            cfg.path(),
             idl_out,
             idl_out,
             verifiable,
             verifiable,
             solana_version,
             solana_version,
             stdout,
             stdout,
             stderr,
             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();
     set_workspace_dir_or_exit();
 
 
@@ -477,6 +469,8 @@ fn build_all(
     idl_out: Option<PathBuf>,
     idl_out: Option<PathBuf>,
     verifiable: bool,
     verifiable: bool,
     solana_version: Option<String>,
     solana_version: Option<String>,
+    stdout: Option<File>, // Used for the package registry server.
+    stderr: Option<File>, // Used for the package registry server.
 ) -> Result<()> {
 ) -> Result<()> {
     let cur_dir = std::env::current_dir()?;
     let cur_dir = std::env::current_dir()?;
     let r = match cfg_path.parent() {
     let r = match cfg_path.parent() {
@@ -489,8 +483,8 @@ fn build_all(
                     idl_out.clone(),
                     idl_out.clone(),
                     verifiable,
                     verifiable,
                     solana_version.clone(),
                     solana_version.clone(),
-                    None,
-                    None,
+                    stdout.as_ref().map(|f| f.try_clone()).transpose()?,
+                    stderr.as_ref().map(|f| f.try_clone()).transpose()?,
                 )?;
                 )?;
             }
             }
             Ok(())
             Ok(())
@@ -531,7 +525,7 @@ fn build_cwd_verifiable(
 ) -> Result<()> {
 ) -> Result<()> {
     // Create output dirs.
     // Create output dirs.
     let workspace_dir = cfg.path().parent().unwrap().canonicalize()?;
     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"))?;
     fs::create_dir_all(workspace_dir.join("target/idl"))?;
 
 
     let container_name = "anchor-program";
     let container_name = "anchor-program";
@@ -595,7 +589,7 @@ fn docker_build(
     stdout: Option<File>,
     stdout: Option<File>,
     stderr: Option<File>,
     stderr: Option<File>,
 ) -> Result<()> {
 ) -> Result<()> {
-    let binary_name = config::extract_lib_name(&cargo_toml)?;
+    let binary_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
 
 
     // Docker vars.
     // Docker vars.
     let image_name = cfg.docker();
     let image_name = cfg.docker();
@@ -718,7 +712,7 @@ fn docker_build(
         .parent()
         .parent()
         .unwrap()
         .unwrap()
         .canonicalize()?
         .canonicalize()?
-        .join(format!("target/deploy/{}.so", binary_name))
+        .join(format!("target/verifiable/{}.so", binary_name))
         .display()
         .display()
         .to_string();
         .to_string();
 
 
@@ -774,45 +768,14 @@ fn verify(
     program_name: Option<String>,
     program_name: Option<String>,
     solana_version: Option<String>,
     solana_version: Option<String>,
 ) -> Result<()> {
 ) -> 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() {
     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.
     // 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.
     // Build the program we want to verify.
     let cur_dir = std::env::current_dir()?;
     let cur_dir = std::env::current_dir()?;
@@ -831,23 +794,12 @@ fn verify(
     std::env::set_current_dir(&cur_dir)?;
     std::env::set_current_dir(&cur_dir)?;
 
 
     // Verify binary.
     // 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
     let bin_path = cfg
         .path()
         .path()
         .parent()
         .parent()
         .ok_or_else(|| anyhow!("Unable to find workspace root"))?
         .ok_or_else(|| anyhow!("Unable to find workspace root"))?
-        .join("target/deploy/")
+        .join("target/verifiable/")
         .join(format!("{}.so", binary_name));
         .join(format!("{}.so", binary_name));
 
 
     let bin_ver = verify_bin(program_id, &bin_path, cfg.provider.cluster.url())?;
     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).
     // Verify IDL (only if it's not a buffer account).
     if let Some(local_idl) = extract_idl("src/lib.rs")? {
     if let Some(local_idl) = extract_idl("src/lib.rs")? {
         if bin_ver.state != BinVerificationState::Buffer {
         if bin_ver.state != BinVerificationState::Buffer {
-            std::env::set_current_dir(program_dir)?;
             let deployed_idl = fetch_idl(cfg_override, program_id)?;
             let deployed_idl = fetch_idl(cfg_override, program_id)?;
             if local_idl != deployed_idl {
             if local_idl != deployed_idl {
                 println!("Error: IDLs don't match");
                 println!("Error: IDLs don't match");
@@ -873,6 +824,27 @@ fn verify(
     Ok(())
     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<BinVerification> {
 pub fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result<BinVerification> {
     let client = RpcClient::new(cluster.to_string());
     let client = RpcClient::new(cluster.to_string());
 
 
@@ -954,9 +926,7 @@ pub enum BinVerificationState {
 
 
 // Fetches an IDL for the given program_id.
 // Fetches an IDL for the given program_id.
 fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
 fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
-    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 client = RpcClient::new(cfg.provider.cluster.url().to_string());
 
 
     let mut account = client
     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<()> {
 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 keypair = cfg.provider.wallet.to_string();
 
 
         let bytes = std::fs::read(idl_filepath)?;
         let bytes = std::fs::read(idl_filepath)?;
@@ -1035,7 +1005,7 @@ fn idl_write_buffer(
     program_id: Pubkey,
     program_id: Pubkey,
     idl_filepath: String,
     idl_filepath: String,
 ) -> Result<Pubkey> {
 ) -> Result<Pubkey> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         let keypair = cfg.provider.wallet.to_string();
         let keypair = cfg.provider.wallet.to_string();
 
 
         let bytes = std::fs::read(idl_filepath)?;
         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<()> {
 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())
         let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
             .map_err(|_| anyhow!("Unable to read keypair file"))?;
             .map_err(|_| anyhow!("Unable to read keypair file"))?;
         let client = RpcClient::new(cfg.provider.cluster.url().to_string());
         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<()> {
 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 client = RpcClient::new(cfg.provider.cluster.url().to_string());
         let idl_address = {
         let idl_address = {
             let account = client
             let account = client
@@ -1135,7 +1105,7 @@ fn idl_set_authority(
     address: Option<Pubkey>,
     address: Option<Pubkey>,
     new_authority: Pubkey,
     new_authority: Pubkey,
 ) -> Result<()> {
 ) -> Result<()> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         // Misc.
         // Misc.
         let idl_address = match address {
         let idl_address = match address {
             None => IdlAccount::address(&program_id),
             None => IdlAccount::address(&program_id),
@@ -1306,7 +1276,7 @@ fn test(
     skip_build: bool,
     skip_build: bool,
     extra_args: Vec<String>,
     extra_args: Vec<String>,
 ) -> Result<()> {
 ) -> Result<()> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         // Build if needed.
         // Build if needed.
         if !skip_build {
         if !skip_build {
             build(cfg_override, None, false, None, None, None, None)?;
             build(cfg_override, None, false, None, None, None, None)?;
@@ -1534,7 +1504,7 @@ fn _deploy(
     cfg_override: &ConfigOverride,
     cfg_override: &ConfigOverride,
     program_str: Option<String>,
     program_str: Option<String>,
 ) -> Result<Vec<(Pubkey, Program)>> {
 ) -> Result<Vec<(Pubkey, Program)>> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         let url = cfg.provider.cluster.url().to_string();
         let url = cfg.provider.cluster.url().to_string();
         let keypair = cfg.provider.wallet.to_string();
         let keypair = cfg.provider.wallet.to_string();
 
 
@@ -1615,7 +1585,7 @@ fn upgrade(
     let path: PathBuf = program_filepath.parse().unwrap();
     let path: PathBuf = program_filepath.parse().unwrap();
     let program_filepath = path.canonicalize()?.display().to_string();
     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")
         let exit = std::process::Command::new("solana")
             .arg("program")
             .arg("program")
             .arg("deploy")
             .arg("deploy")
@@ -1655,7 +1625,7 @@ fn launch(
     )?;
     )?;
     let programs = _deploy(cfg_override, program_name)?;
     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();
         let keypair = cfg.provider.wallet.to_string();
 
 
         // Add metadata to all IDLs.
         // Add metadata to all IDLs.
@@ -1680,10 +1650,7 @@ fn launch(
 // The Solana CLI doesn't redeploy a program if this file exists.
 // The Solana CLI doesn't redeploy a program if this file exists.
 // So remove it to make all commands explicit.
 // So remove it to make all commands explicit.
 fn clear_program_keys(cfg_override: &ConfigOverride) -> Result<()> {
 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()? {
     for program in config.read_all_programs()? {
         let anchor_keypair_path = program.anchor_keypair_path();
         let anchor_keypair_path = program.anchor_keypair_path();
@@ -1827,7 +1794,7 @@ fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8
 }
 }
 
 
 fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
 fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         println!("Running migration deploy script");
         println!("Running migration deploy script");
 
 
         let url = cfg.provider.cluster.url().to_string();
         let url = cfg.provider.cluster.url().to_string();
@@ -1874,10 +1841,7 @@ fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
 }
 }
 
 
 fn set_workspace_dir_or_exit() {
 fn set_workspace_dir_or_exit() {
-    let d = match Config::discover(&ConfigOverride {
-        cluster: None,
-        wallet: None,
-    }) {
+    let d = match Config::discover(&ConfigOverride::default()) {
         Err(_) => {
         Err(_) => {
             println!("Not in anchor workspace.");
             println!("Not in anchor workspace.");
             std::process::exit(1);
             std::process::exit(1);
@@ -1889,7 +1853,7 @@ fn set_workspace_dir_or_exit() {
             println!("Not in anchor workspace.");
             println!("Not in anchor workspace.");
             std::process::exit(1);
             std::process::exit(1);
         }
         }
-        Some((cfg, _inside_cargo)) => {
+        Some(cfg) => {
             match cfg.path().parent() {
             match cfg.path().parent() {
                 None => {
                 None => {
                     println!("Unable to make new program");
                     println!("Unable to make new program");
@@ -1938,7 +1902,7 @@ fn cluster(_cmd: ClusterCommand) -> Result<()> {
 }
 }
 
 
 fn shell(cfg_override: &ConfigOverride) -> Result<()> {
 fn shell(cfg_override: &ConfigOverride) -> Result<()> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         let programs = {
         let programs = {
             // Create idl map from all workspace programs.
             // Create idl map from all workspace programs.
             let mut idls: HashMap<String, Idl> = cfg
             let mut idls: HashMap<String, Idl> = cfg
@@ -2005,7 +1969,7 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> {
 }
 }
 
 
 fn run(cfg_override: &ConfigOverride, script: String) -> Result<()> {
 fn run(cfg_override: &ConfigOverride, script: String) -> Result<()> {
-    with_workspace(cfg_override, |cfg, _cargo| {
+    with_workspace(cfg_override, |cfg| {
         let script = cfg
         let script = cfg
             .scripts
             .scripts
             .get(&script)
             .get(&script)
@@ -2040,9 +2004,21 @@ fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> {
 
 
 fn publish(cfg_override: &ConfigOverride, program_name: String) -> Result<()> {
 fn publish(cfg_override: &ConfigOverride, program_name: String) -> Result<()> {
     // Discover the various workspace configs.
     // 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.");
+
+    let program = cfg
+        .get_program(&program_name)?
+        .ok_or_else(|| anyhow!("Workspace member not found"))?;
 
 
-    if !Path::new("Cargo.lock").exists() {
+    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"));
         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);
     let mut tar = tar::Builder::new(enc);
 
 
     // Files that will always be included if they exist.
     // Files that will always be included if they exist.
+    println!("PACKING: Anchor.toml");
     tar.append_path("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() {
     if Path::new("Cargo.toml").exists() {
+        println!("PACKING: Cargo.toml");
         tar.append_path("Cargo.toml")?;
         tar.append_path("Cargo.toml")?;
     }
     }
     if Path::new("LICENSE").exists() {
     if Path::new("LICENSE").exists() {
+        println!("PACKING: LICENSE");
         tar.append_path("LICENSE")?;
         tar.append_path("LICENSE")?;
     }
     }
     if Path::new("README.md").exists() {
     if Path::new("README.md").exists() {
+        println!("PACKING: README.md");
         tar.append_path("README.md")?;
         tar.append_path("README.md")?;
     }
     }
 
 
     // All workspace programs.
     // All workspace programs.
     for path in cfg.get_program_list()? {
     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"))?;
-
-        // HACK for workspaces wtih single programs. Change this.
-        if relative_path.display().to_string() == *"" {
-            relative_path = "src".into();
+        let mut dirs = walkdir::WalkDir::new(&path)
+            .into_iter()
+            .filter_entry(|e| !is_hidden(e));
+
+        // 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()?;
     tar.into_inner()?;
 
 
     // Upload the tarball to the server.
     // Upload the tarball to the server.
@@ -2158,22 +2164,27 @@ fn registry_api_token(_cfg_override: &ConfigOverride) -> Result<String> {
 //
 //
 // The closure passed into this function must never change the working directory
 // The closure passed into this function must never change the working directory
 // to be outside the workspace. Doing so will have undefined behavior.
 // to be outside the workspace. Doing so will have undefined behavior.
-fn with_workspace<R>(
-    cfg_override: &ConfigOverride,
-    f: impl FnOnce(&WithPath<Config>, Option<PathBuf>) -> R,
-) -> R {
+fn with_workspace<R>(cfg_override: &ConfigOverride, f: impl FnOnce(&WithPath<Config>) -> R) -> R {
     set_workspace_dir_or_exit();
     set_workspace_dir_or_exit();
 
 
     clear_program_keys(cfg_override).unwrap();
     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("Previously set the workspace dir")
         .expect("Anchor.toml must always exist");
         .expect("Anchor.toml must always exist");
 
 
-    let r = f(&cfg, cargo_toml);
+    let r = f(&cfg);
 
 
     set_workspace_dir_or_exit();
     set_workspace_dir_or_exit();
     clear_program_keys(cfg_override).unwrap();
     clear_program_keys(cfg_override).unwrap();
 
 
     r
     r
 }
 }
+
+fn is_hidden(entry: &walkdir::DirEntry) -> bool {
+    entry
+        .file_name()
+        .to_str()
+        .map(|s| s == "." || s.starts_with('.') || s == "target")
+        .unwrap_or(false)
+}

+ 3 - 3
docs/src/getting-started/publishing.md

@@ -40,7 +40,7 @@ cluster = "mainnet"
 wallet = "~/.config/solana/id.json"
 wallet = "~/.config/solana/id.json"
 
 
 [programs.mainnet]
 [programs.mainnet]
-multisig = { address = "A9HAbnCwoD6f2NkZobKFf6buJoN9gUVVvX5PoUnDHS6u", path = "./target/deploy/multisig.so", idl = "./target/idl/multisig.json" }
+multisig = "A9HAbnCwoD6f2NkZobKFf6buJoN9gUVVvX5PoUnDHS6u"
 ```
 ```
 
 
 Here there are four sections.
 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
    standard Anchor workflow, this can be ommitted.  For programs not written in Anchor
    but still want to publish, this should be added.
    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. `[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 = "<path>"` field should also be provided.
+3. `[programs.mainnet]` - configures each program in the workpace, providing
+   the `address` of the program to verify.
 
 
 ::: tip
 ::: tip
 When defining program in `[programs.mainnet]`, make sure the name provided
 When defining program in `[programs.mainnet]`, make sure the name provided