Skip to content
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

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

Merged
merged 4 commits into from
Jun 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
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
105 changes: 81 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ 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)
* [Toolchain override shorthand](#toolchain-override-shorthand)
* [Directory overrides](#directory-overrides)
* [The toolchain file](#the-toolchain-file)
* [Override precedence](#override-precedence)
* [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 +234,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 +279,83 @@ 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 use to a specific nightly for a directory:

```
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 toolchain 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-toolchain`, 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.

## Override precedence

There are several ways to specify which toolchain `rustup` should
execute:

* An explicit toolchain, e.g. `cargo +beta`,
* The `RUSTUP_TOOLCHAIN` environment variable,
* A directory override, ala `rustup override set beta`,
* The `rust-toolchain` file,
* The default toolchain,

and they are prefered by rustup in that order, with the explicit
toolchain having highest precedence, and the default toolchain having
the lowest. There is one exception though: directory overrides and the
`rust-toolchain` file are also preferred by their proximity to the
current directory. That is, these two override methods are discovered
by walking up the directory tree toward the filesystem root, and a
`rust-toolchain` file that is closer to the current directory will be
prefered over a directory override that is further away.

To verify which toolchain is active use `rustup show`.

## 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
98 changes: 81 additions & 17 deletions src/rustup/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@ use rustup_dist::{temp, dist};
use rustup_utils::utils;
use toolchain::{Toolchain, UpdateStatus};
use telemetry_analysis::*;
use settings::{TelemetryMode, SettingsFile, DEFAULT_METADATA_VERSION};
use settings::{TelemetryMode, SettingsFile, Settings, DEFAULT_METADATA_VERSION};

#[derive(Debug)]
pub enum OverrideReason {
Environment,
OverrideDB(PathBuf),
ToolchainFile(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::ToolchainFile(ref path) => {
write!(f, "overridden by '{}'", path.display())
}
}
}
}
Expand Down Expand Up @@ -223,29 +225,91 @@ 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 walk up the directory tree from 'path' looking for either the
// directory in override database, or a `rust-toolchain` file.
if override_.is_none() {
self.settings_file.with(|s| {
override_ = self.find_override_from_dir_walk(path, s)?;

return Ok(Some((toolchain, OverrideReason::Environment)));
Ok(())
})?;
}

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::ToolchainFile(ref path) => {
format!("the toolchain 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))));
fn find_override_from_dir_walk(&self, dir: &Path, settings: &Settings)
-> Result<Option<(String, OverrideReason)>>
{
let notify = self.notify_handler.as_ref();
let dir = utils::canonicalize_path(dir, &|n| notify(n.into()));
let mut dir = Some(&*dir);

while let Some(d) = dir {
// First check the override database
if let Some(name) = settings.dir_override(d, notify) {
let reason = OverrideReason::OverrideDB(d.to_owned());
return Ok(Some((name, reason)));
}

// Then look for 'rust-toolchain'
let toolchain_file = d.join("rust-toolchain");
if let Ok(s) = utils::read_file("toolchain file", &toolchain_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,
toolchain_file.display()))?;

let reason = OverrideReason::ToolchainFile(toolchain_file);
return Ok(Some((toolchain_name.to_string(), reason)));
}
}

dir = d.parent();
}

Ok(None)
Expand Down
15 changes: 3 additions & 12 deletions src/rustup/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,18 +162,9 @@ impl Settings {
self.overrides.insert(key, toolchain);
}

pub fn find_override(&self, dir_unresolved: &Path, notify_handler: &Fn(Notification))
-> Option<(String, PathBuf)> {
let dir = utils::canonicalize_path(dir_unresolved, &|n| notify_handler(n.into()));
let mut maybe_path = Some(&*dir);
while let Some(path) = maybe_path {
let key = Self::path_to_key(path, notify_handler);
if let Some(toolchain) = self.overrides.get(&key) {
return Some((toolchain.to_owned(), path.to_owned()));
}
maybe_path = path.parent();
}
None
pub fn dir_override(&self, dir: &Path, notify_handler: &Fn(Notification)) -> Option<String> {
let key = Self::path_to_key(dir, notify_handler);
self.overrides.get(&key).map(|s| s.clone())
}

pub fn parse(data: &str) -> Result<Self> {
Expand Down
Loading