Skip to content

Commit

Permalink
avm: Allow install, list and use from commit (#2659)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arrowana authored Oct 14, 2023
1 parent 8717364 commit 5900c93
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 39 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions avm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 134 additions & 35 deletions avm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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(|| {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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"
)
}
}
26 changes: 22 additions & 4 deletions avm/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::{Error, Result};
use anyhow::{anyhow, Error, Result};
use avm::InstallTarget;
use clap::{Parser, Subcommand};
use semver::Version;

Expand All @@ -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
Expand All @@ -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(),
Expand Down

1 comment on commit 5900c93

@vercel
Copy link

@vercel vercel bot commented on 5900c93 Oct 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

anchor-docs – ./

anchor-docs-git-master-200ms.vercel.app
www.anchor-lang.com
anchor-lang.com
anchor-docs-200ms.vercel.app

Please sign in to comment.