Skip to content

Teach rustup to override the toolchain from a version file #1172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 24, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 62 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ And it runs on all platforms Rust supports, including Windows.
* [How rustup works](#how-rustup-works)
* [Keeping Rust up to date](#keeping-rust-up-to-date)
* [Working with nightly Rust](#working-with-nightly-rust)
* [Directory overrides](#directory-overrides)
* [Toolchain specification](#toolchain-specification)
* [Directory overrides](#directory-overrides)
* [The version file](#the-version-file)
* [Toolchain override shorthand](#toolchain-override-shorthand)
* [Cross-compilation](#cross-compilation)
* [Working with Rust on Windows](#working-with-rust-on-windows)
* [Working with custom toolchains](#working-with-custom-toolchains-and-local-builds)
Expand Down Expand Up @@ -231,29 +233,6 @@ info: downloading self-updates

```

## Directory overrides

Directories can be assigned their own Rust toolchain with
`rustup override`. When a directory has an override then
any time `rustc` or `cargo` is run inside that directory,
or one of its child directories, the override toolchain
will be invoked.

To pin to a specific nightly:

```
rustup override set nightly-2014-12-18
```

Or a specific stable release:

```
rustup override set 1.0.0
```

To see the active toolchain use `rustup show`. To remove the override
and use the default toolchain again, `rustup override unset`.

## Toolchain specification

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

## Toolchain override shorthand

The `rustup` toolchain proxies can be instructed directly to use a
specific toolchain, a convience for developers who often test
different toolchains. If the first argument to `cargo`, `rustc` or
other tools in the toolchain begins with `+`, it will be interpreted
as a rustup toolchain name, and that toolchain will be preferred,
as in

```
cargo +beta test
```

## Directory overrides

Directories can be assigned their own Rust toolchain with
`rustup override`. When a directory has an override then
any time `rustc` or `cargo` is run inside that directory,
or one of its child directories, the override toolchain
will be invoked.

To pin to a specific nightly:

```
rustup override set nightly-2014-12-18
```

Or a specific stable release:

```
rustup override set 1.0.0
```

To see the active toolchain use `rustup show`. To remove the override
and use the default toolchain again, `rustup override unset`.

## The version file

`rustup` directory overrides are a local configuration, stored in
`$RUSTUP_HOME`. Some projects though find themselves 'pinned' to a
specific release of Rust and want this information reflected in their
source repository. This is most often the case for nightly-only
software that pins to a revision from the release archives.

In these cases the toolchain can be named in the project's directory
in a file called `.rust-version`, the content of which is the name of
a single `rustup` toolchain, and which is suitable to check in to
source control.

The toolchains named in this file have a more restricted form than
rustup toolchains generally, and may only contain the names of the
three release channels, 'stable', 'beta', 'nightly', Rust version
numbers, like '1.0.0', and optionally an archive date, like
'nightly-2017-01-01'. They may not name custom toolchains, nor
host-specific toolchains.

The version file has lower precedence than all other methods of
specifying the toolchain.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just clarifying, given that the file has lower precedence (i.e. it might not be obvious when it is not used), does rustup show work as expected?

Is it worth re-stating:

To see the active toolchain use rustup show.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, rustup show will indicate that .rust-version is in play. I will update the docs as you suggest.

## Cross-compilation

Rust [supports a great number of platforms][p]. For many of these
Expand Down
14 changes: 14 additions & 0 deletions src/rustup-dist/src/dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ impl PartialToolchainDesc {
target: TargetTriple(trip),
}
}

pub fn has_triple(&self) -> bool {
self.target.arch.is_some() || self.target.os.is_some() || self.target.env.is_some()
}
}

impl ToolchainDesc {
Expand Down Expand Up @@ -376,6 +380,16 @@ impl ToolchainDesc {
}
}

// A little convenience for just parsing a channel name or archived channel name
pub fn validate_channel_name(name: &str) -> Result<()> {
let toolchain = PartialToolchainDesc::from_str(&name)?;
if toolchain.has_triple() {
Err(format!("target triple in channel name '{}'", name).into())
} else {
Ok(())
}
}

#[derive(Debug)]
pub struct Manifest<'a>(temp::File<'a>, String);

Expand Down
9 changes: 9 additions & 0 deletions src/rustup-mock/src/clitools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub struct Config {
pub homedir: PathBuf,
/// An empty directory. Tests should not write to this.
pub emptydir: PathBuf,
/// This is cwd for the test
pub workdir: PathBuf,
}

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

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

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

// Change the cwd to a test-specific directory
let cwd = env::current_dir().unwrap();
env::set_current_dir(&config.workdir).unwrap();
let _g = scopeguard::guard(cwd, |d| env::set_current_dir(d).unwrap());

f(config);

// These are the bogus values the test harness sets "HOME" and "CARGO_HOME"
Expand Down
91 changes: 75 additions & 16 deletions src/rustup/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ use settings::{TelemetryMode, SettingsFile, DEFAULT_METADATA_VERSION};
pub enum OverrideReason {
Environment,
OverrideDB(PathBuf),
VersionFile(PathBuf),
}



impl Display for OverrideReason {
fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
match *self {
OverrideReason::Environment => write!(f, "environment override by RUSTUP_TOOLCHAIN"),
OverrideReason::OverrideDB(ref path) => {
write!(f, "directory override for '{}'", path.display())
}
OverrideReason::VersionFile(ref path) => {
write!(f, "overridden by '{}'", path.display())
}
}
}
}
Expand Down Expand Up @@ -223,29 +225,86 @@ impl Cfg {
}

pub fn find_override(&self, path: &Path) -> Result<Option<(Toolchain, OverrideReason)>> {
let mut override_ = None;

// First check RUSTUP_TOOLCHAIN
if let Some(ref name) = self.env_override {
let toolchain = try!(self.verify_toolchain(name).chain_err(|| ErrorKind::ToolchainNotInstalled(name.to_string())));
override_ = Some((name.to_string(), OverrideReason::Environment));
}

// Then look in the override database
if override_.is_none() {
try!(self.settings_file.with(|s| {
let name = s.find_override(path, self.notify_handler.as_ref());
override_ = name.map(|(name, reason_path)| (name, OverrideReason::OverrideDB(reason_path)));

Ok(())
}));
}

return Ok(Some((toolchain, OverrideReason::Environment)));
// Then check the explicit version file
if override_.is_none() {
let name_path = self.find_override_version_file(path)?;
override_ = name_path.map(|(name, path)| (name, OverrideReason::VersionFile(path)));
}

let result = try!(self.settings_file.with(|s| {
Ok(s.find_override(path, self.notify_handler.as_ref()))
}));
if let Some((name, reason_path)) = result {
let toolchain = match self.verify_toolchain(&name) {
Ok(t) => { t },
if let Some((name, reason)) = override_ {
// This is hackishly using the error chain to provide a bit of
// extra context about what went wrong. The CLI will display it
// on a line after the proximate error.

let reason_err = match reason {
OverrideReason::Environment => {
format!("the RUSTUP_TOOLCHAIN environment variable specifies an uninstalled toolchain")
}
OverrideReason::OverrideDB(ref path) => {
format!("the directory override for '{}' specifies an uninstalled toolchain", path.display())
}
OverrideReason::VersionFile(ref path) => {
format!("the version file at '{}' specifies an uninstalled toolchain", path.display())
}
};

match self.verify_toolchain(&name) {
Ok(t) => {
Ok(Some((t, reason)))
}
Err(Error(ErrorKind::Utils(::rustup_utils::ErrorKind::NotADirectory { .. }), _)) => {
// Strip the confusing NotADirectory error and only mention that the override
// toolchain is not installed.
return Err(ErrorKind::OverrideToolchainNotInstalled(name.to_string()).into())
Err(Error::from(reason_err))
.chain_err(|| ErrorKind::OverrideToolchainNotInstalled(name.to_string()))
},
Err(e) => return Err(e).chain_err(|| {
ErrorKind::OverrideToolchainNotInstalled(name.to_string())
})
Err(e) => {
Err(e)
.chain_err(|| Error::from(reason_err))
.chain_err(|| ErrorKind::OverrideToolchainNotInstalled(name.to_string()))
}
}
} else {
Ok(None)
}
}

};
return Ok(Some((toolchain, OverrideReason::OverrideDB(reason_path))));
/// Starting in path walk up the tree looking for .rust-version
fn find_override_version_file(&self, path: &Path) -> Result<Option<(String, PathBuf)>> {
let mut path = Some(path);

while let Some(p) = path {
let version_file = p.join(".rust-version");
if let Ok(s) = utils::read_file("version file", &version_file) {
if let Some(s) = s.lines().next() {
let toolchain_name = s.trim();
dist::validate_channel_name(&toolchain_name)
.chain_err(|| format!("invalid channel name '{}' in '{}'",
toolchain_name,
version_file.display()))?;

return Ok(Some((toolchain_name.to_string(), version_file)));
}
}

path = p.parent();
}

Ok(None)
Expand Down
Loading