浏览代码

avm: Allow install, list and use from commit (#2659)

Pierre 2 年之前
父节点
当前提交
5900c93310
共有 4 个文件被更改,包括 158 次插入39 次删除
  1. 1 0
      Cargo.lock
  2. 1 0
      avm/Cargo.toml
  3. 134 35
      avm/src/lib.rs
  4. 22 4
      avm/src/main.rs

+ 1 - 0
Cargo.lock

@@ -645,6 +645,7 @@ name = "avm"
 version = "0.28.0"
 version = "0.28.0"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
+ "cargo_toml",
  "cfg-if",
  "cfg-if",
  "clap 4.4.6",
  "clap 4.4.6",
  "dirs",
  "dirs",

+ 1 - 0
avm/Cargo.toml

@@ -22,3 +22,4 @@ reqwest = { version = "0.11.9", default-features = false, features = ["blocking"
 semver = "1.0.4"
 semver = "1.0.4"
 serde = { version = "1.0.136", features = ["derive"] }
 serde = { version = "1.0.136", features = ["derive"] }
 tempfile = "3.3.0"
 tempfile = "3.3.0"
+cargo_toml = "0.15.3"

+ 134 - 35
avm/src/lib.rs

@@ -1,12 +1,14 @@
 use anyhow::{anyhow, Result};
 use anyhow::{anyhow, Result};
 use once_cell::sync::Lazy;
 use once_cell::sync::Lazy;
 use reqwest::header::USER_AGENT;
 use reqwest::header::USER_AGENT;
-use semver::Version;
+use reqwest::StatusCode;
+use semver::{Prerelease, Version};
 use serde::{de, Deserialize};
 use serde::{de, Deserialize};
 use std::fs;
 use std::fs;
 use std::io::Write;
 use std::io::Write;
 use std::path::PathBuf;
 use std::path::PathBuf;
 use std::process::Stdio;
 use std::process::Stdio;
+use std::str::FromStr;
 
 
 /// Storage directory for AVM, ~/.avm
 /// Storage directory for AVM, ~/.avm
 pub static AVM_HOME: Lazy<PathBuf> = Lazy::new(|| {
 pub static AVM_HOME: Lazy<PathBuf> = Lazy::new(|| {
@@ -75,32 +77,97 @@ pub fn use_version(opt_version: Option<Version>) -> Result<()> {
 /// Update to the latest version
 /// Update to the latest version
 pub fn update() -> Result<()> {
 pub fn update() -> Result<()> {
     // Find last stable version
     // Find last stable version
-    let version = &get_latest_version();
+    let version = get_latest_version();
 
 
-    install_version(version, false)
+    install_anchor(InstallTarget::Version(version), false)
+}
+
+#[derive(Clone)]
+pub enum InstallTarget {
+    Version(Version),
+    Commit(String),
+}
+
+#[derive(Deserialize)]
+struct GetCommitResponse {
+    sha: String,
+}
+
+/// The commit sha provided can be shortened,
+///
+/// returns the full commit sha3 for unique versioning downstream
+pub fn check_and_get_full_commit(commit: &str) -> Result<String> {
+    let client = reqwest::blocking::Client::new();
+    let response = client
+        .get(format!(
+            "https://api.github.com/repos/coral-xyz/anchor/commits/{commit}"
+        ))
+        .header(USER_AGENT, "avm https://github.com/coral-xyz/anchor")
+        .send()
+        .unwrap();
+    if response.status() != StatusCode::OK {
+        return Err(anyhow!(
+            "Error checking commit {commit}: {}",
+            response.text().unwrap()
+        ));
+    };
+    let get_commit_response: GetCommitResponse = response.json().unwrap();
+    Ok(get_commit_response.sha)
+}
+
+fn get_anchor_version_from_commit(commit: &str) -> Version {
+    // We read the version from cli/Cargo.toml since there is no simpler way to do so
+    let client = reqwest::blocking::Client::new();
+    let response = client
+        .get(format!(
+            "https://raw.githubusercontent.com/coral-xyz/anchor/{}/cli/Cargo.toml",
+            commit
+        ))
+        .header(USER_AGENT, "avm https://github.com/coral-xyz/anchor")
+        .send()
+        .unwrap();
+    if response.status() != StatusCode::OK {
+        panic!("Could not find anchor-cli version for commit: {response:?}");
+    };
+    let anchor_cli_cargo_toml = response.text().unwrap();
+    let anchor_cli_manifest = cargo_toml::Manifest::from_str(&anchor_cli_cargo_toml).unwrap();
+    let anchor_version = anchor_cli_manifest.package().version();
+    let mut version = Version::parse(anchor_version).unwrap();
+    version.pre = Prerelease::from_str(commit).unwrap();
+    version
 }
 }
 
 
 /// Install a version of anchor-cli
 /// Install a version of anchor-cli
-pub fn install_version(version: &Version, force: bool) -> Result<()> {
+pub fn install_anchor(install_target: InstallTarget, force: bool) -> Result<()> {
     // If version is already installed we ignore the request.
     // If version is already installed we ignore the request.
     let installed_versions = read_installed_versions();
     let installed_versions = read_installed_versions();
-    if installed_versions.contains(version) && !force {
+
+    let mut args: Vec<String> = vec![
+        "install".into(),
+        "--git".into(),
+        "https://github.com/coral-xyz/anchor".into(),
+        "anchor-cli".into(),
+        "--locked".into(),
+        "--root".into(),
+        AVM_HOME.to_str().unwrap().into(),
+    ];
+    let version = match install_target {
+        InstallTarget::Version(version) => {
+            args.extend(["--tag".into(), format!("v{}", version), "anchor-cli".into()]);
+            version
+        }
+        InstallTarget::Commit(commit) => {
+            args.extend(["--rev".into(), commit.clone()]);
+            get_anchor_version_from_commit(&commit)
+        }
+    };
+    if installed_versions.contains(&version) && !force {
         println!("Version {version} is already installed");
         println!("Version {version} is already installed");
         return Ok(());
         return Ok(());
     }
     }
 
 
     let exit = std::process::Command::new("cargo")
     let exit = std::process::Command::new("cargo")
-        .args([
-            "install",
-            "--git",
-            "https://github.com/coral-xyz/anchor",
-            "--tag",
-            &format!("v{}", &version),
-            "anchor-cli",
-            "--locked",
-            "--root",
-            AVM_HOME.to_str().unwrap(),
-        ])
+        .args(args)
         .stdout(Stdio::inherit())
         .stdout(Stdio::inherit())
         .stderr(Stdio::inherit())
         .stderr(Stdio::inherit())
         .output()
         .output()
@@ -192,30 +259,37 @@ pub fn fetch_versions() -> Vec<semver::Version> {
 
 
 /// Print available versions and flags indicating installed, current and latest
 /// Print available versions and flags indicating installed, current and latest
 pub fn list_versions() -> Result<()> {
 pub fn list_versions() -> Result<()> {
-    let installed_versions = read_installed_versions();
+    let mut installed_versions = read_installed_versions();
 
 
     let mut available_versions = fetch_versions();
     let mut available_versions = fetch_versions();
     // Reverse version list so latest versions are printed last
     // Reverse version list so latest versions are printed last
     available_versions.reverse();
     available_versions.reverse();
 
 
-    available_versions.iter().enumerate().for_each(|(i, v)| {
-        print!("{v}");
-        let mut flags = vec![];
-        if i == available_versions.len() - 1 {
-            flags.push("latest");
-        }
-        if installed_versions.contains(v) {
-            flags.push("installed");
-        }
-        if current_version().is_ok() && current_version().unwrap() == v.clone() {
-            flags.push("current");
-        }
-        if flags.is_empty() {
-            println!();
-        } else {
-            println!("\t({})", flags.join(", "));
-        }
-    });
+    let print_versions =
+        |versions: Vec<Version>, installed_versions: &mut Vec<Version>, show_latest: bool| {
+            versions.iter().enumerate().for_each(|(i, v)| {
+                print!("{v}");
+                let mut flags = vec![];
+                if i == versions.len() - 1 && show_latest {
+                    flags.push("latest");
+                }
+                if let Some(position) = installed_versions.iter().position(|iv| iv == v) {
+                    flags.push("installed");
+                    installed_versions.remove(position);
+                }
+
+                if current_version().is_ok() && current_version().unwrap() == v.clone() {
+                    flags.push("current");
+                }
+                if flags.is_empty() {
+                    println!();
+                } else {
+                    println!("\t({})", flags.join(", "));
+                }
+            })
+        };
+    print_versions(available_versions, &mut installed_versions, true);
+    print_versions(installed_versions.clone(), &mut installed_versions, false);
 
 
     Ok(())
     Ok(())
 }
 }
@@ -340,4 +414,29 @@ mod tests {
         fs::File::create(AVM_HOME.join("bin").join("garbage").as_path()).unwrap();
         fs::File::create(AVM_HOME.join("bin").join("garbage").as_path()).unwrap();
         assert!(read_installed_versions() == expected);
         assert!(read_installed_versions() == expected);
     }
     }
+
+    #[test]
+    fn test_get_anchor_version_from_commit() {
+        let version = get_anchor_version_from_commit("e1afcbf71e0f2e10fae14525934a6a68479167b9");
+        assert_eq!(
+            version.to_string(),
+            "0.28.0-e1afcbf71e0f2e10fae14525934a6a68479167b9"
+        )
+    }
+
+    #[test]
+    fn test_check_and_get_full_commit_when_full_commit() {
+        assert_eq!(
+            check_and_get_full_commit("e1afcbf71e0f2e10fae14525934a6a68479167b9").unwrap(),
+            "e1afcbf71e0f2e10fae14525934a6a68479167b9"
+        )
+    }
+
+    #[test]
+    fn test_check_and_get_full_commit_when_partial_commit() {
+        assert_eq!(
+            check_and_get_full_commit("e1afcbf").unwrap(),
+            "e1afcbf71e0f2e10fae14525934a6a68479167b9"
+        )
+    }
 }
 }

+ 22 - 4
avm/src/main.rs

@@ -1,4 +1,5 @@
-use anyhow::{Error, Result};
+use anyhow::{anyhow, Error, Result};
+use avm::InstallTarget;
 use clap::{Parser, Subcommand};
 use clap::{Parser, Subcommand};
 use semver::Version;
 use semver::Version;
 
 
@@ -20,8 +21,9 @@ pub enum Commands {
     },
     },
     #[clap(about = "Install a version of Anchor")]
     #[clap(about = "Install a version of Anchor")]
     Install {
     Install {
-        #[clap(value_parser = parse_version)]
-        version: Version,
+        /// Anchor version or commit
+        #[clap(value_parser = parse_install_target)]
+        version_or_commit: InstallTarget,
         #[clap(long)]
         #[clap(long)]
         /// Flag to force installation even if the version
         /// Flag to force installation even if the version
         /// is already installed
         /// is already installed
@@ -46,10 +48,26 @@ fn parse_version(version: &str) -> Result<Version, Error> {
         Version::parse(version).map_err(|e| anyhow::anyhow!(e))
         Version::parse(version).map_err(|e| anyhow::anyhow!(e))
     }
     }
 }
 }
+
+fn parse_install_target(version_or_commit: &str) -> Result<InstallTarget, Error> {
+    parse_version(version_or_commit)
+        .map(InstallTarget::Version)
+        .or_else(|version_error| {
+            avm::check_and_get_full_commit(version_or_commit)
+                .map(InstallTarget::Commit)
+                .map_err(|commit_error| {
+                    anyhow!("Not a valid version or commit: {version_error}, {commit_error}")
+                })
+        })
+}
+
 pub fn entry(opts: Cli) -> Result<()> {
 pub fn entry(opts: Cli) -> Result<()> {
     match opts.command {
     match opts.command {
         Commands::Use { version } => avm::use_version(version),
         Commands::Use { version } => avm::use_version(version),
-        Commands::Install { version, force } => avm::install_version(&version, force),
+        Commands::Install {
+            version_or_commit,
+            force,
+        } => avm::install_anchor(version_or_commit, force),
         Commands::Uninstall { version } => avm::uninstall_version(&version),
         Commands::Uninstall { version } => avm::uninstall_version(&version),
         Commands::List {} => avm::list_versions(),
         Commands::List {} => avm::list_versions(),
         Commands::Update {} => avm::update(),
         Commands::Update {} => avm::update(),