Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow install, list and use from commit #2659

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
126 changes: 91 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,70 @@ 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),
}

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 +232,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 +387,13 @@ 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"
)
}
}
22 changes: 19 additions & 3 deletions avm/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::{Error, Result};
use avm::InstallTarget;
use clap::{Parser, Subcommand};
use semver::Version;

Expand All @@ -20,8 +21,10 @@ pub enum Commands {
},
#[clap(about = "Install a version of Anchor")]
Install {
#[clap(value_parser = parse_version)]
version: Version,
/// Anchor version or commit
version: String,
#[clap(short, long, required = false)]
is_commit: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Single arg trying ver then commit, less clunky than the extra flag

I'm guessing this is not implemented yet. Don't think is_commit flag is necessary, can just decide whether it's a version or a commit based on the input.

Copy link
Contributor Author

@Arrowana Arrowana Oct 12, 2023

Choose a reason for hiding this comment

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

Forgot to push, pushed and back to single arg

Trying to install an incorrect commit

target/debug/avm install dcafb789e1635b8f90e8fd3badd0f9a015d204d42ds2`
error: invalid value 'dcafb789e1635b8f90e8fd3badd0f9a015d204d42ds2' for '<VERSION_OR_COMMIT>': Not a valid version or commit: unexpected character 'd' while parsing major version number, Error checking commit dcafb789e1635b8f90e8fd3badd0f9a015d204d42ds2: {"message":"No commit found for SHA: dcafb789e1635b8f90e8fd3badd0f9a015d204d42ds2","documentation_url":"https://docs.github.com/rest/commits/commits#get-a-commit"}

We can also provide a shortened commit sha with
518b390

#[clap(long)]
/// Flag to force installation even if the version
/// is already installed
Expand All @@ -46,10 +49,23 @@ fn parse_version(version: &str) -> Result<Version, Error> {
Version::parse(version).map_err(|e| anyhow::anyhow!(e))
}
}

fn parse_install_target(version_or_commit: String, is_commit: bool) -> InstallTarget {
if is_commit {
InstallTarget::Commit(version_or_commit)
} else {
InstallTarget::Version(parse_version(&version_or_commit).unwrap())
}
}

pub fn entry(opts: Cli) -> Result<()> {
match opts.command {
Commands::Use { version } => avm::use_version(version),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I've tried it and confirm it works as expected but one area that might need improvement is avm use command as it requires putting the full version-hash avm use 0.28.0-6cf200493a307c01487c7b492b4893e0d6f6cb23 and doesn't work with avm use 6cf200.

Commands::Install { version, force } => avm::install_version(&version, force),
Commands::Install {
version,
is_commit,
force,
} => avm::install_anchor(parse_install_target(version, is_commit), force),
Commands::Uninstall { version } => avm::uninstall_version(&version),
Commands::List {} => avm::list_versions(),
Commands::Update {} => avm::update(),
Expand Down