Skip to content

Commit fa8a05d

Browse files
committed
Recursive tool invocations should invoke the proxy, not the tool directly
The way the proxy was setting up the PATH variable contained two bugs: first, that it allowed the toolchain path to precede the value of CARGO_HOME/bin; but second that it didn't add CARGO_HOME/bin to the path at all. The result was that when e.g. cargo execs rustc, it was directly execing the toolchain rustc. Now it execs the proxy, assuming that CARGO_HOME/bin is set up correctly, and guaranteeing not to run the toolchain tool directly. Fixes #809
1 parent a6db7a3 commit fa8a05d

File tree

6 files changed

+131
-29
lines changed

6 files changed

+131
-29
lines changed

src/rustup-cli/proxy_mode.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ pub fn main() -> Result<()> {
3434

3535
// Build command args now while we know whether or not to skip arg 1.
3636
let cmd_args: Vec<_> = if toolchain.is_none() {
37-
env::args_os().collect()
37+
env::args_os().skip(1).collect()
3838
} else {
39-
env::args_os().take(1).chain(env::args_os().skip(2)).collect()
39+
env::args_os().skip(2).collect()
4040
};
4141

4242
let cfg = try!(set_globals(false));
@@ -51,6 +51,6 @@ fn direct_proxy(cfg: &Cfg, arg0: &str, toolchain: Option<&str>, args: &[OsString
5151
None => try!(cfg.create_command_for_dir(&try!(utils::current_dir()), arg0)),
5252
Some(tc) => try!(cfg.create_command_for_toolchain(tc, arg0)),
5353
};
54-
Ok(try!(run_command_for_dir(cmd, &args, &cfg)))
54+
Ok(try!(run_command_for_dir(cmd, arg0, args, &cfg)))
5555
}
5656

src/rustup-cli/rustup_mode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ fn run(cfg: &Cfg, m: &ArgMatches) -> Result<()> {
487487
let args: Vec<_> = args.collect();
488488
let cmd = try!(cfg.create_command_for_toolchain(toolchain, args[0]));
489489

490-
Ok(try!(command::run_command_for_dir(cmd, &args, &cfg)))
490+
Ok(try!(command::run_command_for_dir(cmd, args[0], &args[1..], &cfg)))
491491
}
492492

493493
fn which(cfg: &Cfg, m: &ArgMatches) -> Result<()> {

src/rustup-mock/src/mock_bin_src.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use std::process::Command;
12
use std::io::{self, BufWriter, Write};
3+
use std::env::consts::EXE_SUFFIX;
24

35
fn main() {
46
let args: Vec<_> = ::std::env::args().collect();
@@ -13,6 +15,12 @@ fn main() {
1315
for _ in 0 .. 10000 {
1416
buf.write_all(b"error: a value named `fail` has already been defined in this module [E0428]\n").unwrap();
1517
}
18+
} else if args.get(1) == Some(&"--call-rustc".to_string()) {
19+
// Used by the fallback_cargo_calls_correct_rustc test. Tests that
20+
// the environment has been set up right such that invoking rustc
21+
// will actually invoke the wrapper
22+
let rustc = &format!("rustc{}", EXE_SUFFIX);
23+
Command::new(rustc).arg("--version").status().unwrap();
1624
} else {
1725
panic!("bad mock proxy commandline");
1826
}

src/rustup/command.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
use std::env;
21
use std::ffi::OsStr;
32
use std::fs::File;
43
use std::io::{self, Write, BufRead, BufReader, Seek, SeekFrom};
5-
use std::path::PathBuf;
64
use std::process::{self, Command, Stdio};
75
use std::time::Instant;
86
use regex::Regex;
@@ -16,21 +14,19 @@ use telemetry::{Telemetry, TelemetryEvent};
1614

1715

1816
pub fn run_command_for_dir<S: AsRef<OsStr>>(cmd: Command,
17+
arg0: &str,
1918
args: &[S],
2019
cfg: &Cfg) -> Result<()> {
21-
let arg0 = env::args().next().map(PathBuf::from);
22-
let arg0 = arg0.as_ref()
23-
.and_then(|a| a.file_name())
24-
.and_then(|a| a.to_str());
25-
let arg0 = try!(arg0.ok_or(ErrorKind::NoExeName));
2620
if (arg0 == "rustc" || arg0 == "rustc.exe") && try!(cfg.telemetry_enabled()) {
27-
return telemetry_rustc(cmd, args, cfg);
21+
return telemetry_rustc(cmd, arg0, args, cfg);
2822
}
2923

30-
run_command_for_dir_without_telemetry(cmd, args)
24+
run_command_for_dir_without_telemetry(cmd, arg0, args)
3125
}
3226

33-
fn telemetry_rustc<S: AsRef<OsStr>>(mut cmd: Command, args: &[S], cfg: &Cfg) -> Result<()> {
27+
fn telemetry_rustc<S: AsRef<OsStr>>(mut cmd: Command,
28+
arg0: &str,
29+
args: &[S], cfg: &Cfg) -> Result<()> {
3430
#[cfg(unix)]
3531
fn file_as_stdio(file: &File) -> Stdio {
3632
use std::os::unix::io::{AsRawFd, FromRawFd};
@@ -45,7 +41,7 @@ fn telemetry_rustc<S: AsRef<OsStr>>(mut cmd: Command, args: &[S], cfg: &Cfg) ->
4541

4642
let now = Instant::now();
4743

48-
cmd.args(&args[1..]);
44+
cmd.args(args);
4945

5046
let has_color_args = args.iter().any(|e| {
5147
let e = e.as_ref().to_str().unwrap_or("");
@@ -130,14 +126,16 @@ fn telemetry_rustc<S: AsRef<OsStr>>(mut cmd: Command, args: &[S], cfg: &Cfg) ->
130126
});
131127

132128
Err(e).chain_err(|| rustup_utils::ErrorKind::RunningCommand {
133-
name: args[0].as_ref().to_owned(),
129+
name: OsStr::new(arg0).to_owned(),
134130
})
135131
},
136132
}
137133
}
138134

139-
fn run_command_for_dir_without_telemetry<S: AsRef<OsStr>>(mut cmd: Command, args: &[S]) -> Result<()> {
140-
cmd.args(&args[1..]);
135+
fn run_command_for_dir_without_telemetry<S: AsRef<OsStr>>(
136+
mut cmd: Command, arg0: &str, args: &[S]) -> Result<()>
137+
{
138+
cmd.args(&args);
141139

142140
// FIXME rust-lang/rust#32254. It's not clear to me
143141
// when and why this is needed.
@@ -151,7 +149,7 @@ fn run_command_for_dir_without_telemetry<S: AsRef<OsStr>>(mut cmd: Command, args
151149
}
152150
Err(e) => {
153151
Err(e).chain_err(|| rustup_utils::ErrorKind::RunningCommand {
154-
name: args[0].as_ref().to_owned(),
152+
name: OsStr::new(arg0).to_owned(),
155153
})
156154
}
157155
}

src/rustup/toolchain.rs

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use install::{self, InstallMethod};
1313
use telemetry;
1414
use telemetry::{Telemetry, TelemetryEvent};
1515

16+
use std::env::consts::EXE_SUFFIX;
17+
use std::ffi::OsString;
1618
use std::process::Command;
1719
use std::path::{Path, PathBuf};
1820
use std::ffi::OsStr;
@@ -67,7 +69,16 @@ impl<'a> Toolchain<'a> {
6769
&self.path
6870
}
6971
pub fn exists(&self) -> bool {
70-
utils::is_directory(&self.path)
72+
// HACK: linked toolchains are symlinks, and, contrary to what std docs
73+
// lead me to believe `fs::metadata`, used by `is_directory` does not
74+
// seem to follow symlinks on windows.
75+
let is_symlink = if cfg!(windows) {
76+
use std::fs;
77+
fs::symlink_metadata(&self.path).map(|m| m.file_type().is_symlink()).unwrap_or(false)
78+
} else {
79+
false
80+
};
81+
utils::is_directory(&self.path) || is_symlink
7182
}
7283
pub fn verify(&self) -> Result<()> {
7384
Ok(try!(utils::assert_is_directory(&self.path)))
@@ -277,12 +288,24 @@ impl<'a> Toolchain<'a> {
277288
return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into());
278289
}
279290

280-
// Assume this binary exists within the current toolchain
281-
let bin_path = self.path.join("bin").join(binary.as_ref());
291+
// Create the path to this binary within the current toolchain sysroot
292+
let binary = if let Some(binary_str) = binary.as_ref().to_str() {
293+
if binary_str.ends_with(EXE_SUFFIX) {
294+
binary.as_ref().to_owned()
295+
} else {
296+
OsString::from(format!("{}{}", binary_str, EXE_SUFFIX))
297+
}
298+
} else {
299+
// Very weird case. Non-unicode command.
300+
binary.as_ref().to_owned()
301+
};
302+
303+
let bin_path = self.path.join("bin").join(&binary);
282304
let mut cmd = Command::new(if utils::is_file(&bin_path) {
283305
&bin_path
284306
} else {
285-
// If not, let the OS try to resolve it globally for us
307+
// If the bin doesn't actually exist in the sysroot, let the OS try
308+
// to resolve it globally for us
286309
Path::new(&binary)
287310
});
288311
self.set_env(&mut cmd);
@@ -293,7 +316,41 @@ impl<'a> Toolchain<'a> {
293316
// to give custom toolchains access to cargo
294317
pub fn create_fallback_command<T: AsRef<OsStr>>(&self, binary: T,
295318
primary_toolchain: &Toolchain) -> Result<Command> {
296-
let mut cmd = try!(self.create_command(binary));
319+
// With the hacks below this only works for cargo atm
320+
assert!(binary.as_ref() == "cargo" || binary.as_ref() == "cargo.exe");
321+
322+
if !self.exists() {
323+
return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into());
324+
}
325+
if !primary_toolchain.exists() {
326+
return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into());
327+
}
328+
let src_file = self.path.join("bin").join("cargo.exe");
329+
330+
// MAJOR HACKS: Copy cargo.exe to its own directory on windows before
331+
// running it. This is so that the fallback cargo, when it in turn runs
332+
// rustc.exe, will run the rustc.exe out of the PATH environment
333+
// variable, _not_ the rustc.exe sitting in the same directory as the
334+
// fallback. See the `fallback_cargo_calls_correct_rustc` testcase and
335+
// PR 812.
336+
let exe_path = if cfg!(windows) {
337+
use std::fs;
338+
let fallback_dir = self.cfg.multirust_dir.join("fallback");
339+
try!(fs::create_dir_all(&fallback_dir)
340+
.chain_err(|| "unable to create dir to hold fallback exe"));
341+
let fallback_file = fallback_dir.join("cargo.exe");
342+
if fallback_file.exists() {
343+
try!(fs::remove_file(&fallback_file)
344+
.chain_err(|| "unable to unlink old fallback exe"));
345+
}
346+
try!(fs::hard_link(&src_file, &fallback_file)
347+
.chain_err(|| "unable to hard link fallback exe"));
348+
fallback_file
349+
} else {
350+
src_file
351+
};
352+
let mut cmd = Command::new(exe_path);
353+
self.set_env(&mut cmd);
297354
cmd.env("RUSTUP_TOOLCHAIN", &primary_toolchain.name);
298355
Ok(cmd)
299356
}
@@ -328,13 +385,13 @@ impl<'a> Toolchain<'a> {
328385
}
329386
env_var::prepend_path(sysenv::LOADER_PATH, &new_path, cmd);
330387

331-
// Prepend first cargo_home, then toolchain/bin to the PATH
332-
let mut path_to_prepend = PathBuf::from("");
388+
// Prepend CARGO_HOME/bin to the PATH variable so that we're sure to run
389+
// cargo/rustc via the proxy bins. There is no fallback case for if the
390+
// proxy bins don't exist. We'll just be running whatever happens to
391+
// be on the PATH.
333392
if let Ok(cargo_home) = utils::cargo_home() {
334-
path_to_prepend.push(cargo_home.join("bin"));
393+
env_var::prepend_path("PATH", &cargo_home.join("bin"), cmd);
335394
}
336-
path_to_prepend.push(self.path.join("bin"));
337-
env_var::prepend_path("PATH", path_to_prepend.as_path(), cmd);
338395
}
339396

340397
pub fn doc_path(&self, relative: &str) -> Result<PathBuf> {

tests/cli-rustup.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ extern crate rustup_utils;
55
extern crate rustup_mock;
66
extern crate tempdir;
77

8+
use std::fs;
9+
use std::env::consts::EXE_SUFFIX;
810
use rustup_mock::clitools::{self, Config, Scenario,
911
expect_ok, expect_ok_ex,
1012
expect_stdout_ok,
@@ -269,6 +271,43 @@ fn link() {
269271
});
270272
}
271273

274+
// Issue #809. When we call the fallback cargo, when it in turn invokes
275+
// "rustc", that rustc should actually be the rustup proxy, not the toolchain rustc.
276+
// That way the proxy can pick the correct toolchain.
277+
#[test]
278+
fn fallback_cargo_calls_correct_rustc() {
279+
setup(&|config| {
280+
// Hm, this is the _only_ test that assumes that toolchain proxies
281+
// exist in CARGO_HOME. Adding that proxy here.
282+
let ref rustup_path = config.exedir.join(format!("rustup{}", EXE_SUFFIX));
283+
let ref cargo_bin_path = config.cargodir.join("bin");
284+
fs::create_dir_all(cargo_bin_path).unwrap();
285+
let ref rustc_path = cargo_bin_path.join(format!("rustc{}", EXE_SUFFIX));
286+
fs::hard_link(rustup_path, rustc_path).unwrap();
287+
288+
// Install a custom toolchain and a nightly toolchain for the cargo fallback
289+
let path = config.customdir.join("custom-1");
290+
let path = path.to_string_lossy();
291+
expect_ok(config, &["rustup", "toolchain", "link", "custom",
292+
&path]);
293+
expect_ok(config, &["rustup", "default", "custom"]);
294+
expect_ok(config, &["rustup", "update", "nightly"]);
295+
expect_stdout_ok(config, &["rustc", "--version"],
296+
"hash-c-1");
297+
expect_stdout_ok(config, &["cargo", "--version"],
298+
"hash-n-2");
299+
300+
assert!(rustc_path.exists());
301+
302+
// Here --call-rustc tells the mock cargo bin to exec `rustc --version`.
303+
// We should be ultimately calling the custom rustc, according to the
304+
// RUSTUP_TOOLCHAIN variable set by the original "cargo" proxy, and
305+
// interpreted by the nested "rustc" proxy.
306+
expect_stdout_ok(config, &["cargo", "--call-rustc"],
307+
"hash-c-1");
308+
});
309+
}
310+
272311
#[test]
273312
fn show_toolchain_none() {
274313
setup(&|config| {

0 commit comments

Comments
 (0)