Explorar o código

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

Paul %!s(int64=3) %!d(string=hai) anos
pai
achega
1749a7bd53
Modificáronse 5 ficheiros con 212 adicións e 76 borrados
  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]
 
-
 ### 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 - 0
Cargo.lock

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

+ 1 - 0
cli/Cargo.toml

@@ -38,3 +38,4 @@ tokio = "1.0"
 pathdiff = "0.2.0"
 cargo_toml = "0.9.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 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,

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

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