|
| 1 | +//! Tests for Cargo's behavior under Rustup. |
| 2 | +
|
| 3 | +use cargo_test_support::paths::{home, root, CargoPathExt}; |
| 4 | +use cargo_test_support::{cargo_process, process, project}; |
| 5 | +use std::env; |
| 6 | +use std::env::consts::EXE_EXTENSION; |
| 7 | +use std::ffi::OsString; |
| 8 | +use std::fs; |
| 9 | +use std::path::{Path, PathBuf}; |
| 10 | + |
| 11 | +/// Helper to generate an executable. |
| 12 | +fn make_exe(dest: &Path, name: &str, contents: &str, env: &[(&str, PathBuf)]) -> PathBuf { |
| 13 | + let rs_name = format!("{name}.rs"); |
| 14 | + fs::write( |
| 15 | + root().join(&rs_name), |
| 16 | + &format!("fn main() {{ {contents} }}"), |
| 17 | + ) |
| 18 | + .unwrap(); |
| 19 | + let mut pb = process("rustc"); |
| 20 | + env.iter().for_each(|(key, value)| { |
| 21 | + pb.env(key, value); |
| 22 | + }); |
| 23 | + pb.arg("--edition=2021") |
| 24 | + .arg(root().join(&rs_name)) |
| 25 | + .exec() |
| 26 | + .unwrap(); |
| 27 | + let exe = Path::new(name).with_extension(EXE_EXTENSION); |
| 28 | + let output = dest.join(&exe); |
| 29 | + fs::rename(root().join(&exe), &output).unwrap(); |
| 30 | + output |
| 31 | +} |
| 32 | + |
| 33 | +fn prepend_path(path: &Path) -> OsString { |
| 34 | + let mut paths = vec![path.to_path_buf()]; |
| 35 | + paths.extend(env::split_paths(&env::var_os("PATH").unwrap_or_default())); |
| 36 | + env::join_paths(paths).unwrap() |
| 37 | +} |
| 38 | + |
| 39 | +struct RustupEnvironment { |
| 40 | + /// Path for ~/.cargo/bin |
| 41 | + cargo_bin: PathBuf, |
| 42 | + /// Path for ~/.rustup |
| 43 | + rustup_home: PathBuf, |
| 44 | + /// Path to the cargo executable in the toolchain directory |
| 45 | + /// (~/.rustup/toolchain/test-toolchain/bin/cargo.exe). |
| 46 | + cargo_toolchain_exe: PathBuf, |
| 47 | +} |
| 48 | + |
| 49 | +/// Creates an executable which prints a message and then runs the *real* rustc. |
| 50 | +fn real_rustc_wrapper(bin_dir: &Path, message: &str) -> PathBuf { |
| 51 | + let real_rustc = cargo_util::paths::resolve_executable("rustc".as_ref()).unwrap(); |
| 52 | + // The toolchain rustc needs to call the real rustc. In order to do that, |
| 53 | + // it needs to restore or clear the RUSTUP environment variables so that |
| 54 | + // if rustup is installed, it will call the correct rustc. |
| 55 | + let rustup_toolchain_setup = match std::env::var_os("RUSTUP_TOOLCHAIN") { |
| 56 | + Some(t) => format!( |
| 57 | + ".env(\"RUSTUP_TOOLCHAIN\", \"{}\")", |
| 58 | + t.into_string().unwrap() |
| 59 | + ), |
| 60 | + None => format!(".env_remove(\"RUSTUP_TOOLCHAIN\")"), |
| 61 | + }; |
| 62 | + let mut env = vec![("CARGO_RUSTUP_TEST_real_rustc", real_rustc)]; |
| 63 | + let rustup_home_setup = match std::env::var_os("RUSTUP_HOME") { |
| 64 | + Some(h) => { |
| 65 | + env.push(("CARGO_RUSTUP_TEST_RUSTUP_HOME", h.into())); |
| 66 | + format!(".env(\"RUSTUP_HOME\", env!(\"CARGO_RUSTUP_TEST_RUSTUP_HOME\"))") |
| 67 | + } |
| 68 | + None => format!(".env_remove(\"RUSTUP_HOME\")"), |
| 69 | + }; |
| 70 | + make_exe( |
| 71 | + bin_dir, |
| 72 | + "rustc", |
| 73 | + &format!( |
| 74 | + r#" |
| 75 | + eprintln!("{message}"); |
| 76 | + let r = std::process::Command::new(env!("CARGO_RUSTUP_TEST_real_rustc")) |
| 77 | + .args(std::env::args_os().skip(1)) |
| 78 | + {rustup_toolchain_setup} |
| 79 | + {rustup_home_setup} |
| 80 | + .status(); |
| 81 | + std::process::exit(r.unwrap().code().unwrap_or(2)); |
| 82 | + "# |
| 83 | + ), |
| 84 | + &env, |
| 85 | + ) |
| 86 | +} |
| 87 | + |
| 88 | +/// Creates a simulation of a rustup environment with `~/.cargo/bin` and |
| 89 | +/// `~/.rustup` directories populated with some executables that simulate |
| 90 | +/// rustup. |
| 91 | +fn simulated_rustup_environment() -> RustupEnvironment { |
| 92 | + // Set up ~/.rustup/toolchains/test-toolchain/bin with a custom rustc and cargo. |
| 93 | + let rustup_home = home().join(".rustup"); |
| 94 | + let toolchain_bin = rustup_home |
| 95 | + .join("toolchains") |
| 96 | + .join("test-toolchain") |
| 97 | + .join("bin"); |
| 98 | + toolchain_bin.mkdir_p(); |
| 99 | + let rustc_toolchain_exe = real_rustc_wrapper(&toolchain_bin, "real rustc running"); |
| 100 | + let cargo_toolchain_exe = make_exe( |
| 101 | + &toolchain_bin, |
| 102 | + "cargo", |
| 103 | + r#"panic!("cargo toolchain should not be called");"#, |
| 104 | + &[], |
| 105 | + ); |
| 106 | + |
| 107 | + // Set up ~/.cargo/bin with a typical set of rustup proxies. |
| 108 | + let cargo_bin = home().join(".cargo").join("bin"); |
| 109 | + cargo_bin.mkdir_p(); |
| 110 | + |
| 111 | + let rustc_proxy = make_exe( |
| 112 | + &cargo_bin, |
| 113 | + "rustc", |
| 114 | + &format!( |
| 115 | + r#" |
| 116 | + match std::env::args().next().unwrap().as_ref() {{ |
| 117 | + "rustc" => {{}} |
| 118 | + arg => panic!("proxy only supports rustc, got {{arg:?}}"), |
| 119 | + }} |
| 120 | + eprintln!("rustc proxy running"); |
| 121 | + let r = std::process::Command::new(env!("CARGO_RUSTUP_TEST_rustc_toolchain_exe")) |
| 122 | + .args(std::env::args_os().skip(1)) |
| 123 | + .status(); |
| 124 | + std::process::exit(r.unwrap().code().unwrap_or(2)); |
| 125 | + "# |
| 126 | + ), |
| 127 | + &[("CARGO_RUSTUP_TEST_rustc_toolchain_exe", rustc_toolchain_exe)], |
| 128 | + ); |
| 129 | + fs::hard_link( |
| 130 | + &rustc_proxy, |
| 131 | + cargo_bin.join("cargo").with_extension(EXE_EXTENSION), |
| 132 | + ) |
| 133 | + .unwrap(); |
| 134 | + fs::hard_link( |
| 135 | + &rustc_proxy, |
| 136 | + cargo_bin.join("rustup").with_extension(EXE_EXTENSION), |
| 137 | + ) |
| 138 | + .unwrap(); |
| 139 | + |
| 140 | + RustupEnvironment { |
| 141 | + cargo_bin, |
| 142 | + rustup_home, |
| 143 | + cargo_toolchain_exe, |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +#[cargo_test] |
| 148 | +fn typical_rustup() { |
| 149 | + // Test behavior under a typical rustup setup with a normal toolchain. |
| 150 | + let RustupEnvironment { |
| 151 | + cargo_bin, |
| 152 | + rustup_home, |
| 153 | + cargo_toolchain_exe, |
| 154 | + } = simulated_rustup_environment(); |
| 155 | + |
| 156 | + // Set up a project and run a normal cargo build. |
| 157 | + let p = project().file("src/lib.rs", "").build(); |
| 158 | + // The path is modified so that cargo will call `rustc` from |
| 159 | + // `~/.cargo/bin/rustc to use our custom rustup proxies. |
| 160 | + let path = prepend_path(&cargo_bin); |
| 161 | + p.cargo("check") |
| 162 | + .env("RUSTUP_TOOLCHAIN", "test-toolchain") |
| 163 | + .env("RUSTUP_HOME", &rustup_home) |
| 164 | + .env("PATH", &path) |
| 165 | + .with_stderr( |
| 166 | + "\ |
| 167 | +[CHECKING] foo v0.0.1 [..] |
| 168 | +rustc proxy running |
| 169 | +real rustc running |
| 170 | +[FINISHED] [..] |
| 171 | +", |
| 172 | + ) |
| 173 | + .run(); |
| 174 | + |
| 175 | + // Do a similar test, but with a toolchain link that does not have cargo |
| 176 | + // (which normally would do a fallback to nightly/beta/stable). |
| 177 | + cargo_toolchain_exe.rm_rf(); |
| 178 | + p.build_dir().rm_rf(); |
| 179 | + |
| 180 | + p.cargo("check") |
| 181 | + .env("RUSTUP_TOOLCHAIN", "test-toolchain") |
| 182 | + .env("RUSTUP_HOME", &rustup_home) |
| 183 | + .env("PATH", &path) |
| 184 | + .with_stderr( |
| 185 | + "\ |
| 186 | +[CHECKING] foo v0.0.1 [..] |
| 187 | +rustc proxy running |
| 188 | +real rustc running |
| 189 | +[FINISHED] [..] |
| 190 | +", |
| 191 | + ) |
| 192 | + .run(); |
| 193 | +} |
| 194 | + |
| 195 | +// This doesn't work on Windows because Cargo forces the PATH to contain the |
| 196 | +// sysroot_libdir, which is actually `bin`, preventing the test from |
| 197 | +// overriding the bin directory. |
| 198 | +#[cargo_test(ignore_windows = "PATH can't be overridden on Windows")] |
| 199 | +fn custom_calls_other_cargo() { |
| 200 | + // Test behavior when a custom subcommand tries to manipulate PATH to use |
| 201 | + // a different toolchain. |
| 202 | + let RustupEnvironment { |
| 203 | + cargo_bin, |
| 204 | + rustup_home, |
| 205 | + cargo_toolchain_exe: _, |
| 206 | + } = simulated_rustup_environment(); |
| 207 | + |
| 208 | + // Create a directory with a custom toolchain (outside of the rustup universe). |
| 209 | + let custom_bin = root().join("custom-bin"); |
| 210 | + custom_bin.mkdir_p(); |
| 211 | + // `cargo` points to the real cargo. |
| 212 | + let cargo_exe = cargo_test_support::cargo_exe(); |
| 213 | + fs::hard_link(&cargo_exe, custom_bin.join(cargo_exe.file_name().unwrap())).unwrap(); |
| 214 | + // `rustc` executes the real rustc. |
| 215 | + real_rustc_wrapper(&custom_bin, "custom toolchain rustc running"); |
| 216 | + |
| 217 | + // A project that cargo-custom will try to build. |
| 218 | + let p = project().file("src/lib.rs", "").build(); |
| 219 | + |
| 220 | + // Create a custom cargo subcommand. |
| 221 | + // This will modify PATH to a custom toolchain and call cargo from that. |
| 222 | + make_exe( |
| 223 | + &cargo_bin, |
| 224 | + "cargo-custom", |
| 225 | + r#" |
| 226 | + use std::env; |
| 227 | + use std::process::Command; |
| 228 | +
|
| 229 | + eprintln!("custom command running"); |
| 230 | +
|
| 231 | + let mut paths = vec![std::path::PathBuf::from(env!("CARGO_RUSTUP_TEST_custom_bin"))]; |
| 232 | + paths.extend(env::split_paths(&env::var_os("PATH").unwrap_or_default())); |
| 233 | + let path = env::join_paths(paths).unwrap(); |
| 234 | +
|
| 235 | + let status = Command::new("cargo") |
| 236 | + .arg("check") |
| 237 | + .current_dir(env!("CARGO_RUSTUP_TEST_project_dir")) |
| 238 | + .env("PATH", path) |
| 239 | + .status() |
| 240 | + .unwrap(); |
| 241 | + assert!(status.success()); |
| 242 | + "#, |
| 243 | + &[ |
| 244 | + ("CARGO_RUSTUP_TEST_custom_bin", custom_bin), |
| 245 | + ("CARGO_RUSTUP_TEST_project_dir", p.root()), |
| 246 | + ], |
| 247 | + ); |
| 248 | + |
| 249 | + cargo_process("custom") |
| 250 | + // Set these to simulate what would happen when running under rustup. |
| 251 | + // We want to make sure that cargo-custom does not try to use the |
| 252 | + // rustup proxies. |
| 253 | + .env("RUSTUP_TOOLCHAIN", "test-toolchain") |
| 254 | + .env("RUSTUP_HOME", &rustup_home) |
| 255 | + .with_stderr( |
| 256 | + "\ |
| 257 | +custom command running |
| 258 | +[CHECKING] foo [..] |
| 259 | +custom toolchain rustc running |
| 260 | +[FINISHED] [..] |
| 261 | +", |
| 262 | + ) |
| 263 | + .run(); |
| 264 | +} |
0 commit comments