Skip to content

Commit c2ff689

Browse files
authored
Merge pull request #1172 from brson/version-file
Teach rustup to override the toolchain from a version file
2 parents e2dfde2 + 9e1f621 commit c2ff689

File tree

6 files changed

+459
-58
lines changed

6 files changed

+459
-58
lines changed

README.md

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ And it runs on all platforms Rust supports, including Windows.
1717
* [How rustup works](#how-rustup-works)
1818
* [Keeping Rust up to date](#keeping-rust-up-to-date)
1919
* [Working with nightly Rust](#working-with-nightly-rust)
20-
* [Directory overrides](#directory-overrides)
2120
* [Toolchain specification](#toolchain-specification)
21+
* [Toolchain override shorthand](#toolchain-override-shorthand)
22+
* [Directory overrides](#directory-overrides)
23+
* [The toolchain file](#the-toolchain-file)
24+
* [Override precedence](#override-precedence)
2225
* [Cross-compilation](#cross-compilation)
2326
* [Working with Rust on Windows](#working-with-rust-on-windows)
2427
* [Working with custom toolchains](#working-with-custom-toolchains-and-local-builds)
@@ -231,29 +234,6 @@ info: downloading self-updates
231234
232235
```
233236

234-
## Directory overrides
235-
236-
Directories can be assigned their own Rust toolchain with
237-
`rustup override`. When a directory has an override then
238-
any time `rustc` or `cargo` is run inside that directory,
239-
or one of its child directories, the override toolchain
240-
will be invoked.
241-
242-
To pin to a specific nightly:
243-
244-
```
245-
rustup override set nightly-2014-12-18
246-
```
247-
248-
Or a specific stable release:
249-
250-
```
251-
rustup override set 1.0.0
252-
```
253-
254-
To see the active toolchain use `rustup show`. To remove the override
255-
and use the default toolchain again, `rustup override unset`.
256-
257237
## Toolchain specification
258238

259239
Many `rustup` commands deal with *toolchains*, a single installation
@@ -299,6 +279,83 @@ Toolchain names that don't name a channel instead can be used to name
299279
[MSVC-based toolchain]: https://www.rust-lang.org/downloads.html#win-foot
300280
[custom toolchains]: #working-with-custom-toolchains-and-local-builds
301281

282+
## Toolchain override shorthand
283+
284+
The `rustup` toolchain proxies can be instructed directly to use a
285+
specific toolchain, a convience for developers who often test
286+
different toolchains. If the first argument to `cargo`, `rustc` or
287+
other tools in the toolchain begins with `+`, it will be interpreted
288+
as a rustup toolchain name, and that toolchain will be preferred,
289+
as in
290+
291+
```
292+
cargo +beta test
293+
```
294+
295+
## Directory overrides
296+
297+
Directories can be assigned their own Rust toolchain with `rustup
298+
override`. When a directory has an override then any time `rustc` or
299+
`cargo` is run inside that directory, or one of its child directories,
300+
the override toolchain will be invoked.
301+
302+
To use to a specific nightly for a directory:
303+
304+
```
305+
rustup override set nightly-2014-12-18
306+
```
307+
308+
Or a specific stable release:
309+
310+
```
311+
rustup override set 1.0.0
312+
```
313+
314+
To see the active toolchain use `rustup show`. To remove the override
315+
and use the default toolchain again, `rustup override unset`.
316+
317+
## The toolchain file
318+
319+
`rustup` directory overrides are a local configuration, stored in
320+
`$RUSTUP_HOME`. Some projects though find themselves 'pinned' to a
321+
specific release of Rust and want this information reflected in their
322+
source repository. This is most often the case for nightly-only
323+
software that pins to a revision from the release archives.
324+
325+
In these cases the toolchain can be named in the project's directory
326+
in a file called `rust-toolchain`, the content of which is the name of
327+
a single `rustup` toolchain, and which is suitable to check in to
328+
source control.
329+
330+
The toolchains named in this file have a more restricted form than
331+
rustup toolchains generally, and may only contain the names of the
332+
three release channels, 'stable', 'beta', 'nightly', Rust version
333+
numbers, like '1.0.0', and optionally an archive date, like
334+
'nightly-2017-01-01'. They may not name custom toolchains, nor
335+
host-specific toolchains.
336+
337+
## Override precedence
338+
339+
There are several ways to specify which toolchain `rustup` should
340+
execute:
341+
342+
* An explicit toolchain, e.g. `cargo +beta`,
343+
* The `RUSTUP_TOOLCHAIN` environment variable,
344+
* A directory override, ala `rustup override set beta`,
345+
* The `rust-toolchain` file,
346+
* The default toolchain,
347+
348+
and they are prefered by rustup in that order, with the explicit
349+
toolchain having highest precedence, and the default toolchain having
350+
the lowest. There is one exception though: directory overrides and the
351+
`rust-toolchain` file are also preferred by their proximity to the
352+
current directory. That is, these two override methods are discovered
353+
by walking up the directory tree toward the filesystem root, and a
354+
`rust-toolchain` file that is closer to the current directory will be
355+
prefered over a directory override that is further away.
356+
357+
To verify which toolchain is active use `rustup show`.
358+
302359
## Cross-compilation
303360

304361
Rust [supports a great number of platforms][p]. For many of these

src/rustup-dist/src/dist.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ impl PartialToolchainDesc {
302302
target: TargetTriple(trip),
303303
}
304304
}
305+
306+
pub fn has_triple(&self) -> bool {
307+
self.target.arch.is_some() || self.target.os.is_some() || self.target.env.is_some()
308+
}
305309
}
306310

307311
impl ToolchainDesc {
@@ -376,6 +380,16 @@ impl ToolchainDesc {
376380
}
377381
}
378382

383+
// A little convenience for just parsing a channel name or archived channel name
384+
pub fn validate_channel_name(name: &str) -> Result<()> {
385+
let toolchain = PartialToolchainDesc::from_str(&name)?;
386+
if toolchain.has_triple() {
387+
Err(format!("target triple in channel name '{}'", name).into())
388+
} else {
389+
Ok(())
390+
}
391+
}
392+
379393
#[derive(Debug)]
380394
pub struct Manifest<'a>(temp::File<'a>, String);
381395

src/rustup-mock/src/clitools.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ pub struct Config {
3434
pub homedir: PathBuf,
3535
/// An empty directory. Tests should not write to this.
3636
pub emptydir: PathBuf,
37+
/// This is cwd for the test
38+
pub workdir: PathBuf,
3739
}
3840

3941
// Describes all the features of the mock dist server.
@@ -72,6 +74,7 @@ pub fn setup(s: Scenario, f: &Fn(&Config)) {
7274
let cargodir = TempDir::new("rustup-cargo").unwrap();
7375
let homedir = TempDir::new("rustup-home").unwrap();
7476
let emptydir = TempDir::new("rustup-empty").unwrap();
77+
let workdir = TempDir::new("rustup-workdir").unwrap();
7578

7679
// The uninstall process on windows involves using the directory above
7780
// CARGO_HOME, so make sure it's a subdir of our tempdir
@@ -86,6 +89,7 @@ pub fn setup(s: Scenario, f: &Fn(&Config)) {
8689
cargodir: cargodir,
8790
homedir: homedir.path().to_owned(),
8891
emptydir: emptydir.path().to_owned(),
92+
workdir: workdir.path().to_owned(),
8993
};
9094

9195
create_mock_dist_server(&config.distdir, s);
@@ -139,6 +143,11 @@ pub fn setup(s: Scenario, f: &Fn(&Config)) {
139143
}
140144
let _g = LOCK.lock();
141145

146+
// Change the cwd to a test-specific directory
147+
let cwd = env::current_dir().unwrap();
148+
env::set_current_dir(&config.workdir).unwrap();
149+
let _g = scopeguard::guard(cwd, |d| env::set_current_dir(d).unwrap());
150+
142151
f(config);
143152

144153
// These are the bogus values the test harness sets "HOME" and "CARGO_HOME"

src/rustup/config.rs

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,25 @@ use rustup_dist::{temp, dist};
1212
use rustup_utils::utils;
1313
use toolchain::{Toolchain, UpdateStatus};
1414
use telemetry_analysis::*;
15-
use settings::{TelemetryMode, SettingsFile, DEFAULT_METADATA_VERSION};
15+
use settings::{TelemetryMode, SettingsFile, Settings, DEFAULT_METADATA_VERSION};
1616

1717
#[derive(Debug)]
1818
pub enum OverrideReason {
1919
Environment,
2020
OverrideDB(PathBuf),
21+
ToolchainFile(PathBuf),
2122
}
2223

23-
24-
2524
impl Display for OverrideReason {
2625
fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
2726
match *self {
2827
OverrideReason::Environment => write!(f, "environment override by RUSTUP_TOOLCHAIN"),
2928
OverrideReason::OverrideDB(ref path) => {
3029
write!(f, "directory override for '{}'", path.display())
3130
}
31+
OverrideReason::ToolchainFile(ref path) => {
32+
write!(f, "overridden by '{}'", path.display())
33+
}
3234
}
3335
}
3436
}
@@ -223,29 +225,91 @@ impl Cfg {
223225
}
224226

225227
pub fn find_override(&self, path: &Path) -> Result<Option<(Toolchain, OverrideReason)>> {
228+
let mut override_ = None;
229+
230+
// First check RUSTUP_TOOLCHAIN
226231
if let Some(ref name) = self.env_override {
227-
let toolchain = try!(self.verify_toolchain(name).chain_err(|| ErrorKind::ToolchainNotInstalled(name.to_string())));
232+
override_ = Some((name.to_string(), OverrideReason::Environment));
233+
}
234+
235+
// Then walk up the directory tree from 'path' looking for either the
236+
// directory in override database, or a `rust-toolchain` file.
237+
if override_.is_none() {
238+
self.settings_file.with(|s| {
239+
override_ = self.find_override_from_dir_walk(path, s)?;
228240

229-
return Ok(Some((toolchain, OverrideReason::Environment)));
241+
Ok(())
242+
})?;
230243
}
231244

232-
let result = try!(self.settings_file.with(|s| {
233-
Ok(s.find_override(path, self.notify_handler.as_ref()))
234-
}));
235-
if let Some((name, reason_path)) = result {
236-
let toolchain = match self.verify_toolchain(&name) {
237-
Ok(t) => { t },
245+
if let Some((name, reason)) = override_ {
246+
// This is hackishly using the error chain to provide a bit of
247+
// extra context about what went wrong. The CLI will display it
248+
// on a line after the proximate error.
249+
250+
let reason_err = match reason {
251+
OverrideReason::Environment => {
252+
format!("the RUSTUP_TOOLCHAIN environment variable specifies an uninstalled toolchain")
253+
}
254+
OverrideReason::OverrideDB(ref path) => {
255+
format!("the directory override for '{}' specifies an uninstalled toolchain", path.display())
256+
}
257+
OverrideReason::ToolchainFile(ref path) => {
258+
format!("the toolchain file at '{}' specifies an uninstalled toolchain", path.display())
259+
}
260+
};
261+
262+
match self.verify_toolchain(&name) {
263+
Ok(t) => {
264+
Ok(Some((t, reason)))
265+
}
238266
Err(Error(ErrorKind::Utils(::rustup_utils::ErrorKind::NotADirectory { .. }), _)) => {
239267
// Strip the confusing NotADirectory error and only mention that the override
240268
// toolchain is not installed.
241-
return Err(ErrorKind::OverrideToolchainNotInstalled(name.to_string()).into())
269+
Err(Error::from(reason_err))
270+
.chain_err(|| ErrorKind::OverrideToolchainNotInstalled(name.to_string()))
242271
},
243-
Err(e) => return Err(e).chain_err(|| {
244-
ErrorKind::OverrideToolchainNotInstalled(name.to_string())
245-
})
272+
Err(e) => {
273+
Err(e)
274+
.chain_err(|| Error::from(reason_err))
275+
.chain_err(|| ErrorKind::OverrideToolchainNotInstalled(name.to_string()))
276+
}
277+
}
278+
} else {
279+
Ok(None)
280+
}
281+
}
246282

247-
};
248-
return Ok(Some((toolchain, OverrideReason::OverrideDB(reason_path))));
283+
fn find_override_from_dir_walk(&self, dir: &Path, settings: &Settings)
284+
-> Result<Option<(String, OverrideReason)>>
285+
{
286+
let notify = self.notify_handler.as_ref();
287+
let dir = utils::canonicalize_path(dir, &|n| notify(n.into()));
288+
let mut dir = Some(&*dir);
289+
290+
while let Some(d) = dir {
291+
// First check the override database
292+
if let Some(name) = settings.dir_override(d, notify) {
293+
let reason = OverrideReason::OverrideDB(d.to_owned());
294+
return Ok(Some((name, reason)));
295+
}
296+
297+
// Then look for 'rust-toolchain'
298+
let toolchain_file = d.join("rust-toolchain");
299+
if let Ok(s) = utils::read_file("toolchain file", &toolchain_file) {
300+
if let Some(s) = s.lines().next() {
301+
let toolchain_name = s.trim();
302+
dist::validate_channel_name(&toolchain_name)
303+
.chain_err(|| format!("invalid channel name '{}' in '{}'",
304+
toolchain_name,
305+
toolchain_file.display()))?;
306+
307+
let reason = OverrideReason::ToolchainFile(toolchain_file);
308+
return Ok(Some((toolchain_name.to_string(), reason)));
309+
}
310+
}
311+
312+
dir = d.parent();
249313
}
250314

251315
Ok(None)

src/rustup/settings.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,18 +162,9 @@ impl Settings {
162162
self.overrides.insert(key, toolchain);
163163
}
164164

165-
pub fn find_override(&self, dir_unresolved: &Path, notify_handler: &Fn(Notification))
166-
-> Option<(String, PathBuf)> {
167-
let dir = utils::canonicalize_path(dir_unresolved, &|n| notify_handler(n.into()));
168-
let mut maybe_path = Some(&*dir);
169-
while let Some(path) = maybe_path {
170-
let key = Self::path_to_key(path, notify_handler);
171-
if let Some(toolchain) = self.overrides.get(&key) {
172-
return Some((toolchain.to_owned(), path.to_owned()));
173-
}
174-
maybe_path = path.parent();
175-
}
176-
None
165+
pub fn dir_override(&self, dir: &Path, notify_handler: &Fn(Notification)) -> Option<String> {
166+
let key = Self::path_to_key(dir, notify_handler);
167+
self.overrides.get(&key).map(|s| s.clone())
177168
}
178169

179170
pub fn parse(data: &str) -> Result<Self> {

0 commit comments

Comments
 (0)