cli: Add `anchor expand` command (#1160)

This commit is contained in:
Paul 2021-12-27 19:34:45 +01:00 committed by GitHub
parent 22c2c30ef4
commit 1749a7bd53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 215 additions and 79 deletions

View File

@ -11,7 +11,6 @@ incremented for features.
## [Unreleased]
### Fixes
* ts: Change commitment message `recent` to `processed` and `max` to `finalized` ([#1128](https://github.com/project-serum/anchor/pull/1128))
@ -26,6 +25,7 @@ incremented for features.
* lang: Handle arrays with const as length ([#968](https://github.com/project-serum/anchor/pull/968)).
* ts: Add optional commitment argument to `fetch` and `fetchMultiple` ([#1171](https://github.com/project-serum/anchor/pull/1171)).
* lang: Implement `AsRef<T>` for `Account<'a, T>`([#1173](https://github.com/project-serum/anchor/pull/1173))
* cli: Add `anchor expand` command which wraps around `cargo expand` ([#1160](https://github.com/project-serum/anchor/pull/1160))
### Breaking

1
Cargo.lock generated
View File

@ -152,6 +152,7 @@ dependencies = [
"anchor-syn",
"anyhow",
"cargo_toml",
"chrono",
"clap 3.0.0-beta.4",
"dirs",
"flate2",

View File

@ -38,3 +38,4 @@ tokio = "1.0"
pathdiff = "0.2.0"
cargo_toml = "0.9.2"
walkdir = "2"
chrono = "0.4.19"

View File

@ -33,6 +33,7 @@ use solana_sdk::sysvar;
use solana_sdk::transaction::Transaction;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
@ -97,6 +98,25 @@ pub enum Command {
)]
cargo_args: Vec<String>,
},
/// Expands macros (wrapper around cargo expand)
///
/// Use it in a program folder to expand program
///
/// Use it in a workspace but outside a program
/// folder to expand the entire workspace
Expand {
/// Expand only this program
#[clap(short, long)]
program_name: Option<String>,
/// Arguments to pass to the underlying `cargo expand` command
#[clap(
required = false,
takes_value = true,
multiple_values = true,
last = true
)]
cargo_args: Vec<String>,
},
/// Verifies the on-chain bytecode matches the locally compiled artifact.
/// Run this command inside a program subdirectory, i.e., in the dir
/// containing the program's Cargo.toml.
@ -370,6 +390,10 @@ pub fn entry(opts: Opts) -> Result<()> {
cargo_args,
),
Command::Deploy { program_name } => deploy(&opts.cfg_override, program_name),
Command::Expand {
program_name,
cargo_args,
} => expand(&opts.cfg_override, program_name, &cargo_args),
Command::Upgrade {
program_id,
program_filepath,
@ -559,6 +583,102 @@ fn new_program(name: &str) -> Result<()> {
Ok(())
}
pub fn expand(
cfg_override: &ConfigOverride,
program_name: Option<String>,
cargo_args: &[String],
) -> Result<()> {
// Change to the workspace member directory, if needed.
if let Some(program_name) = program_name.as_ref() {
cd_member(cfg_override, program_name)?;
}
let workspace_cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
let cfg_parent = workspace_cfg.path().parent().expect("Invalid Anchor.toml");
let cargo = Manifest::discover()?;
let expansions_path = cfg_parent.join(".anchor/expanded-macros");
fs::create_dir_all(&expansions_path)?;
match cargo {
// No Cargo.toml found, expand entire workspace
None => expand_all(&workspace_cfg, expansions_path, cargo_args),
// Cargo.toml is at root of workspace, expand entire workspace
Some(cargo) if cargo.path().parent() == workspace_cfg.path().parent() => {
expand_all(&workspace_cfg, expansions_path, cargo_args)
}
// Reaching this arm means Cargo.toml belongs to a single package. Expand it.
Some(cargo) => expand_program(
// If we found Cargo.toml, it must be in a directory so unwrap is safe
cargo.path().parent().unwrap().to_path_buf(),
expansions_path,
cargo_args,
),
}
}
fn expand_all(
workspace_cfg: &WithPath<Config>,
expansions_path: PathBuf,
cargo_args: &[String],
) -> Result<()> {
let cur_dir = std::env::current_dir()?;
for p in workspace_cfg.get_program_list()? {
expand_program(p, expansions_path.clone(), cargo_args)?;
}
std::env::set_current_dir(cur_dir)?;
Ok(())
}
fn expand_program(
program_path: PathBuf,
expansions_path: PathBuf,
cargo_args: &[String],
) -> Result<()> {
let cargo = Manifest::from_path(program_path.join("Cargo.toml"))
.map_err(|_| anyhow!("Could not find Cargo.toml for program"))?;
let target_dir_arg = {
let mut target_dir_arg = OsString::from("--target-dir=");
target_dir_arg.push(expansions_path.join("expand-target"));
target_dir_arg
};
let package_name = &cargo
.package
.as_ref()
.ok_or_else(|| anyhow!("Cargo config is missing a package"))?
.name;
let program_expansions_path = expansions_path.join(package_name);
fs::create_dir_all(&program_expansions_path)?;
let exit = std::process::Command::new("cargo")
.arg("expand")
.arg(target_dir_arg)
.arg(&format!("--package={}", package_name))
.args(cargo_args)
.stderr(Stdio::inherit())
.output()
.map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
if !exit.status.success() {
eprintln!("'anchor expand' failed. Perhaps you have not installed 'cargo-expand'? https://github.com/dtolnay/cargo-expand#installation");
std::process::exit(exit.status.code().unwrap_or(1));
}
let version = cargo.version();
let time = chrono::Utc::now().to_string().replace(' ', "_");
let file_path =
program_expansions_path.join(format!("{}-{}-{}.rs", package_name, version, time));
fs::write(&file_path, &exit.stdout).map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
println!(
"Expanded {} into file {}\n",
package_name,
file_path.to_string_lossy()
);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn build(
cfg_override: &ConfigOverride,

View File

@ -18,6 +18,7 @@ SUBCOMMANDS:
build Builds the workspace
cluster Cluster commands
deploy Deploys each program in the workspace
expand Expands the macros of a program or the workspace
help Prints this message or the help of the given subcommand(s)
idl Commands for interacting with interface definitions
init Initializes a workspace
@ -30,21 +31,6 @@ SUBCOMMANDS:
Cargo.toml
```
## Init
```
anchor init
```
Initializes a project workspace with the following structure.
* `Anchor.toml`: Anchor configuration file.
* `Cargo.toml`: Rust workspace configuration file.
* `package.json`: JavaScript dependencies file.
* `programs/`: Directory for Solana program crates.
* `app/`: Directory for your application frontend.
* `tests/`: Directory for JavaScript integration tests.
* `migrations/deploy.js`: Deploy script.
## Build
@ -60,6 +46,25 @@ anchor build --verifiable
Runs the build inside a docker image so that the output binary is deterministic (assuming a Cargo.lock file is used). This command must be run from within a single crate subdirectory within the workspace. For example, `programs/<my-program>/`.
## Cluster
### Cluster list
```
anchor cluster list
```
This lists cluster endpoints:
```
Cluster Endpoints:
* Mainnet - https://solana-api.projectserum.com
* Mainnet - https://api.mainnet-beta.solana.com
* Devnet - https://api.devnet.solana.com
* Testnet - https://api.testnet.solana.com
```
## Deploy
```
@ -73,64 +78,17 @@ This is different from the `solana program deploy` command, because everytime it
it will generate a *new* program address.
:::
## Upgrade
## Expand
```
anchor upgrade <target/deploy/program.so> --program-id <program-id>
anchor expand
```
Uses Solana's upgradeable BPF loader to upgrade the on chain program code.
If run inside a program folder, expands the macros of the program.
## Test
If run in the workspace but outside a program folder, expands the macros of the workspace.
```
anchor test
```
Run an integration test suite against the configured cluster, deploying new versions
of all workspace programs before running them.
If the configured network is a localnet, then automatically starts the localnetwork and runs
the test.
::: tip Note
Be sure to shutdown any other local validators, otherwise `anchor test` will fail to run.
If you'd prefer to run the program against your local validator use `anchor test --skip-local-validator`.
:::
When running tests we stream program logs to `.anchor/program-logs/<address>.<program-name>.log`
::: tip Note
The Anchor workflow [recommends](https://www.parity.io/paritys-checklist-for-secure-smart-contract-development/)
to test your program using integration tests in a language other
than Rust to make sure that bugs related to syntax misunderstandings
are coverable with tests and not just replicated in tests.
:::
## Migrate
```
anchor migrate
```
Runs the deploy script located at `migrations/deploy.js`, injecting a provider configured
from the workspace's `Anchor.toml`. For example,
```javascript
// File: migrations/deploys.js
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
anchor.setProvider(provider);
// Add your deploy script here.
}
```
Migrations are a new feature
and only support this simple deploy script at the moment.
If run with the `--program-name` option, expand only the given program.
## Idl
@ -195,6 +153,46 @@ anchor idl set-authority -n <new-authority> -p <program-id>
Sets a new authority on the IDL account. Both the `new-authority` and `program-id`
must be encoded in base 58.
## Init
```
anchor init
```
Initializes a project workspace with the following structure.
* `Anchor.toml`: Anchor configuration file.
* `Cargo.toml`: Rust workspace configuration file.
* `package.json`: JavaScript dependencies file.
* `programs/`: Directory for Solana program crates.
* `app/`: Directory for your application frontend.
* `tests/`: Directory for JavaScript integration tests.
* `migrations/deploy.js`: Deploy script.
## Migrate
```
anchor migrate
```
Runs the deploy script located at `migrations/deploy.js`, injecting a provider configured
from the workspace's `Anchor.toml`. For example,
```javascript
// File: migrations/deploys.js
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
anchor.setProvider(provider);
// Add your deploy script here.
}
```
Migrations are a new feature
and only support this simple deploy script at the moment.
## New
```
@ -203,25 +201,41 @@ anchor new <program-name>
Creates a new program in the workspace's `programs/` directory initialized with boilerplate.
## Cluster
### Cluster list
## Test
```
anchor cluster list
anchor test
```
This lists cluster endpoints:
Run an integration test suit against the configured cluster, deploying new versions
of all workspace programs before running them.
If the configured network is a localnet, then automatically starts the localnetwork and runs
the test.
::: tip Note
Be sure to shutdown any other local validators, otherwise `anchor test` will fail to run.
If you'd prefer to run the program against your local validator use `anchor test --skip-local-validator`.
:::
When running tests we stream program logs to `.anchor/program-logs/<address>.<program-name>.log`
::: tip Note
The Anchor workflow [recommends](https://www.parity.io/paritys-checklist-for-secure-smart-contract-development/)
to test your program using integration tests in a language other
than Rust to make sure that bugs related to syntax misunderstandings
are coverable with tests and not just replicated in tests.
:::
## Upgrade
```
Cluster Endpoints:
* Mainnet - https://solana-api.projectserum.com
* Mainnet - https://api.mainnet-beta.solana.com
* Devnet - https://api.devnet.solana.com
* Testnet - https://api.testnet.solana.com
anchor upgrade <target/deploy/program.so> --program-id <program-id>
```
Uses Solana's upgradeable BPF loader to upgrade the on chain program code.
## Verify
```