Skip to content

Commit

Permalink
cli: Add keys include/exclude in programs section (#546)
Browse files Browse the repository at this point in the history
  • Loading branch information
fanatid authored Jul 26, 2021
1 parent 8dc7bed commit b8dae74
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 35 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ incremented for features.

## [Unreleased]

### Features

* cli: Add keys `members` / `exclude` in config `programs` section ([#546](https://github.com/project-serum/anchor/pull/546)).

### Breaking Changes

* ts: Use `hex` by default for decoding Instruction ([#547](https://github.com/project-serum/anchor/pull/547)).
Expand Down
86 changes: 69 additions & 17 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct Config {
pub clusters: ClustersConfig,
pub scripts: ScriptsConfig,
pub test: Option<Test>,
pub workspace: WorkspaceConfig,
}

#[derive(Debug, Default)]
Expand All @@ -31,6 +32,14 @@ pub type ScriptsConfig = BTreeMap<String, String>;

pub type ClustersConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct WorkspaceConfig {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub members: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exclude: Vec<String>,
}

impl Config {
pub fn discover(
cfg_override: &ConfigOverride,
Expand Down Expand Up @@ -96,6 +105,51 @@ impl Config {
solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
.map_err(|_| anyhow!("Unable to read keypair file"))
}

pub fn get_program_list(&self, path: PathBuf) -> Result<Vec<PathBuf>> {
let mut programs = vec![];
for f in fs::read_dir(path)? {
let path = f?.path();
let program = path
.components()
.last()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.expect("failed to get program from path");

match (
self.workspace.members.is_empty(),
self.workspace.exclude.is_empty(),
) {
(true, true) => programs.push(path),
(true, false) => {
if !self.workspace.exclude.contains(&program) {
programs.push(path);
}
}
(false, _) => {
if self.workspace.members.contains(&program) {
programs.push(path);
}
}
}
}
Ok(programs)
}

// TODO: this should read idl dir instead of parsing source.
pub fn read_all_programs(&self) -> Result<Vec<Program>> {
let mut r = vec![];
for path in self.get_program_list("programs".into())? {
let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"))?;
let lib_name = extract_lib_name(&path.join("Cargo.toml"))?;
r.push(Program {
lib_name,
path,
idl,
});
}
Ok(r)
}
}

// Pubkey serializes as a byte array so use this type a hack to serialize
Expand All @@ -106,6 +160,7 @@ struct _Config {
test: Option<Test>,
scripts: Option<ScriptsConfig>,
clusters: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
workspace: Option<WorkspaceConfig>,
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -135,6 +190,7 @@ impl ToString for Config {
false => Some(self.scripts.clone()),
},
clusters,
workspace: Some(self.workspace.clone()),
};

toml::to_string(&cfg).expect("Must be well formed")
Expand All @@ -155,6 +211,19 @@ impl FromStr for Config {
scripts: cfg.scripts.unwrap_or_else(BTreeMap::new),
test: cfg.test,
clusters: cfg.clusters.map_or(Ok(BTreeMap::new()), deser_clusters)?,
workspace: cfg.workspace.map(|workspace| {
let (members, exclude) = match (workspace.members.is_empty(), workspace.exclude.is_empty()) {
(true, true) => (vec![], vec![]),
(true, false) => (vec![], workspace.exclude),
(false, is_empty) => {
if !is_empty {
println!("Fields `members` and `exclude` in `[workspace]` section are not compatible, only `members` will be used.");
}
(workspace.members, vec![])
}
};
WorkspaceConfig { members, exclude }
}).unwrap_or_default()
})
}
}
Expand Down Expand Up @@ -224,23 +293,6 @@ pub struct GenesisEntry {
pub program: String,
}

// TODO: this should read idl dir instead of parsing source.
pub fn read_all_programs() -> Result<Vec<Program>> {
let files = fs::read_dir("programs")?;
let mut r = vec![];
for f in files {
let path = f?.path();
let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"))?;
let lib_name = extract_lib_name(&path.join("Cargo.toml"))?;
r.push(Program {
lib_name,
path,
idl,
});
}
Ok(r)
}

pub fn extract_lib_name(path: impl AsRef<Path>) -> Result<String> {
let mut toml = File::open(path)?;
let mut contents = String::new();
Expand Down
41 changes: 23 additions & 18 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! CLI for workspace management of anchor programs.

use crate::config::{read_all_programs, Config, Program, ProgramWorkspace, WalletPath};
use crate::config::{Config, Program, ProgramWorkspace, WalletPath};
use anchor_client::Cluster;
use anchor_lang::idl::{IdlAccount, IdlInstruction};
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
Expand Down Expand Up @@ -364,15 +364,16 @@ fn build(
verifiable: bool,
program_name: Option<String>,
) -> Result<()> {
let (cfg, path, cargo) = Config::discover(cfg_override)?.expect("Not in workspace.");

if let Some(program_name) = program_name {
for program in read_all_programs()? {
for program in cfg.read_all_programs()? {
let p = program.path.file_name().unwrap().to_str().unwrap();
if program_name.as_str() == p {
std::env::set_current_dir(&program.path)?;
}
}
}
let (cfg, path, cargo) = Config::discover(cfg_override)?.expect("Not in workspace.");
let idl_out = match idl {
Some(idl) => Some(PathBuf::from(idl)),
None => {
Expand All @@ -395,7 +396,7 @@ fn build(
}

fn build_all(
_cfg: &Config,
cfg: &Config,
cfg_path: PathBuf,
idl_out: Option<PathBuf>,
verifiable: bool,
Expand All @@ -404,9 +405,7 @@ fn build_all(
let r = match cfg_path.parent() {
None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
Some(parent) => {
let files = fs::read_dir(parent.join("programs"))?;
for f in files {
let p = f?.path();
for p in cfg.get_program_list(parent.join("programs"))? {
build_cwd(
cfg_path.as_path(),
p.join("Cargo.toml"),
Expand Down Expand Up @@ -1002,7 +1001,7 @@ fn test(
}

// Setup log reader.
let log_streams = stream_logs(cfg.provider.cluster.url());
let log_streams = stream_logs(cfg);

// Run the tests.
let test_result: Result<_> = {
Expand Down Expand Up @@ -1065,7 +1064,7 @@ fn test(
// in the genesis block. This allows us to run tests without every deploying.
fn genesis_flags(cfg: &Config) -> Result<Vec<String>> {
let mut flags = Vec::new();
for mut program in read_all_programs()? {
for mut program in cfg.read_all_programs()? {
let binary_path = program.binary_path().display().to_string();

let kp = Keypair::generate(&mut OsRng);
Expand Down Expand Up @@ -1094,14 +1093,14 @@ fn genesis_flags(cfg: &Config) -> Result<Vec<String>> {
Ok(flags)
}

fn stream_logs(url: &str) -> Result<Vec<std::process::Child>> {
fn stream_logs(config: &Config) -> Result<Vec<std::process::Child>> {
let program_logs_dir = ".anchor/program-logs";
if Path::new(program_logs_dir).exists() {
std::fs::remove_dir_all(program_logs_dir)?;
}
fs::create_dir_all(program_logs_dir)?;
let mut handles = vec![];
for program in read_all_programs()? {
for program in config.read_all_programs()? {
let mut file = File::open(&format!("target/idl/{}.json", program.lib_name))?;
let mut contents = vec![];
file.read_to_end(&mut contents)?;
Expand All @@ -1120,7 +1119,7 @@ fn stream_logs(url: &str) -> Result<Vec<std::process::Child>> {
.arg("logs")
.arg(metadata.address)
.arg("--url")
.arg(url)
.arg(config.provider.cluster.url())
.stdout(stdio)
.spawn()?;
handles.push(child);
Expand Down Expand Up @@ -1197,7 +1196,7 @@ fn _deploy(

let mut programs = Vec::new();

for mut program in read_all_programs()? {
for mut program in cfg.read_all_programs()? {
if let Some(single_prog_str) = &program_str {
let program_name = program.path.file_name().unwrap().to_str().unwrap();
if single_prog_str.as_str() != program_name {
Expand Down Expand Up @@ -1320,8 +1319,13 @@ 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() -> Result<()> {
for program in read_all_programs()? {
fn clear_program_keys(cfg_override: &ConfigOverride) -> Result<()> {
let config = Config::discover(cfg_override)
.unwrap_or_default()
.unwrap_or_default()
.0;

for program in config.read_all_programs()? {
let anchor_keypair_path = program.anchor_keypair_path();
if Path::exists(&anchor_keypair_path) {
std::fs::remove_file(anchor_keypair_path).expect("Always remove");
Expand Down Expand Up @@ -1576,7 +1580,8 @@ fn cluster(_cmd: ClusterCommand) -> Result<()> {
fn shell(cfg_override: &ConfigOverride) -> Result<()> {
with_workspace(cfg_override, |cfg, _path, _cargo| {
let programs = {
let mut idls: HashMap<String, Idl> = read_all_programs()?
let mut idls: HashMap<String, Idl> = cfg
.read_all_programs()?
.iter()
.map(|program| (program.idl.name.clone(), program.idl.clone()))
.collect();
Expand Down Expand Up @@ -1664,7 +1669,7 @@ fn with_workspace<R>(
) -> R {
set_workspace_dir_or_exit();

clear_program_keys().unwrap();
clear_program_keys(cfg_override).unwrap();

let (cfg, cfg_path, cargo_toml) = Config::discover(cfg_override)
.expect("Previously set the workspace dir")
Expand All @@ -1673,7 +1678,7 @@ fn with_workspace<R>(
let r = f(&cfg, cfg_path, cargo_toml);

set_workspace_dir_or_exit();
clear_program_keys().unwrap();
clear_program_keys(cfg_override).unwrap();

r
}
3 changes: 3 additions & 0 deletions examples/misc/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ wallet = "~/.config/solana/id.json"
[[test.genesis]]
address = "FtMNMKp9DZHKWUyVAsj3Q5QV8ow4P3fUPP7ZrWEQJzKr"
program = "./target/deploy/misc.so"

[workspace]
exclude = ["shared"]
8 changes: 8 additions & 0 deletions examples/misc/programs/shared/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
7 changes: 7 additions & 0 deletions examples/misc/programs/shared/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
4 changes: 4 additions & 0 deletions examples/typescript/Anchor.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

[workspace]
members = ["typescript"]
exclude = ["typescript"]
8 changes: 8 additions & 0 deletions examples/typescript/programs/shared/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
7 changes: 7 additions & 0 deletions examples/typescript/programs/shared/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
3 changes: 3 additions & 0 deletions examples/zero-copy/Anchor.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

[workspace]
members = ["zero-copy"]
8 changes: 8 additions & 0 deletions examples/zero-copy/programs/shared/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
7 changes: 7 additions & 0 deletions examples/zero-copy/programs/shared/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

0 comments on commit b8dae74

Please sign in to comment.