diff --git a/doc/user-guide/src/environment-variables.md b/doc/user-guide/src/environment-variables.md index 559fe4ad1f..5253ac195d 100644 --- a/doc/user-guide/src/environment-variables.md +++ b/doc/user-guide/src/environment-variables.md @@ -25,6 +25,9 @@ - `RUSTUP_UPDATE_ROOT` (default `https://static.rust-lang.org/rustup`). Sets the root URL for downloading self-update. +- `RUSTUP_VERSION` (default: none). Overrides the rustup version (e.g. `1.27.1`) + to be downloaded when executing `rustup-init.sh` or `rustup self update`. + - `RUSTUP_IO_THREADS` *unstable* (defaults to reported cpu count). Sets the number of threads to perform close IO in. Set to `1` to force single-threaded IO for troubleshooting, or an arbitrary number to override diff --git a/rustup-init.sh b/rustup-init.sh index 52f2b9e1f5..8b69c9e086 100755 --- a/rustup-init.sh +++ b/rustup-init.sh @@ -87,7 +87,15 @@ main() { ;; esac - local _url="${RUSTUP_UPDATE_ROOT}/dist/${_arch}/rustup-init${_ext}" + local _url + if [ "${RUSTUP_VERSION+set}" = 'set' ]; then + say "\`RUSTUP_VERSION\` has been set to \`${RUSTUP_VERSION}\`" + _url="${RUSTUP_UPDATE_ROOT}/archive/${RUSTUP_VERSION}" + else + _url="${RUSTUP_UPDATE_ROOT}/dist" + fi + _url="${_url}/${_arch}/rustup-init${_ext}" + local _dir if ! _dir="$(ensure mktemp -d)"; then diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 42741a91aa..62bd638f27 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -55,7 +55,7 @@ use crate::{ errors::*, markdown::md, }, - config::Cfg, + config::{Cfg, non_empty_env_var}, dist::{self, PartialToolchainDesc, Profile, TargetTriple, ToolchainDesc}, errors::RustupError, install::UpdateStatus, @@ -1104,7 +1104,12 @@ pub(crate) async fn prepare_update(process: &Process) -> Result> // Get available version info!("checking for self-update"); - let available_version = get_available_rustup_version(process).await?; + let available_version = if let Some(ver) = non_empty_env_var("RUSTUP_VERSION", process)? { + info!("`RUSTUP_VERSION` has been set to `{ver}`"); + ver + } else { + get_available_rustup_version(process).await? + }; // If up-to-date if available_version == current_version { diff --git a/src/config.rs b/src/config.rs index 68ad62b0f8..f4b4471bd0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1027,22 +1027,25 @@ impl<'a> Cfg<'a> { } } +/// The root path of the release server, without the `/dist` suffix. +/// By default, it points to [`dist::DEFAULT_DIST_SERVER`]. pub(crate) fn dist_root_server(process: &Process) -> Result { - Ok(match non_empty_env_var("RUSTUP_DIST_SERVER", process)? { - Some(s) => { + Ok( + if let Some(s) = non_empty_env_var("RUSTUP_DIST_SERVER", process)? { trace!("`RUSTUP_DIST_SERVER` has been set to `{s}`"); s } - None => { - // For backward compatibility - non_empty_env_var("RUSTUP_DIST_ROOT", process)? - .inspect(|url| trace!("`RUSTUP_DIST_ROOT` has been set to `{url}`")) - .as_ref() - .map(|root| root.trim_end_matches("/dist")) - .unwrap_or(dist::DEFAULT_DIST_SERVER) - .to_owned() - } - }) + // For backwards compatibility + else if let Some(mut root) = non_empty_env_var("RUSTUP_DIST_ROOT", process)? { + trace!("`RUSTUP_DIST_ROOT` has been set to `{root}`"); + if let Some(stripped) = root.strip_suffix("/dist") { + root.truncate(stripped.len()); + } + root + } else { + dist::DEFAULT_DIST_SERVER.to_owned() + }, + ) } impl Debug for Cfg<'_> { @@ -1088,7 +1091,7 @@ fn get_default_host_triple(s: &Settings, process: &Process) -> TargetTriple { .unwrap_or_else(|| TargetTriple::from_host_or_build(process)) } -fn non_empty_env_var(name: &str, process: &Process) -> anyhow::Result> { +pub(crate) fn non_empty_env_var(name: &str, process: &Process) -> anyhow::Result> { match process.var(name) { Ok(s) if !s.is_empty() => Ok(Some(s)), Ok(_) => Ok(None), diff --git a/src/test/clitools.rs b/src/test/clitools.rs index 4a05603ac3..0cc5f69dc6 100644 --- a/src/test/clitools.rs +++ b/src/test/clitools.rs @@ -220,7 +220,19 @@ impl Config { /// Expect an exact strings on stdout/stderr with an ok status code pub async fn expect_ok_ex(&mut self, args: &[&str], stdout: &str, stderr: &str) { - let out = self.run(args[0], &args[1..], &[]).await; + self.expect_ok_ex_env(args, &[], stdout, stderr).await; + } + + /// Expect an exact strings on stdout/stderr with an ok status code, + /// with extra environment variables + pub async fn expect_ok_ex_env( + &mut self, + args: &[&str], + env: &[(&str, &str)], + stdout: &str, + stderr: &str, + ) { + let out = self.run(args[0], &args[1..], env).await; if !out.ok || out.stdout != stdout || out.stderr != stderr { print_command(args, &out); println!("expected.ok: true"); diff --git a/tests/suite/cli_self_upd.rs b/tests/suite/cli_self_upd.rs index 1d0e820681..b3cd21a0e9 100644 --- a/tests/suite/cli_self_upd.rs +++ b/tests/suite/cli_self_upd.rs @@ -385,6 +385,30 @@ info: downloading self-update .await; } +#[tokio::test] +async fn update_precise() { + let version = env!("CARGO_PKG_VERSION"); + let expected_output = format!( + "info: checking for self-update +info: `RUSTUP_VERSION` has been set to `{TEST_VERSION}` +info: downloading self-update +" + ); + + let mut cx = SelfUpdateTestContext::new(TEST_VERSION).await; + cx.config + .expect_ok(&["rustup-init", "-y", "--no-modify-path"]) + .await; + cx.config + .expect_ok_ex_env( + &["rustup", "self", "update"], + &[("RUSTUP_VERSION", TEST_VERSION)], + &format!(" rustup updated - {version} (from {version})\n\n",), + &expected_output, + ) + .await; +} + #[cfg(windows)] #[tokio::test] async fn update_overwrites_programs_display_version() {