avm: Allow install, list and use from commit (#2659)
This commit is contained in:
parent
8717364f81
commit
5900c93310
|
@ -645,6 +645,7 @@ name = "avm"
|
|||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
"cfg-if",
|
||||
"clap 4.4.6",
|
||||
"dirs",
|
||||
|
|
|
@ -22,3 +22,4 @@ reqwest = { version = "0.11.9", default-features = false, features = ["blocking"
|
|||
semver = "1.0.4"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
tempfile = "3.3.0"
|
||||
cargo_toml = "0.15.3"
|
169
avm/src/lib.rs
169
avm/src/lib.rs
|
@ -1,12 +1,14 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::header::USER_AGENT;
|
||||
use semver::Version;
|
||||
use reqwest::StatusCode;
|
||||
use semver::{Prerelease, Version};
|
||||
use serde::{de, Deserialize};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Storage directory for AVM, ~/.avm
|
||||
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
|
||||
pub fn update() -> Result<()> {
|
||||
// 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
|
||||
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.
|
||||
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");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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())
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
|
@ -192,30 +259,37 @@ pub fn fetch_versions() -> Vec<semver::Version> {
|
|||
|
||||
/// Print available versions and flags indicating installed, current and latest
|
||||
pub fn list_versions() -> Result<()> {
|
||||
let installed_versions = read_installed_versions();
|
||||
let mut installed_versions = read_installed_versions();
|
||||
|
||||
let mut available_versions = fetch_versions();
|
||||
// Reverse version list so latest versions are printed last
|
||||
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(())
|
||||
}
|
||||
|
@ -340,4 +414,29 @@ mod tests {
|
|||
fs::File::create(AVM_HOME.join("bin").join("garbage").as_path()).unwrap();
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::{Error, Result};
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use avm::InstallTarget;
|
||||
use clap::{Parser, Subcommand};
|
||||
use semver::Version;
|
||||
|
||||
|
@ -20,8 +21,9 @@ pub enum Commands {
|
|||
},
|
||||
#[clap(about = "Install a version of Anchor")]
|
||||
Install {
|
||||
#[clap(value_parser = parse_version)]
|
||||
version: Version,
|
||||
/// Anchor version or commit
|
||||
#[clap(value_parser = parse_install_target)]
|
||||
version_or_commit: InstallTarget,
|
||||
#[clap(long)]
|
||||
/// Flag to force installation even if the version
|
||||
/// is already installed
|
||||
|
@ -46,10 +48,26 @@ fn parse_version(version: &str) -> Result<Version, Error> {
|
|||
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<()> {
|
||||
match opts.command {
|
||||
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::List {} => avm::list_versions(),
|
||||
Commands::Update {} => avm::update(),
|
||||
|
|
Loading…
Reference in New Issue