Browse Source

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

Paul 3 years ago
parent
commit
1749a7bd53
5 changed files with 212 additions and 76 deletions
  1. 1 1
      CHANGELOG.md
  2. 1 0
      Cargo.lock
  3. 1 0
      cli/Cargo.toml
  4. 120 0
      cli/src/lib.rs
  5. 89 75
      docs/src/cli/commands.md

+ 1 - 1
CHANGELOG.md

@@ -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 - 0
Cargo.lock

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

+ 1 - 0
cli/Cargo.toml

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

+ 89 - 75
docs/src/cli/commands.md

@@ -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,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>/`.
 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 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`
+Deploys all programs in the workspace to the configured cluster.
 
 
 ::: tip Note
 ::: 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
 ## 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,24 +201,40 @@ 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
-
-### 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
 ## Verify