Skip to content

Commit 6ff6655

Browse files
avm: Make installation download binaries by default (otter-sec#3445)
1 parent 6df05aa commit 6ff6655

3 files changed

Lines changed: 111 additions & 71 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ The minor version will be incremented upon a breaking change and the patch versi
6262
- cli: Add test template for [Mollusk](https://github.com/buffalojoec/mollusk) ([#3352](https://github.com/coral-xyz/anchor/pull/3352)).
6363
- idl: Disallow account discriminators that can conflict with the `zero` constraint ([#3365](https://github.com/coral-xyz/anchor/pull/3365)).
6464
- 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)).
65+
- avm: Make installation download binaries by default ([#3445](https://github.com/coral-xyz/anchor/pull/3445)).
6566

6667
### Fixes
6768

avm/src/lib.rs

Lines changed: 105 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub fn use_version(opt_version: Option<Version>) -> Result<()> {
8787
.next()
8888
.expect("Expected input")?;
8989
match input.as_str() {
90-
"y" | "yes" => return install_version(InstallTarget::Version(version), false),
90+
"y" | "yes" => return install_version(InstallTarget::Version(version), false, false),
9191
_ => return Err(anyhow!("Installation rejected.")),
9292
};
9393
}
@@ -107,7 +107,7 @@ pub enum InstallTarget {
107107
/// Update to the latest version
108108
pub fn update() -> Result<()> {
109109
let latest_version = get_latest_version()?;
110-
install_version(InstallTarget::Version(latest_version), false)
110+
install_version(InstallTarget::Version(latest_version), false, false)
111111
}
112112

113113
/// The commit sha provided can be shortened,
@@ -165,84 +165,119 @@ fn get_anchor_version_from_commit(commit: &str) -> Result<Version> {
165165
}
166166

167167
/// Install a version of anchor-cli
168-
pub fn install_version(install_target: InstallTarget, force: bool) -> Result<()> {
169-
let mut args: Vec<String> = vec![
170-
"install".into(),
171-
"--git".into(),
172-
"https://github.com/coral-xyz/anchor".into(),
173-
"anchor-cli".into(),
174-
"--locked".into(),
175-
"--root".into(),
176-
AVM_HOME.to_str().unwrap().into(),
177-
];
178-
let version = match install_target {
179-
InstallTarget::Version(version) => {
180-
args.extend(["--tag".into(), format!("v{}", version), "anchor-cli".into()]);
181-
version
182-
}
183-
InstallTarget::Commit(commit) => {
184-
args.extend(["--rev".into(), commit.clone()]);
185-
get_anchor_version_from_commit(&commit)?
186-
}
168+
pub fn install_version(
169+
install_target: InstallTarget,
170+
force: bool,
171+
from_source: bool,
172+
) -> Result<()> {
173+
let version = match &install_target {
174+
InstallTarget::Version(version) => version.to_owned(),
175+
InstallTarget::Commit(commit) => get_anchor_version_from_commit(commit)?,
187176
};
188-
189-
// If version is already installed we ignore the request.
190-
let installed_versions = read_installed_versions()?;
191-
if installed_versions.contains(&version) && !force {
192-
println!("Version {version} is already installed");
177+
// Return early if version is already installed
178+
if !force && read_installed_versions()?.contains(&version) {
179+
eprintln!("Version `{version}` is already installed");
193180
return Ok(());
194181
}
195182

196-
// If the version is older than v0.31, install using `rustc 1.79.0` to get around the problem
197-
// explained in https://github.com/coral-xyz/anchor/pull/3143
198-
if version < Version::parse("0.31.0")? {
199-
const REQUIRED_VERSION: &str = "1.79.0";
200-
let is_installed = Command::new("rustup")
201-
.args(["toolchain", "list"])
202-
.output()
203-
.map(|output| String::from_utf8(output.stdout))??
204-
.lines()
205-
.any(|line| line.starts_with(REQUIRED_VERSION));
206-
if !is_installed {
207-
let exit_status = Command::new("rustup")
208-
.args(["toolchain", "install", REQUIRED_VERSION])
209-
.spawn()?
210-
.wait()?;
211-
if !exit_status.success() {
212-
return Err(anyhow!(
213-
"Installation of `rustc {REQUIRED_VERSION}` failed. \
183+
let is_older_than_v0_31_0 = version < Version::parse("0.31.0")?;
184+
if from_source || is_older_than_v0_31_0 {
185+
// Build from source using `cargo install --git`
186+
let mut args: Vec<String> = vec![
187+
"install".into(),
188+
"anchor-cli".into(),
189+
"--git".into(),
190+
"https://github.com/coral-xyz/anchor".into(),
191+
"--locked".into(),
192+
"--root".into(),
193+
AVM_HOME.to_str().unwrap().into(),
194+
];
195+
let conditional_args = match install_target {
196+
InstallTarget::Version(version) => ["--tag".into(), format!("v{}", version)],
197+
InstallTarget::Commit(commit) => ["--rev".into(), commit],
198+
};
199+
args.extend_from_slice(&conditional_args);
200+
201+
// If the version is older than v0.31, install using `rustc 1.79.0` to get around the problem
202+
// explained in https://github.com/coral-xyz/anchor/pull/3143
203+
if is_older_than_v0_31_0 {
204+
const REQUIRED_VERSION: &str = "1.79.0";
205+
let is_installed = Command::new("rustup")
206+
.args(["toolchain", "list"])
207+
.output()
208+
.map(|output| String::from_utf8(output.stdout))??
209+
.lines()
210+
.any(|line| line.starts_with(REQUIRED_VERSION));
211+
if !is_installed {
212+
let exit_status = Command::new("rustup")
213+
.args(["toolchain", "install", REQUIRED_VERSION])
214+
.spawn()?
215+
.wait()?;
216+
if !exit_status.success() {
217+
return Err(anyhow!(
218+
"Installation of `rustc {REQUIRED_VERSION}` failed. \
214219
`rustc <1.80` is required to install Anchor v{version} from source. \
215220
See https://github.com/coral-xyz/anchor/pull/3143 for more information."
216-
));
221+
));
222+
}
217223
}
218-
}
219224

220-
// Prepend the toolchain to use with the `cargo install` command
221-
args.insert(0, format!("+{REQUIRED_VERSION}"));
222-
}
225+
// Prepend the toolchain to use with the `cargo install` command
226+
args.insert(0, format!("+{REQUIRED_VERSION}"));
227+
}
223228

224-
let output = Command::new("cargo")
225-
.args(args)
226-
.stdout(Stdio::inherit())
227-
.stderr(Stdio::inherit())
228-
.output()
229-
.map_err(|e| anyhow!("Cargo install for {version} failed: {e}"))?;
230-
if !output.status.success() {
231-
return Err(anyhow!(
232-
"Failed to install {version}, is it a valid version?"
233-
));
234-
}
229+
let output = Command::new("cargo")
230+
.args(args)
231+
.stdout(Stdio::inherit())
232+
.stderr(Stdio::inherit())
233+
.output()
234+
.map_err(|e| anyhow!("`cargo install` for version `{version}` failed: {e}"))?;
235+
if !output.status.success() {
236+
return Err(anyhow!(
237+
"Failed to install {version}, is it a valid version?"
238+
));
239+
}
235240

236-
let bin_dir = get_bin_dir_path();
237-
let bin_name = if cfg!(target_os = "windows") {
238-
"anchor.exe"
241+
let bin_dir = get_bin_dir_path();
242+
let bin_name = if cfg!(target_os = "windows") {
243+
"anchor.exe"
244+
} else {
245+
"anchor"
246+
};
247+
fs::rename(bin_dir.join(bin_name), version_binary_path(&version))?;
239248
} else {
240-
"anchor"
241-
};
242-
fs::rename(
243-
bin_dir.join(bin_name),
244-
bin_dir.join(format!("anchor-{version}")),
245-
)?;
249+
let output = Command::new("rustc").arg("-vV").output()?;
250+
let target = core::str::from_utf8(&output.stdout)?
251+
.lines()
252+
.find(|line| line.starts_with("host:"))
253+
.and_then(|line| line.split(':').last())
254+
.ok_or_else(|| anyhow!("`host` not found from `rustc -vV` output"))?
255+
.trim();
256+
let ext = if cfg!(target_os = "windows") {
257+
".exe"
258+
} else {
259+
""
260+
};
261+
let res = reqwest::blocking::get(format!(
262+
"https://github.com/coral-xyz/anchor/releases/download/v{version}/anchor-{version}-{target}{ext}"
263+
))?;
264+
if !res.status().is_success() {
265+
return Err(anyhow!(
266+
"Failed to download the binary for version `{version}` (status code: {})",
267+
res.status()
268+
));
269+
}
270+
271+
let bin_path = version_binary_path(&version);
272+
fs::write(&bin_path, res.bytes()?)?;
273+
274+
// Set file to executable on UNIX
275+
#[cfg(not(target_os = "windows"))]
276+
fs::set_permissions(
277+
bin_path,
278+
<fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o775),
279+
)?;
280+
}
246281

247282
// If .version file is empty or not parseable, write the newly installed version to it
248283
if current_version().is_err() {
@@ -255,7 +290,7 @@ pub fn install_version(install_target: InstallTarget, force: bool) -> Result<()>
255290

256291
/// Remove an installed version of anchor-cli
257292
pub fn uninstall_version(version: &Version) -> Result<()> {
258-
let version_path = get_bin_dir_path().join(format!("anchor-{version}"));
293+
let version_path = version_binary_path(version);
259294
if !version_path.exists() {
260295
return Err(anyhow!("anchor-cli {} is not installed", version));
261296
}

avm/src/main.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ pub enum Commands {
2626
/// Flag to force installation even if the version
2727
/// is already installed
2828
force: bool,
29+
#[clap(long)]
30+
/// Build from source code rather than downloading prebuilt binaries
31+
from_source: bool,
2932
},
3033
#[clap(about = "Uninstall a version of Anchor")]
3134
Uninstall {
@@ -77,7 +80,8 @@ pub fn entry(opts: Cli) -> Result<()> {
7780
Commands::Install {
7881
version_or_commit,
7982
force,
80-
} => avm::install_version(version_or_commit, force),
83+
from_source,
84+
} => avm::install_version(version_or_commit, force, from_source),
8185
Commands::Uninstall { version } => avm::uninstall_version(&version),
8286
Commands::List {} => avm::list_versions(),
8387
Commands::Update {} => avm::update(),

0 commit comments

Comments
 (0)