Skip to content

Commit 107d8e5

Browse files
committed
Teach rustup to override the toolchain from a version file
This adds a .rust-version file similar to rbenv's .ruby-version. The form of toolchain supported here is limited to channel names, explicit versions, and optional archive dates.
1 parent 2d8e6c0 commit 107d8e5

File tree

5 files changed

+402
-42
lines changed

5 files changed

+402
-42
lines changed

README.md

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ 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+
* [Directory overrides](#directory-overrides)
22+
* [The version file](#the-version-file)
23+
* [Toolchain override shorthand](#toolchain-override-shorthand)
2224
* [Cross-compilation](#cross-compilation)
2325
* [Working with Rust on Windows](#working-with-rust-on-windows)
2426
* [Working with custom toolchains](#working-with-custom-toolchains-and-local-builds)
@@ -231,29 +233,6 @@ info: downloading self-updates
231233
232234
```
233235

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-
257236
## Toolchain specification
258237

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

281+
## Toolchain override shorthand
282+
283+
The `rustup` toolchain proxies can be instructed directly to use a
284+
specific toolchain, a convience for developers who often test
285+
different toolchains. If the first argument to `cargo`, `rustc` or
286+
other tools in the toolchain begins with `+`, it will be interpreted
287+
as a rustup toolchain name, and that toolchain will be preferred,
288+
as in
289+
290+
```
291+
cargo +beta test
292+
```
293+
294+
## Directory overrides
295+
296+
Directories can be assigned their own Rust toolchain with
297+
`rustup override`. When a directory has an override then
298+
any time `rustc` or `cargo` is run inside that directory,
299+
or one of its child directories, the override toolchain
300+
will be invoked.
301+
302+
To pin to a specific nightly:
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 version 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-version`, 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+
The version file has lower precedence than all other methods of
338+
specifying the toolchain.
339+
302340
## Cross-compilation
303341

304342
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: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,19 @@ use settings::{TelemetryMode, SettingsFile, DEFAULT_METADATA_VERSION};
1818
pub enum OverrideReason {
1919
Environment,
2020
OverrideDB(PathBuf),
21+
VersionFile(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::VersionFile(ref path) => {
32+
write!(f, "overridden by '{}'", path.display())
33+
}
3234
}
3335
}
3436
}
@@ -223,29 +225,86 @@ 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 look in the override database
236+
if override_.is_none() {
237+
try!(self.settings_file.with(|s| {
238+
let name = s.find_override(path, self.notify_handler.as_ref());
239+
override_ = name.map(|(name, reason_path)| (name, OverrideReason::OverrideDB(reason_path)));
240+
241+
Ok(())
242+
}));
243+
}
228244

229-
return Ok(Some((toolchain, OverrideReason::Environment)));
245+
// Then check the explicit version file
246+
if override_.is_none() {
247+
let name_path = self.find_override_version_file(path)?;
248+
override_ = name_path.map(|(name, path)| (name, OverrideReason::VersionFile(path)));
230249
}
231250

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 },
251+
if let Some((name, reason)) = override_ {
252+
// This is hackishly using the error chain to provide a bit of
253+
// extra context about what went wrong. The CLI will display it
254+
// on a line after the proximate error.
255+
256+
let reason_err = match reason {
257+
OverrideReason::Environment => {
258+
format!("the RUSTUP_TOOLCHAIN environment variable specifies an uninstalled toolchain")
259+
}
260+
OverrideReason::OverrideDB(ref path) => {
261+
format!("the directory override for '{}' specifies an uninstalled toolchain", path.display())
262+
}
263+
OverrideReason::VersionFile(ref path) => {
264+
format!("the version file at '{}' specifies an uninstalled toolchain", path.display())
265+
}
266+
};
267+
268+
match self.verify_toolchain(&name) {
269+
Ok(t) => {
270+
Ok(Some((t, reason)))
271+
}
238272
Err(Error(ErrorKind::Utils(::rustup_utils::ErrorKind::NotADirectory { .. }), _)) => {
239273
// Strip the confusing NotADirectory error and only mention that the override
240274
// toolchain is not installed.
241-
return Err(ErrorKind::OverrideToolchainNotInstalled(name.to_string()).into())
275+
Err(Error::from(reason_err))
276+
.chain_err(|| ErrorKind::OverrideToolchainNotInstalled(name.to_string()))
242277
},
243-
Err(e) => return Err(e).chain_err(|| {
244-
ErrorKind::OverrideToolchainNotInstalled(name.to_string())
245-
})
278+
Err(e) => {
279+
Err(e)
280+
.chain_err(|| Error::from(reason_err))
281+
.chain_err(|| ErrorKind::OverrideToolchainNotInstalled(name.to_string()))
282+
}
283+
}
284+
} else {
285+
Ok(None)
286+
}
287+
}
246288

247-
};
248-
return Ok(Some((toolchain, OverrideReason::OverrideDB(reason_path))));
289+
/// Starting in path walk up the tree looking for .rust-version
290+
fn find_override_version_file(&self, path: &Path) -> Result<Option<(String, PathBuf)>> {
291+
let mut path = Some(path);
292+
293+
while let Some(p) = path {
294+
let version_file = p.join(".rust-version");
295+
if let Ok(s) = utils::read_file("version file", &version_file) {
296+
if let Some(s) = s.lines().next() {
297+
let toolchain_name = s.trim();
298+
dist::validate_channel_name(&toolchain_name)
299+
.chain_err(|| format!("invalid channel name '{}' in '{}'",
300+
toolchain_name,
301+
version_file.display()))?;
302+
303+
return Ok(Some((toolchain_name.to_string(), version_file)));
304+
}
305+
}
306+
307+
path = p.parent();
249308
}
250309

251310
Ok(None)

0 commit comments

Comments
 (0)