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] ## [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

1
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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