Przeglądaj źródła

cli: Add ability to override toolchain from `Anchor.toml` (#2649)

acheron 2 lat temu
rodzic
commit
4f996d0a58
4 zmienionych plików z 166 dodań i 31 usunięć
  1. 2 0
      CHANGELOG.md
  2. 15 12
      cli/src/config.rs
  3. 134 14
      cli/src/lib.rs
  4. 15 5
      docs/src/pages/docs/manifest.md

+ 2 - 0
CHANGELOG.md

@@ -33,6 +33,7 @@ The minor version will be incremented upon a breaking change and the patch versi
 - cli: Add `test.upgradeable`, `test.genesis.upgradeable` setting in anchor.toml to support testing upgradeable programs ([#2641](https://github.com/coral-xyz/anchor/pull/2642)).
 - cli, client, lang, spl: Update Solana toolchain and dependencies to `1.17.0`, `1.16` remains supported ([#2645](https://github.com/coral-xyz/anchor/pull/2645)).
 - spl: Add support for memo program ([#2661](https://github.com/coral-xyz/anchor/pull/2661)).
+- cli: Add `toolchain` property in `Anchor.toml` to override Anchor and Solana versions ([#2649](https://github.com/coral-xyz/anchor/pull/2649)).
 
 ### Fixes
 
@@ -55,6 +56,7 @@ The minor version will be incremented upon a breaking change and the patch versi
 - ts: Remove `base64-js` dependency ([#2635](https://github.com/coral-xyz/anchor/pull/2635)).
 - syn: `IdlTypeDefinitionTy` enum has a new variant `Alias` ([#2637](https://github.com/coral-xyz/anchor/pull/2637)).
 - cli, client, lang, spl: Solana `1.14` is no longer supported, minimum required Solana version is `1.16.0` ([#2645](https://github.com/coral-xyz/anchor/pull/2645)).
+- cli: `anchor_version` and `solana_version` property in `Anchor.toml` that was being used in verifiable builds are moved inside `toolchain`. They are now being used for all commands in the workspace, not just verifiable builds ([#2649](https://github.com/coral-xyz/anchor/pull/2649)).
 
 ## [0.28.0] - 2023-06-09
 

+ 15 - 12
cli/src/config.rs

@@ -349,8 +349,7 @@ impl<T> std::ops::DerefMut for WithPath<T> {
 
 #[derive(Debug, Default)]
 pub struct Config {
-    pub anchor_version: Option<String>,
-    pub solana_version: Option<String>,
+    pub toolchain: ToolchainConfig,
     pub features: FeaturesConfig,
     pub registry: RegistryConfig,
     pub provider: ProviderConfig,
@@ -364,6 +363,12 @@ pub struct Config {
     pub test_config: Option<TestConfig>,
 }
 
+#[derive(Default, Clone, Debug, Serialize, Deserialize)]
+pub struct ToolchainConfig {
+    pub anchor_version: Option<String>,
+    pub solana_version: Option<String>,
+}
+
 #[derive(Default, Clone, Debug, Serialize, Deserialize)]
 pub struct FeaturesConfig {
     #[serde(default)]
@@ -444,11 +449,12 @@ impl Config {
     }
 
     pub fn docker(&self) -> String {
-        let ver = self
+        let version = self
+            .toolchain
             .anchor_version
-            .clone()
-            .unwrap_or_else(|| crate::DOCKER_BUILDER_VERSION.to_string());
-        format!("backpackapp/build:v{ver}")
+            .as_deref()
+            .unwrap_or(crate::DOCKER_BUILDER_VERSION);
+        format!("backpackapp/build:v{version}")
     }
 
     pub fn discover(cfg_override: &ConfigOverride) -> Result<Option<WithPath<Config>>> {
@@ -507,8 +513,7 @@ impl Config {
 
 #[derive(Debug, Serialize, Deserialize)]
 struct _Config {
-    anchor_version: Option<String>,
-    solana_version: Option<String>,
+    toolchain: Option<ToolchainConfig>,
     features: Option<FeaturesConfig>,
     programs: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
     registry: Option<RegistryConfig>,
@@ -583,8 +588,7 @@ impl ToString for Config {
             }
         };
         let cfg = _Config {
-            anchor_version: self.anchor_version.clone(),
-            solana_version: self.solana_version.clone(),
+            toolchain: Some(self.toolchain.clone()),
             features: Some(self.features.clone()),
             registry: Some(self.registry.clone()),
             provider: Provider {
@@ -612,8 +616,7 @@ impl FromStr for Config {
         let cfg: _Config = toml::from_str(s)
             .map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
         Ok(Config {
-            anchor_version: cfg.anchor_version,
-            solana_version: cfg.solana_version,
+            toolchain: cfg.toolchain.unwrap_or_default(),
             features: cfg.features.unwrap_or_default(),
             registry: cfg.registry.unwrap_or_default(),
             provider: ProviderConfig {

+ 134 - 14
cli/src/lib.rs

@@ -12,6 +12,8 @@ use anchor_syn::idl::types::{
 };
 use anyhow::{anyhow, Context, Result};
 use clap::Parser;
+use config::ToolchainConfig;
+use dirs::home_dir;
 use flate2::read::GzDecoder;
 use flate2::read::ZlibDecoder;
 use flate2::write::{GzEncoder, ZlibEncoder};
@@ -463,6 +465,126 @@ pub enum ClusterCommand {
 }
 
 pub fn entry(opts: Opts) -> Result<()> {
+    let toolchain_config = override_toolchain(&opts.cfg_override)?;
+    let result = process_command(opts);
+    restore_toolchain(&toolchain_config)?;
+
+    result
+}
+
+/// Override the toolchain from `Anchor.toml`.
+///
+/// Returns the previous versions to restore back to.
+fn override_toolchain(cfg_override: &ConfigOverride) -> Result<ToolchainConfig> {
+    let mut previous_versions = ToolchainConfig::default();
+
+    let cfg = Config::discover(cfg_override)?;
+    if let Some(cfg) = cfg {
+        fn get_current_version(cmd_name: &str) -> Result<String> {
+            let output: std::process::Output = std::process::Command::new(cmd_name)
+                .arg("--version")
+                .output()?;
+            let output_version = std::str::from_utf8(&output.stdout)?;
+            let version = Regex::new(r"(\d+\.\d+\.\S+)")
+                .unwrap()
+                .captures_iter(output_version)
+                .next()
+                .unwrap()
+                .get(0)
+                .unwrap()
+                .as_str()
+                .to_string();
+
+            Ok(version)
+        }
+
+        if let Some(solana_version) = &cfg.toolchain.solana_version {
+            let current_version = get_current_version("solana")?;
+            if solana_version != &current_version {
+                // We are overriding with `solana-install` command instead of using the binaries
+                // from `~/.local/share/solana/install/releases` because we use multiple Solana
+                // binaries in various commands.
+                let exit_status = std::process::Command::new("solana-install")
+                    .arg("init")
+                    .arg(solana_version)
+                    .stderr(Stdio::null())
+                    .stdout(Stdio::null())
+                    .spawn()?
+                    .wait()?;
+
+                if !exit_status.success() {
+                    println!(
+                        "Failed to override `solana` version to {solana_version}, \
+                    using {current_version} instead"
+                    );
+                } else {
+                    previous_versions.solana_version = Some(current_version);
+                }
+            }
+        }
+
+        // Anchor version override should be handled last
+        if let Some(anchor_version) = &cfg.toolchain.anchor_version {
+            let current_version = VERSION;
+            if anchor_version != current_version {
+                let binary_path = home_dir()
+                    .unwrap()
+                    .join(".avm")
+                    .join("bin")
+                    .join(format!("anchor-{anchor_version}"));
+
+                if !binary_path.exists() {
+                    println!(
+                        "`anchor` {anchor_version} is not installed with `avm`. Installing...\n"
+                    );
+
+                    let exit_status = std::process::Command::new("avm")
+                        .arg("install")
+                        .arg(anchor_version)
+                        .spawn()?
+                        .wait()?;
+
+                    if !exit_status.success() {
+                        println!(
+                            "Failed to install `anchor` {anchor_version}, \
+                            using {current_version} instead"
+                        );
+
+                        return Ok(previous_versions);
+                    }
+                }
+
+                let exit_code = std::process::Command::new(binary_path)
+                    .args(std::env::args_os().skip(1))
+                    .spawn()?
+                    .wait()?
+                    .code()
+                    .unwrap_or(1);
+                restore_toolchain(&previous_versions)?;
+                std::process::exit(exit_code);
+            }
+        }
+    }
+
+    Ok(previous_versions)
+}
+
+/// Restore toolchain to how it was before the command was run.
+fn restore_toolchain(toolchain_config: &ToolchainConfig) -> Result<()> {
+    if let Some(solana_version) = &toolchain_config.solana_version {
+        std::process::Command::new("solana-install")
+            .arg("init")
+            .arg(solana_version)
+            .stderr(Stdio::null())
+            .stdout(Stdio::null())
+            .spawn()?
+            .wait()?;
+    }
+
+    Ok(())
+}
+
+fn process_command(opts: Opts) -> Result<()> {
     match opts.command {
         Command::Init {
             name,
@@ -1000,7 +1122,7 @@ pub fn build(
     let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
     let build_config = BuildConfig {
         verifiable,
-        solana_version: solana_version.or_else(|| cfg.solana_version.clone()),
+        solana_version: solana_version.or_else(|| cfg.toolchain.solana_version.clone()),
         docker_image: docker_image.unwrap_or_else(|| cfg.docker()),
         bootstrap,
     };
@@ -1668,16 +1790,16 @@ fn verify(
     if !skip_build {
         build(
             cfg_override,
-            None,                                                  // idl
-            None,                                                  // idl ts
-            true,                                                  // verifiable
-            true,                                                  // skip lint
-            None,                                                  // program name
-            solana_version.or_else(|| cfg.solana_version.clone()), // solana version
-            docker_image,                                          // docker image
-            bootstrap,                                             // bootstrap docker image
-            None,                                                  // stdout
-            None,                                                  // stderr
+            None,                                                            // idl
+            None,                                                            // idl ts
+            true,                                                            // verifiable
+            true,                                                            // skip lint
+            None,                                                            // program name
+            solana_version.or_else(|| cfg.toolchain.solana_version.clone()), // solana version
+            docker_image,                                                    // docker image
+            bootstrap, // bootstrap docker image
+            None,      // stdout
+            None,      // stderr
             env_vars,
             cargo_args,
             false,
@@ -2647,9 +2769,7 @@ fn deserialize_idl_defined_type_to_json(
         .iter()
         .chain(idl.accounts.iter())
         .find(|defined_type| defined_type.name == defined_type_name)
-        .ok_or_else(|| {
-            anyhow::anyhow!("Struct/Enum named {} not found in IDL.", defined_type_name)
-        })?
+        .ok_or_else(|| anyhow!("Type `{}` not found in IDL.", defined_type_name))?
         .ty;
 
     let mut deserialized_fields = Map::new();

+ 15 - 5
docs/src/pages/docs/manifest.md

@@ -66,11 +66,11 @@ types = "app/src/idl/"
 #### members
 
 Sets the paths --relative to the `Anchor.toml`--
-   to all programs in the local
-   workspace, i.e., the path to the `Cargo.toml` manifest associated with each
-   program that can be compiled by the `anchor` CLI. For programs using the
-   standard Anchor workflow, this can be omitted. For programs not written in Anchor
-   but still want to publish, this should be added.
+to all programs in the local
+workspace, i.e., the path to the `Cargo.toml` manifest associated with each
+program that can be compiled by the `anchor` CLI. For programs using the
+standard Anchor workflow, this can be omitted. For programs not written in Anchor
+but still want to publish, this should be added.
 
 Example:
 
@@ -193,3 +193,13 @@ filename = "some_account.json"
 address = "Ev8WSPQsGb4wfjybqff5eZNcS3n6HaMsBkMk9suAiuM"
 filename = "some_other_account.json"
 ```
+
+## toolchain
+
+Override toolchain data in the workspace similar to [`rust-toolchain.toml`](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file).
+
+```toml
+[toolchain]
+anchor_version = "0.28.0"    # `anchor-cli` version to use(requires `avm`)
+solana_version = "1.16.0"    # Solana version to use(applies to all Solana tools)
+```