diff --git a/CHANGELOG.md b/CHANGELOG.md index db2631b24a..081be7f9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ The minor version will be incremented upon a breaking change and the patch versi - cli: Add test template for [Mollusk](https://github.com/buffalojoec/mollusk) ([#3352](https://github.com/coral-xyz/anchor/pull/3352)). - idl: Disallow account discriminators that can conflict with the `zero` constraint ([#3365](https://github.com/coral-xyz/anchor/pull/3365)). - cli: Include recommended solana args by default and add new `--max-retries` option to the `deploy` command ([#3354](https://github.com/coral-xyz/anchor/pull/3354)). +- avm: Make installation download binaries by default ([#3445](https://github.com/coral-xyz/anchor/pull/3445)). ### Fixes diff --git a/avm/src/lib.rs b/avm/src/lib.rs index 5648d6336a..90fb834985 100644 --- a/avm/src/lib.rs +++ b/avm/src/lib.rs @@ -87,7 +87,7 @@ pub fn use_version(opt_version: Option) -> Result<()> { .next() .expect("Expected input")?; match input.as_str() { - "y" | "yes" => return install_version(InstallTarget::Version(version), false), + "y" | "yes" => return install_version(InstallTarget::Version(version), false, false), _ => return Err(anyhow!("Installation rejected.")), }; } @@ -107,7 +107,7 @@ pub enum InstallTarget { /// Update to the latest version pub fn update() -> Result<()> { let latest_version = get_latest_version()?; - install_version(InstallTarget::Version(latest_version), false) + install_version(InstallTarget::Version(latest_version), false, false) } /// The commit sha provided can be shortened, @@ -165,84 +165,119 @@ fn get_anchor_version_from_commit(commit: &str) -> Result { } /// Install a version of anchor-cli -pub fn install_version(install_target: InstallTarget, force: bool) -> Result<()> { - let mut args: Vec = 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)? - } +pub fn install_version( + install_target: InstallTarget, + force: bool, + from_source: bool, +) -> Result<()> { + let version = match &install_target { + InstallTarget::Version(version) => version.to_owned(), + InstallTarget::Commit(commit) => get_anchor_version_from_commit(commit)?, }; - - // If version is already installed we ignore the request. - let installed_versions = read_installed_versions()?; - if installed_versions.contains(&version) && !force { - println!("Version {version} is already installed"); + // Return early if version is already installed + if !force && read_installed_versions()?.contains(&version) { + eprintln!("Version `{version}` is already installed"); return Ok(()); } - // If the version is older than v0.31, install using `rustc 1.79.0` to get around the problem - // explained in https://github.com/coral-xyz/anchor/pull/3143 - if version < Version::parse("0.31.0")? { - const REQUIRED_VERSION: &str = "1.79.0"; - let is_installed = Command::new("rustup") - .args(["toolchain", "list"]) - .output() - .map(|output| String::from_utf8(output.stdout))?? - .lines() - .any(|line| line.starts_with(REQUIRED_VERSION)); - if !is_installed { - let exit_status = Command::new("rustup") - .args(["toolchain", "install", REQUIRED_VERSION]) - .spawn()? - .wait()?; - if !exit_status.success() { - return Err(anyhow!( - "Installation of `rustc {REQUIRED_VERSION}` failed. \ + let is_older_than_v0_31_0 = version < Version::parse("0.31.0")?; + if from_source || is_older_than_v0_31_0 { + // Build from source using `cargo install --git` + let mut args: Vec = vec![ + "install".into(), + "anchor-cli".into(), + "--git".into(), + "https://github.com/coral-xyz/anchor".into(), + "--locked".into(), + "--root".into(), + AVM_HOME.to_str().unwrap().into(), + ]; + let conditional_args = match install_target { + InstallTarget::Version(version) => ["--tag".into(), format!("v{}", version)], + InstallTarget::Commit(commit) => ["--rev".into(), commit], + }; + args.extend_from_slice(&conditional_args); + + // If the version is older than v0.31, install using `rustc 1.79.0` to get around the problem + // explained in https://github.com/coral-xyz/anchor/pull/3143 + if is_older_than_v0_31_0 { + const REQUIRED_VERSION: &str = "1.79.0"; + let is_installed = Command::new("rustup") + .args(["toolchain", "list"]) + .output() + .map(|output| String::from_utf8(output.stdout))?? + .lines() + .any(|line| line.starts_with(REQUIRED_VERSION)); + if !is_installed { + let exit_status = Command::new("rustup") + .args(["toolchain", "install", REQUIRED_VERSION]) + .spawn()? + .wait()?; + if !exit_status.success() { + return Err(anyhow!( + "Installation of `rustc {REQUIRED_VERSION}` failed. \ `rustc <1.80` is required to install Anchor v{version} from source. \ See https://github.com/coral-xyz/anchor/pull/3143 for more information." - )); + )); + } } - } - // Prepend the toolchain to use with the `cargo install` command - args.insert(0, format!("+{REQUIRED_VERSION}")); - } + // Prepend the toolchain to use with the `cargo install` command + args.insert(0, format!("+{REQUIRED_VERSION}")); + } - let output = Command::new("cargo") - .args(args) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .output() - .map_err(|e| anyhow!("Cargo install for {version} failed: {e}"))?; - if !output.status.success() { - return Err(anyhow!( - "Failed to install {version}, is it a valid version?" - )); - } + let output = Command::new("cargo") + .args(args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .output() + .map_err(|e| anyhow!("`cargo install` for version `{version}` failed: {e}"))?; + if !output.status.success() { + return Err(anyhow!( + "Failed to install {version}, is it a valid version?" + )); + } - let bin_dir = get_bin_dir_path(); - let bin_name = if cfg!(target_os = "windows") { - "anchor.exe" + let bin_dir = get_bin_dir_path(); + let bin_name = if cfg!(target_os = "windows") { + "anchor.exe" + } else { + "anchor" + }; + fs::rename(bin_dir.join(bin_name), version_binary_path(&version))?; } else { - "anchor" - }; - fs::rename( - bin_dir.join(bin_name), - bin_dir.join(format!("anchor-{version}")), - )?; + let output = Command::new("rustc").arg("-vV").output()?; + let target = core::str::from_utf8(&output.stdout)? + .lines() + .find(|line| line.starts_with("host:")) + .and_then(|line| line.split(':').last()) + .ok_or_else(|| anyhow!("`host` not found from `rustc -vV` output"))? + .trim(); + let ext = if cfg!(target_os = "windows") { + ".exe" + } else { + "" + }; + let res = reqwest::blocking::get(format!( + "https://github.com/coral-xyz/anchor/releases/download/v{version}/anchor-{version}-{target}{ext}" + ))?; + if !res.status().is_success() { + return Err(anyhow!( + "Failed to download the binary for version `{version}` (status code: {})", + res.status() + )); + } + + let bin_path = version_binary_path(&version); + fs::write(&bin_path, res.bytes()?)?; + + // Set file to executable on UNIX + #[cfg(not(target_os = "windows"))] + fs::set_permissions( + bin_path, + ::from_mode(0o775), + )?; + } // If .version file is empty or not parseable, write the newly installed version to it if current_version().is_err() { @@ -255,7 +290,7 @@ pub fn install_version(install_target: InstallTarget, force: bool) -> Result<()> /// Remove an installed version of anchor-cli pub fn uninstall_version(version: &Version) -> Result<()> { - let version_path = get_bin_dir_path().join(format!("anchor-{version}")); + let version_path = version_binary_path(version); if !version_path.exists() { return Err(anyhow!("anchor-cli {} is not installed", version)); } diff --git a/avm/src/main.rs b/avm/src/main.rs index 943e8d4dad..a770405f75 100644 --- a/avm/src/main.rs +++ b/avm/src/main.rs @@ -26,6 +26,9 @@ pub enum Commands { /// Flag to force installation even if the version /// is already installed force: bool, + #[clap(long)] + /// Build from source code rather than downloading prebuilt binaries + from_source: bool, }, #[clap(about = "Uninstall a version of Anchor")] Uninstall { @@ -77,7 +80,8 @@ pub fn entry(opts: Cli) -> Result<()> { Commands::Install { version_or_commit, force, - } => avm::install_version(version_or_commit, force), + from_source, + } => avm::install_version(version_or_commit, force, from_source), Commands::Uninstall { version } => avm::uninstall_version(&version), Commands::List {} => avm::list_versions(), Commands::Update {} => avm::update(),