cli: anchor version manager (avm) (#1385)

This commit is contained in:
Tom Linton 2022-02-04 13:13:40 +13:00 committed by GitHub
parent 33c5dab909
commit 75469f423c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 509 additions and 84 deletions

View File

@ -23,6 +23,7 @@ incremented for features.
* ts: Add new `methods` namespace to the program client, introducing a more ergonomic builder API ([#1324](https://github.com/project-serum/anchor/pull/1324)).
* ts: Add registry utility for fetching the latest verified build ([#1371](https://github.com/project-serum/anchor/pull/1371)).
* cli: Expose the solana-test-validator --account flag in Anchor.toml via [[test.validator.account]] ([#1366](https://github.com/project-serum/anchor/pull/1366)).
* cli: Add avm, a tool for managing anchor-cli versions ([#1385](https://github.com/project-serum/anchor/pull/1385)).
### Breaking

196
Cargo.lock generated
View File

@ -154,11 +154,11 @@ dependencies = [
"cargo_toml",
"chrono",
"clap 3.0.13",
"dirs",
"dirs 3.0.2",
"flate2",
"heck 0.3.3",
"pathdiff",
"rand 0.7.3",
"rand",
"reqwest",
"semver 1.0.4",
"serde",
@ -301,6 +301,24 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "avm"
version = "0.1.0"
dependencies = [
"anyhow",
"cfg-if 1.0.0",
"clap 3.0.13",
"dialoguer 0.9.0",
"dirs 4.0.0",
"once_cell",
"reqwest",
"semver 1.0.4",
"serde",
"serde_json",
"tempfile",
"thiserror",
]
[[package]]
name = "backtrace"
version = "0.3.63"
@ -959,6 +977,18 @@ dependencies = [
"tempfile",
]
[[package]]
name = "dialoguer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61579ada4ec0c6031cfac3f86fdba0d195a7ebeb5e36693bd53cb5999a25beeb"
dependencies = [
"console 0.15.0",
"lazy_static",
"tempfile",
"zeroize",
]
[[package]]
name = "digest"
version = "0.8.1"
@ -995,6 +1025,15 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
@ -1074,7 +1113,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek 3.2.0",
"ed25519",
"rand 0.7.3",
"rand",
"serde",
"serde_bytes",
"sha2",
@ -1197,6 +1236,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fastrand"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
"instant",
]
[[package]]
name = "feature-probe"
version = "0.1.1"
@ -1430,9 +1478,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "h2"
version = "0.3.7"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55"
checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e"
dependencies = [
"bytes 1.1.0",
"fnv",
@ -1552,7 +1600,7 @@ checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
dependencies = [
"bytes 1.1.0",
"fnv",
"itoa",
"itoa 0.4.8",
]
[[package]]
@ -1599,7 +1647,7 @@ dependencies = [
"http-body",
"httparse",
"httpdate",
"itoa",
"itoa 0.4.8",
"pin-project-lite",
"socket2 0.4.2",
"tokio",
@ -1610,17 +1658,15 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.22.1"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
dependencies = [
"futures-util",
"http",
"hyper",
"log",
"rustls",
"tokio",
"tokio-rustls",
"webpki",
]
[[package]]
@ -1717,6 +1763,12 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "jobserver"
version = "0.1.24"
@ -1794,7 +1846,7 @@ dependencies = [
"libsecp256k1-core",
"libsecp256k1-gen-ecmult",
"libsecp256k1-gen-genmult",
"rand 0.7.3",
"rand",
"serde",
"sha2",
"typenum",
@ -2390,21 +2442,9 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_chacha",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
"rand_hc",
]
[[package]]
@ -2417,16 +2457,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@ -2441,9 +2471,6 @@ name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_hc"
@ -2454,15 +2481,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "rayon"
version = "1.5.1"
@ -2541,15 +2559,16 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.6"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525"
dependencies = [
"base64 0.13.0",
"bytes 1.1.0",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
@ -2565,6 +2584,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
@ -2636,17 +2656,25 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.19.1"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84"
dependencies = [
"base64 0.13.0",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "rustls-pemfile"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9"
dependencies = [
"base64 0.13.0",
]
[[package]]
name = "rustversion"
version = "1.0.5"
@ -2692,9 +2720,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.6.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
@ -2764,9 +2792,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.130"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [
"serde_derive",
]
@ -2782,9 +2810,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.130"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2 1.0.32",
"quote 1.0.10",
@ -2793,11 +2821,11 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.71"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085"
dependencies = [
"itoa",
"itoa 1.0.1",
"ryu",
"serde",
]
@ -2809,7 +2837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
dependencies = [
"form_urlencoded",
"itoa",
"itoa 0.4.8",
"ryu",
"serde",
]
@ -2846,7 +2874,7 @@ dependencies = [
"arrayref",
"bincode",
"bs58 0.3.1",
"rand 0.7.3",
"rand",
"serde",
"serde_json",
"serum-borsh",
@ -3099,7 +3127,7 @@ dependencies = [
"either",
"lazy_static",
"libc",
"rand_chacha 0.2.2",
"rand_chacha",
"regex-syntax",
"reqwest",
"ring",
@ -3219,7 +3247,7 @@ dependencies = [
"clap 2.33.3",
"log",
"nix",
"rand 0.7.3",
"rand",
"serde",
"serde_derive",
"socket2 0.3.19",
@ -3246,7 +3274,7 @@ dependencies = [
"libc",
"log",
"nix",
"rand 0.7.3",
"rand",
"rayon",
"serde",
"solana-logger",
@ -3278,7 +3306,7 @@ dependencies = [
"log",
"num-derive",
"num-traits",
"rand 0.7.3",
"rand",
"rustc_version 0.2.3",
"rustversion",
"serde",
@ -3311,7 +3339,7 @@ checksum = "82b91d441ed00427226b08e9990367ecb4c952c70ab827c0250bd233e1ae9540"
dependencies = [
"base32",
"console 0.14.1",
"dialoguer",
"dialoguer 0.6.2",
"hidapi",
"log",
"num-derive",
@ -3351,7 +3379,7 @@ dependencies = [
"num-traits",
"num_cpus",
"ouroboros",
"rand 0.7.3",
"rand",
"rayon",
"regex",
"rustc_version 0.2.3",
@ -3409,8 +3437,8 @@ dependencies = [
"num-traits",
"pbkdf2 0.6.0",
"qstring",
"rand 0.7.3",
"rand_chacha 0.2.2",
"rand",
"rand_chacha",
"rand_core 0.6.3",
"rustc_version 0.2.3",
"rustversion",
@ -3675,13 +3703,13 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.2.0"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if 1.0.0",
"fastrand",
"libc",
"rand 0.8.4",
"redox_syscall 0.2.10",
"remove_dir_all",
"winapi",
@ -3770,7 +3798,7 @@ dependencies = [
"hmac 0.8.1",
"once_cell",
"pbkdf2 0.4.0",
"rand 0.7.3",
"rand",
"rustc-hash",
"sha2",
"thiserror",
@ -3837,9 +3865,9 @@ dependencies = [
[[package]]
name = "tokio-rustls"
version = "0.22.0"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b"
dependencies = [
"rustls",
"tokio",
@ -3915,7 +3943,7 @@ dependencies = [
"input_buffer",
"log",
"native-tls",
"rand 0.7.3",
"rand",
"sha-1",
"url",
"utf-8",
@ -4144,9 +4172,9 @@ dependencies = [
[[package]]
name = "webpki"
version = "0.21.4"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
@ -4154,9 +4182,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.21.1"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449"
dependencies = [
"webpki",
]

View File

@ -6,6 +6,7 @@ codegen-units = 1
[workspace]
members = [
"avm",
"cli",
"client",
"lang",

26
avm/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "avm"
version = "0.1.0"
edition = "2018"
[[bin]]
name = "avm"
path = "src/main.rs"
[[bin]]
name = "anchor"
path = "src/anchor/main.rs"
[dependencies]
clap = { version = "3.0.13", features = [ "derive" ]}
cfg-if = "1.0.0"
anyhow = "1.0.32"
dialoguer = "0.9.0"
dirs = "4.0"
semver = "1.0.4"
serde = { version = "1.0.136", features = [ "derive" ]}
serde_json = "1.0.78"
thiserror = "1.0.30"
once_cell = { version = "1.8.0" }
reqwest = { version = "0.11.9", features = ['blocking', 'json'] }
tempfile = "3.3.0"

24
avm/src/anchor/main.rs Normal file
View File

@ -0,0 +1,24 @@
use std::{env, fs, process::Command};
fn main() -> anyhow::Result<()> {
let args = env::args().skip(1).collect::<Vec<String>>();
let version = avm::current_version()
.map_err(|_e| anyhow::anyhow!("Anchor version not set. Please run `avm use latest`."))?;
let binary_path = avm::version_binary_path(&version);
if fs::metadata(&binary_path).is_err() {
anyhow::bail!(
"anchor-cli {} not installed. Please run `avm use {}`.",
version,
version
);
}
Command::new(binary_path)
.args(args)
.spawn()?
.wait_with_output()
.expect("Failed to run anchor-cli");
Ok(())
}

287
avm/src/lib.rs Normal file
View File

@ -0,0 +1,287 @@
use anyhow::{anyhow, Result};
use dialoguer::Input;
use once_cell::sync::Lazy;
use reqwest::header::USER_AGENT;
use semver::Version;
use serde::{de, Deserialize};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::process::Stdio;
/// Storage directory for AVM, ~/.avm
pub static AVM_HOME: Lazy<PathBuf> = Lazy::new(|| {
cfg_if::cfg_if! {
if #[cfg(test)] {
let dir = tempfile::tempdir().expect("Could not create temporary directory");
dir.path().join(".avm")
} else {
let mut user_home = dirs::home_dir().expect("Could not find home directory");
user_home.push(".avm");
user_home
}
}
});
/// Path to the current version file ~/.avm/.version
pub fn current_version_file_path() -> PathBuf {
let mut current_version_file_path = AVM_HOME.to_path_buf();
current_version_file_path.push(".version");
current_version_file_path
}
/// Read the current version from the version file
pub fn current_version() -> Result<Version> {
let v = fs::read_to_string(current_version_file_path().as_path())
.map_err(|e| anyhow!("Could not read version file: {}", e))?;
Version::parse(v.trim_end_matches('\n').to_string().as_str())
.map_err(|e| anyhow!("Could not parse version file: {}", e))
}
/// Path to the binary for the given version
pub fn version_binary_path(version: &Version) -> PathBuf {
let mut version_path = AVM_HOME.join("bin");
version_path.push(format!("anchor-{}", version));
version_path
}
/// Update the current version to a new version
pub fn use_version(version: &Version) -> Result<()> {
let installed_versions = read_installed_versions();
// Make sure the requested version is installed
if !installed_versions.contains(version) {
let input: String = Input::new()
.with_prompt(format!(
"anchor-cli {} is not installed, would you like to install it? (y/n)",
version
))
.with_initial_text("y")
.default("n".into())
.interact_text()?;
if matches!(input.as_str(), "y" | "yy" | "Y" | "yes" | "Yes") {
install_version(version)?;
}
}
let mut current_version_file = fs::File::create(current_version_file_path().as_path())?;
current_version_file.write_all(version.to_string().as_bytes())?;
Ok(())
}
/// Install a version of anchor-cli
pub fn install_version(version: &Version) -> Result<()> {
let exit = std::process::Command::new("cargo")
.args(&[
"install",
"--git",
"https://github.com/project-serum/anchor",
"--tag",
&format!("v{}", &version),
"anchor-cli",
"--locked",
"--root",
AVM_HOME.to_str().unwrap(),
])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output()
.map_err(|e| {
anyhow::format_err!("Cargo install for {} failed: {}", version, e.to_string())
})?;
if !exit.status.success() {
return Err(anyhow!(
"Failed to install {}, is it a valid version?",
version
));
}
fs::rename(
&AVM_HOME.join("bin").join("anchor"),
&AVM_HOME.join("bin").join(format!("anchor-{}", version)),
)?;
Ok(())
}
/// Remove an installed version of anchor-cli
pub fn uninstall_version(version: &Version) -> Result<()> {
let version_path = AVM_HOME.join("bin").join(format!("anchor-{}", version));
if !version_path.exists() {
return Err(anyhow!("anchor-cli {} is not installed", version));
}
if version == &current_version().unwrap() {
return Err(anyhow!("anchor-cli {} is currently in use", version));
}
fs::remove_file(version_path.as_path())?;
Ok(())
}
/// Ensure the users home directory is setup with the paths required by AVM.
pub fn ensure_paths() {
let home_dir = AVM_HOME.to_path_buf();
if !home_dir.as_path().exists() {
fs::create_dir_all(home_dir.clone()).expect("Could not create .avm directory");
}
let bin_dir = home_dir.join("bin");
if !bin_dir.as_path().exists() {
fs::create_dir_all(bin_dir).expect("Could not create .avm/bin directory");
}
if !current_version_file_path().exists() {
fs::File::create(current_version_file_path()).expect("Could not create .version file");
}
}
/// Retrieve a list of installable versions of anchor-cli using the GitHub API and tags on the Anchor
/// repository.
pub fn fetch_versions() -> Vec<semver::Version> {
#[derive(Deserialize)]
struct Release {
#[serde(rename = "name", deserialize_with = "version_deserializer")]
version: semver::Version,
}
fn version_deserializer<'de, D>(deserializer: D) -> Result<semver::Version, D::Error>
where
D: de::Deserializer<'de>,
{
let s: &str = de::Deserialize::deserialize(deserializer)?;
Version::parse(s.trim_start_matches('v')).map_err(de::Error::custom)
}
let client = reqwest::blocking::Client::new();
let versions: Vec<Release> = client
.get("https://api.github.com/repos/project-serum/anchor/tags")
.header(USER_AGENT, "avm https://github.com/project-serum/anchor")
.send()
.unwrap()
.json()
.unwrap();
versions.into_iter().map(|r| r.version).collect()
}
/// Print available versions and flags indicating installed, current and latest
pub fn list_versions() -> Result<()> {
let 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().unwrap() == v.clone() {
flags.push("current");
}
if flags.is_empty() {
println!();
} else {
println!("\t({})", flags.join(", "));
}
});
Ok(())
}
pub fn get_latest_version() -> semver::Version {
let available_versions = fetch_versions();
available_versions.first().unwrap().clone()
}
/// Read the installed anchor-cli versions by reading the binaries in the AVM_HOME/bin directory.
pub fn read_installed_versions() -> Vec<semver::Version> {
let home_dir = AVM_HOME.to_path_buf();
let mut versions = vec![];
for file in fs::read_dir(&home_dir.join("bin")).unwrap() {
let file_name = file.unwrap().file_name();
// Match only things that look like anchor-*
if file_name.to_str().unwrap().starts_with("anchor-") {
let version = file_name
.to_str()
.unwrap()
.trim_start_matches("anchor-")
.parse::<semver::Version>()
.unwrap();
versions.push(version);
}
}
versions
}
#[cfg(test)]
mod tests {
use crate::*;
use semver::Version;
use std::fs;
use std::io::Write;
#[test]
fn test_ensure_paths() {
ensure_paths();
assert!(AVM_HOME.exists());
let bin_dir = AVM_HOME.join("bin");
assert!(bin_dir.exists());
let current_version_file = AVM_HOME.join(".version");
assert!(current_version_file.exists());
}
#[test]
fn test_current_version_file_path() {
ensure_paths();
assert!(current_version_file_path().exists());
}
#[test]
fn test_version_binary_path() {
assert!(
version_binary_path(&Version::parse("0.18.2").unwrap())
== AVM_HOME.join("bin/anchor-0.18.2")
);
}
#[test]
fn test_current_version() {
ensure_paths();
let mut current_version_file =
fs::File::create(current_version_file_path().as_path()).unwrap();
current_version_file.write_all("0.18.2".as_bytes()).unwrap();
assert!(current_version().unwrap() == Version::parse("0.18.2").unwrap());
}
#[test]
#[should_panic(expected = "anchor-cli 0.18.1 is not installed")]
fn test_uninstall_non_installed_version() {
uninstall_version(&Version::parse("0.18.1").unwrap()).unwrap();
}
#[test]
#[should_panic(expected = "anchor-cli 0.18.2 is currently in use")]
fn test_uninstalled_in_use_version() {
ensure_paths();
let version = Version::parse("0.18.2").unwrap();
let mut current_version_file =
fs::File::create(current_version_file_path().as_path()).unwrap();
current_version_file.write_all("0.18.2".as_bytes()).unwrap();
// Create a fake binary for anchor-0.18.2 in the bin directory
fs::File::create(version_binary_path(&version)).unwrap();
uninstall_version(&version).unwrap();
}
#[test]
fn test_read_installed_versions() {
ensure_paths();
let version = Version::parse("0.18.2").unwrap();
// Create a fake binary for anchor-0.18.2 in the bin directory
fs::File::create(version_binary_path(&version)).unwrap();
let expected = vec![version];
assert!(read_installed_versions() == expected);
// Should ignore this file because its not anchor- prefixed
fs::File::create(AVM_HOME.join("bin").join("garbage").as_path()).unwrap();
assert!(read_installed_versions() == expected);
}
}

58
avm/src/main.rs Normal file
View File

@ -0,0 +1,58 @@
use anyhow::{Error, Result};
use clap::{Parser, Subcommand};
use semver::Version;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Parser)]
#[clap(name = "avm", about = "Anchor version manager")]
pub struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
#[clap(about = "Use a specific version of Anchor")]
Use {
#[clap(parse(try_from_str = parse_version))]
version: Version,
},
#[clap(about = "Install a version of Anchor")]
Install {
#[clap(parse(try_from_str = parse_version))]
version: Version,
},
#[clap(about = "Uninstall a version of Anchor")]
Uninstall {
#[clap(parse(try_from_str = parse_version))]
version: Version,
},
#[clap(about = "List available versions of Anchor")]
List {},
}
// If `latest` is passed use the latest available version.
fn parse_version(version: &str) -> Result<Version, Error> {
if version == "latest" {
Ok(avm::get_latest_version())
} else {
Version::parse(version).map_err(|e| anyhow::anyhow!(e))
}
}
pub fn entry(opts: Cli) -> Result<()> {
match opts.command {
Commands::Use { version } => avm::use_version(&version),
Commands::Install { version } => avm::install_version(&version),
Commands::Uninstall { version } => avm::uninstall_version(&version),
Commands::List {} => avm::list_versions(),
}
}
fn main() -> Result<()> {
// Make sure the user's home directory is setup with the paths required by AVM.
avm::ensure_paths();
let opt = Cli::parse();
entry(opt)
}