|
20 | 20 | // Cargo.toml section. |
21 | 21 |
|
22 | 22 | use std::{ |
| 23 | + collections::HashSet, |
23 | 24 | env, fmt, fs, |
24 | | - io::{self, Read as _}, |
| 25 | + io::{self, BufRead as _, Write as _}, |
25 | 26 | process::{self, Command, Output}, |
26 | 27 | }; |
27 | 28 |
|
@@ -126,44 +127,143 @@ fn get_toolchain_versions() -> Versions { |
126 | 127 | } |
127 | 128 | } |
128 | 129 |
|
129 | | -fn is_toolchain_installed(versions: &Versions, name: &str) -> Result<bool, Error> { |
130 | | - let version = versions.get(name)?; |
131 | | - let output = rustup(["run", version, "cargo", "version"], None).output().unwrap(); |
132 | | - if output.status.success() { |
133 | | - let output = rustup([&format!("+{version}"), "component", "list"], None).output_or_exit(); |
134 | | - let stdout = String::from_utf8(output.stdout).unwrap(); |
135 | | - Ok(stdout.contains("rust-src (installed)")) |
136 | | - } else { |
137 | | - Ok(false) |
| 130 | +fn ensure_installed_or_exit( |
| 131 | + is_installed: impl FnOnce() -> Result<bool, Error>, |
| 132 | + install: impl FnOnce() -> Result<(), Error>, |
| 133 | + missing_item_desc: &str, |
| 134 | + prompt: &str, |
| 135 | +) -> Result<(), Error> { |
| 136 | + if is_installed()? { |
| 137 | + return Ok(()); |
138 | 138 | } |
139 | | -} |
140 | 139 |
|
141 | | -fn install_toolchain_or_exit(versions: &Versions, name: &str) -> Result<(), Error> { |
142 | | - eprintln!("[cargo-zerocopy] missing either toolchain '{name}' or component 'rust-src'"); |
| 140 | + eprintln!("[cargo-zerocopy] {missing_item_desc}"); |
143 | 141 | if env::var("GITHUB_RUN_ID").is_ok() { |
144 | 142 | eprintln!("[cargo-zerocopy] detected GitHub Actions environment; auto-installing without waiting for confirmation"); |
145 | 143 | } else if env::var("CARGO_ZEROCOPY_AUTO_INSTALL_TOOLCHAIN").is_ok() { |
146 | 144 | eprintln!("[cargo-zerocopy] detected CARGO_ZEROCOPY_AUTO_INSTALL_TOOLCHAIN environment variable; auto-installing without waiting for confirmation"); |
147 | 145 | } else { |
148 | | - eprintln!("[cargo-zerocopy] set CARGO_ZEROCOPY_AUTO_INSTALL_TOOLCHAIN=1 to always install toolchains without prompting"); |
| 146 | + eprintln!("[cargo-zerocopy] set CARGO_ZEROCOPY_AUTO_INSTALL_TOOLCHAIN=1 to always install toolchains and targets without prompting"); |
149 | 147 | loop { |
150 | | - eprint!("[cargo-zerocopy] would you like to install toolchain '{name}' and component 'rust-src' via 'rustup' (y/n)? "); |
151 | | - let mut input = [0]; |
152 | | - io::stdin().read_exact(&mut input).unwrap(); |
153 | | - match input[0] as char { |
154 | | - 'y' | 'Y' => break, |
155 | | - 'n' | 'N' => process::exit(1), |
156 | | - _ => (), |
| 148 | + eprint!("[cargo-zerocopy] {prompt} (y/n)? "); |
| 149 | + io::stderr().flush().unwrap(); |
| 150 | + let mut line = String::new(); |
| 151 | + io::stdin().lock().read_line(&mut line).unwrap(); |
| 152 | + let input = line.trim().to_lowercase(); |
| 153 | + if input.starts_with('y') { |
| 154 | + break; |
| 155 | + } else if input.starts_with('n') { |
| 156 | + process::exit(1); |
157 | 157 | } |
158 | 158 | } |
159 | 159 | } |
160 | 160 |
|
161 | | - let version = versions.get(name)?; |
162 | | - rustup(["toolchain", "install", version, "-c", "rust-src"], None).execute(); |
| 161 | + install()?; |
163 | 162 |
|
164 | 163 | Ok(()) |
165 | 164 | } |
166 | 165 |
|
| 166 | +fn install_toolchain_or_exit(versions: &Versions, name: &str) -> Result<(), Error> { |
| 167 | + let version = versions.get(name)?.to_string(); |
| 168 | + let is_nightly = version.contains("nightly"); |
| 169 | + |
| 170 | + ensure_installed_or_exit( |
| 171 | + || { |
| 172 | + let output = rustup(["run", &version, "cargo", "version"], None).output(); |
| 173 | + let output = match output { |
| 174 | + Ok(o) => o, |
| 175 | + Err(e) => { |
| 176 | + eprintln!("[cargo-zerocopy] failed to run rustup: {e}"); |
| 177 | + process::exit(1); |
| 178 | + } |
| 179 | + }; |
| 180 | + if output.status.success() { |
| 181 | + let output = |
| 182 | + rustup([&format!("+{version}"), "component", "list"], None).output_or_exit(); |
| 183 | + let stdout = String::from_utf8(output.stdout).unwrap(); |
| 184 | + let is_installed = |
| 185 | + |c| stdout.lines().any(|l| l.starts_with(c) && l.contains("(installed)")); |
| 186 | + let mut installed = |
| 187 | + is_installed("rust-src") && is_installed("rustfmt") && is_installed("clippy"); |
| 188 | + if is_nightly { |
| 189 | + installed = installed && is_installed("miri"); |
| 190 | + } |
| 191 | + Ok(installed) |
| 192 | + } else { |
| 193 | + Ok(false) |
| 194 | + } |
| 195 | + }, |
| 196 | + || { |
| 197 | + let mut args = vec![ |
| 198 | + "toolchain", |
| 199 | + "install", |
| 200 | + &version, |
| 201 | + "-c", |
| 202 | + "rust-src", |
| 203 | + "-c", |
| 204 | + "rustfmt", |
| 205 | + "-c", |
| 206 | + "clippy", |
| 207 | + ]; |
| 208 | + if is_nightly { |
| 209 | + args.push("-c"); |
| 210 | + args.push("miri"); |
| 211 | + } |
| 212 | + rustup(args, None).execute(); |
| 213 | + Ok(()) |
| 214 | + }, |
| 215 | + &format!( |
| 216 | + "missing toolchain '{name}' or one of its components (rust-src, rustfmt, clippy{})", |
| 217 | + if is_nightly { ", miri" } else { "" } |
| 218 | + ), |
| 219 | + &format!("would you like to install toolchain '{name}' and its components via 'rustup'"), |
| 220 | + ) |
| 221 | +} |
| 222 | + |
| 223 | +fn install_targets_or_exit(version: &str, targets: &[String]) -> Result<(), Error> { |
| 224 | + // Avoid running `rustup` in the common case that no `--target` arguments |
| 225 | + // are provided. |
| 226 | + if targets.is_empty() { |
| 227 | + return Ok(()); |
| 228 | + } |
| 229 | + |
| 230 | + let output = rustup(["target", "list", "--toolchain", version], None).output_or_exit(); |
| 231 | + let stdout = String::from_utf8(output.stdout).unwrap(); |
| 232 | + let mut installed = HashSet::new(); |
| 233 | + let mut available = HashSet::new(); |
| 234 | + |
| 235 | + for line in stdout.lines() { |
| 236 | + let mut parts = line.split_whitespace(); |
| 237 | + if let Some(target) = parts.next() { |
| 238 | + available.insert(target.to_string()); |
| 239 | + if parts.next() == Some("(installed)") { |
| 240 | + installed.insert(target.to_string()); |
| 241 | + } |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + let to_install = targets |
| 246 | + .iter() |
| 247 | + .filter(|target| { |
| 248 | + !installed.contains(target.as_str()) && available.contains(target.as_str()) |
| 249 | + }) |
| 250 | + .cloned() |
| 251 | + .collect::<Vec<_>>(); |
| 252 | + |
| 253 | + let to_install_str = to_install.join(", "); |
| 254 | + ensure_installed_or_exit( |
| 255 | + || Ok(to_install.is_empty()), |
| 256 | + || { |
| 257 | + let mut args = vec!["target", "add", "--toolchain", version]; |
| 258 | + args.extend(to_install.iter().map(|s| s.as_str())); |
| 259 | + rustup(args, None).execute(); |
| 260 | + Ok(()) |
| 261 | + }, |
| 262 | + &format!("missing target(s): {to_install_str}"), |
| 263 | + &format!("would you like to install target(s) '{to_install_str}' via 'rustup'"), |
| 264 | + ) |
| 265 | +} |
| 266 | + |
167 | 267 | fn get_rustflags(name: &str) -> String { |
168 | 268 | // See #1792 for context on zerocopy_derive_union_into_bytes. |
169 | 269 | let mut flags = |
@@ -223,10 +323,40 @@ fn delegate_cargo() -> Result<(), Error> { |
223 | 323 | if let Some(name) = arg.strip_prefix('+') { |
224 | 324 | let version = versions.get(name)?; |
225 | 325 |
|
226 | | - if !is_toolchain_installed(&versions, name)? { |
227 | | - install_toolchain_or_exit(&versions, name)?; |
| 326 | + install_toolchain_or_exit(&versions, name)?; |
| 327 | + |
| 328 | + let mut targets = Vec::new(); |
| 329 | + if let Ok(t) = env::var("CARGO_BUILD_TARGET") { |
| 330 | + targets.push(t); |
| 331 | + } |
| 332 | + |
| 333 | + let args_vec = args.collect::<Vec<_>>(); |
| 334 | + let mut i = 0; |
| 335 | + while i < args_vec.len() { |
| 336 | + let arg = &args_vec[i]; |
| 337 | + if arg == "--" { |
| 338 | + break; |
| 339 | + } |
| 340 | + |
| 341 | + if arg == "--target" { |
| 342 | + if i + 1 < args_vec.len() { |
| 343 | + targets.push(args_vec[i + 1].clone()); |
| 344 | + i += 1; |
| 345 | + } |
| 346 | + } else if let Some(t) = arg.strip_prefix("--target=") { |
| 347 | + targets.push(t.to_string()); |
| 348 | + } |
| 349 | + i += 1; |
228 | 350 | } |
229 | 351 |
|
| 352 | + targets.retain(|t| !t.ends_with(".json")); |
| 353 | + targets.sort(); |
| 354 | + targets.dedup(); |
| 355 | + |
| 356 | + install_targets_or_exit(version, &targets)?; |
| 357 | + |
| 358 | + let mut args = args_vec.into_iter(); |
| 359 | + |
230 | 360 | let env_rustflags = env::vars() |
231 | 361 | .filter_map(|(k, v)| if k == "RUSTFLAGS" { Some(v) } else { None }) |
232 | 362 | .next() |
|
0 commit comments