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 anchor expand command #1160

Merged
merged 16 commits into from
Dec 27, 2021
Merged
Show file tree
Hide file tree
Changes from 13 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ incremented for features.

* lang: Add `programdata_address: Option<Pubkey>` field to `Program` account. Will be populated if account is a program owned by the upgradable bpf loader ([#1125](https://github.com/project-serum/anchor/pull/1125))
* lang,ts,ci,cli,docs: update solana toolchain to version 1.8.5([#1133](https://github.com/project-serum/anchor/pull/1133))
* cli: Add `anchor expand` command which wraps around `cargo expand` ([#1160](https://github.com/project-serum/anchor/pull/1160))

### Breaking

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ tokio = "1.0"
pathdiff = "0.2.0"
cargo_toml = "0.9.2"
walkdir = "2"
chrono = "0.4.19"
120 changes: 120 additions & 0 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
164 changes: 89 additions & 75 deletions docs/src/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,21 +31,6 @@ SUBCOMMANDS:
Cargo.toml
```

## Init
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this list is now in alphabetical order like the overview list above


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

Expand All @@ -60,77 +46,49 @@ 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>/`.

## Deploy
## Cluster

### Cluster list

```
anchor deploy
anchor cluster list
```

Deploys all programs in the workspace to the configured cluster.

::: tip Note
This is different from the `solana program deploy` command, because everytime it's run
it will generate a *new* program address.
:::

## Upgrade
This lists cluster endpoints:

```
anchor upgrade <target/deploy/program.so> --program-id <program-id>
```
Cluster Endpoints:

Uses Solana's upgradeable BPF loader to upgrade the on chain program code.
* 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
```

## Test
## Deploy

```
anchor test
anchor deploy
```

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`
Deploys all programs in the workspace to the configured cluster.

::: 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.
This is different from the `solana program deploy` command, because everytime it's run
it will generate a *new* program address.
:::

## Migrate
## Expand

```
anchor migrate
anchor expand
```

Runs the deploy script located at `migrations/deploy.js`, injecting a provider configured
from the workspace's `Anchor.toml`. For example,
If run inside a program folder, expands the macros of the program.

```javascript
// File: migrations/deploys.js
If run in the workspace but outside a program folder, expands the macros of the workspace.

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

Expand Down Expand Up @@ -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

```
Expand All @@ -203,24 +201,40 @@ 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.

```
Cluster Endpoints:
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

* 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

Expand Down