cli: Add `anchor expand` command (#1160)
This commit is contained in:
parent
22c2c30ef4
commit
1749a7bd53
|
@ -11,7 +11,6 @@ incremented for features.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
* ts: Change commitment message `recent` to `processed` and `max` to `finalized` ([#1128](https://github.com/project-serum/anchor/pull/1128))
|
* 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)).
|
* 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)).
|
* 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))
|
* 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
|
### Breaking
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ dependencies = [
|
||||||
"anchor-syn",
|
"anchor-syn",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
|
"chrono",
|
||||||
"clap 3.0.0-beta.4",
|
"clap 3.0.0-beta.4",
|
||||||
"dirs",
|
"dirs",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
|
|
@ -38,3 +38,4 @@ tokio = "1.0"
|
||||||
pathdiff = "0.2.0"
|
pathdiff = "0.2.0"
|
||||||
cargo_toml = "0.9.2"
|
cargo_toml = "0.9.2"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
chrono = "0.4.19"
|
||||||
|
|
120
cli/src/lib.rs
120
cli/src/lib.rs
|
@ -33,6 +33,7 @@ use solana_sdk::sysvar;
|
||||||
use solana_sdk::transaction::Transaction;
|
use solana_sdk::transaction::Transaction;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -97,6 +98,25 @@ pub enum Command {
|
||||||
)]
|
)]
|
||||||
cargo_args: Vec<String>,
|
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.
|
/// Verifies the on-chain bytecode matches the locally compiled artifact.
|
||||||
/// Run this command inside a program subdirectory, i.e., in the dir
|
/// Run this command inside a program subdirectory, i.e., in the dir
|
||||||
/// containing the program's Cargo.toml.
|
/// containing the program's Cargo.toml.
|
||||||
|
@ -370,6 +390,10 @@ pub fn entry(opts: Opts) -> Result<()> {
|
||||||
cargo_args,
|
cargo_args,
|
||||||
),
|
),
|
||||||
Command::Deploy { program_name } => deploy(&opts.cfg_override, program_name),
|
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 {
|
Command::Upgrade {
|
||||||
program_id,
|
program_id,
|
||||||
program_filepath,
|
program_filepath,
|
||||||
|
@ -559,6 +583,102 @@ fn new_program(name: &str) -> Result<()> {
|
||||||
Ok(())
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn build(
|
pub fn build(
|
||||||
cfg_override: &ConfigOverride,
|
cfg_override: &ConfigOverride,
|
||||||
|
|
|
@ -18,6 +18,7 @@ SUBCOMMANDS:
|
||||||
build Builds the workspace
|
build Builds the workspace
|
||||||
cluster Cluster commands
|
cluster Cluster commands
|
||||||
deploy Deploys each program in the workspace
|
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)
|
help Prints this message or the help of the given subcommand(s)
|
||||||
idl Commands for interacting with interface definitions
|
idl Commands for interacting with interface definitions
|
||||||
init Initializes a workspace
|
init Initializes a workspace
|
||||||
|
@ -30,21 +31,6 @@ SUBCOMMANDS:
|
||||||
Cargo.toml
|
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
|
## 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>/`.
|
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
|
## Deploy
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -73,64 +78,17 @@ This is different from the `solana program deploy` command, because everytime it
|
||||||
it will generate a *new* program address.
|
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.
|
||||||
|
|
||||||
```
|
If run with the `--program-name` option, expand only the given program.
|
||||||
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.
|
|
||||||
|
|
||||||
## Idl
|
## 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`
|
Sets a new authority on the IDL account. Both the `new-authority` and `program-id`
|
||||||
must be encoded in base 58.
|
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
|
## New
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -203,25 +201,41 @@ anchor new <program-name>
|
||||||
|
|
||||||
Creates a new program in the workspace's `programs/` directory initialized with boilerplate.
|
Creates a new program in the workspace's `programs/` directory initialized with boilerplate.
|
||||||
|
|
||||||
## Cluster
|
## Test
|
||||||
|
|
||||||
### Cluster list
|
|
||||||
|
|
||||||
```
|
```
|
||||||
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:
|
anchor upgrade <target/deploy/program.so> --program-id <program-id>
|
||||||
|
|
||||||
* 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Uses Solana's upgradeable BPF loader to upgrade the on chain program code.
|
||||||
|
|
||||||
## Verify
|
## Verify
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
Loading…
Reference in New Issue