Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: Add keys include/exclude in programs section #546

Merged
merged 6 commits into from
Jul 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

* lang: `CpiAccount::reload` mutates the existing struct instead of returning a new one ([#526](https://github.com/project-serum/anchor/pull/526)).
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);
}
}