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

This commit is contained in:
Armani Ferrante 2021-08-10 21:36:56 -07:00 committed by GitHub
parent 80e4030e45
commit bd68e28f15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 279 additions and 204 deletions

View File

@ -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

1
Cargo.lock generated
View File

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

View File

@ -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"

View File

@ -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<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> {
pub fn get_program_list(&self) -> Result<Vec<PathBuf>> {
// Canonicalize the workspace filepaths to compare with relative paths.
@ -82,7 +145,7 @@ impl WithPath<Config> {
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<Config> {
.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<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> {
@ -174,71 +274,51 @@ impl Config {
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| {
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<(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 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<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> {
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<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)]
pub struct Program {
pub lib_name: String,
// Canonicalized path to the program directory.
pub path: PathBuf,
pub idl: Option<Idl>,
}
@ -463,7 +532,6 @@ pub struct ProgramWorkspace {
pub struct AnchorPackage {
pub name: String,
pub address: String,
pub path: String,
pub idl: Option<String>,
}
@ -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 })
}
}

View File

@ -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/<name>` 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<File>, // Used for the package registry server.
stderr: Option<File>, // 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<PathBuf>,
verifiable: bool,
solana_version: Option<String>,
stdout: Option<File>, // Used for the package registry server.
stderr: Option<File>, // 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<File>,
stderr: Option<File>,
) -> 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<String>,
solana_version: Option<String>,
) -> 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<BinVerification> {
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<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 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<Pubkey> {
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<Pubkey>,
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<String>,
) -> 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<String>,
) -> Result<Vec<(Pubkey, Program)>> {
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<Vec<u8
}
fn migrate(cfg_override: &ConfigOverride) -> 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<String, Idl> = 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<String> {
//
// 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<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();
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)
}

View File

@ -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 = "<path>"` 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