Skip to content

Commit ca496e3

Browse files
authored
[cargo-zerocopy] Auto-install targets, more components (#3118)
Auto-install any `--target` arguments. Auto-install all of the following components: `rust-src`, `rustfmt`, `clippy`, `miri` (nightly only) gherrit-pr-id: Gj5ggnjidkmm6c3foelh4ou7sbi55y4qr
1 parent 37b84ef commit ca496e3

File tree

1 file changed

+155
-25
lines changed

1 file changed

+155
-25
lines changed

tools/cargo-zerocopy/src/main.rs

Lines changed: 155 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
// Cargo.toml section.
2121

2222
use std::{
23+
collections::HashSet,
2324
env, fmt, fs,
24-
io::{self, Read as _},
25+
io::{self, BufRead as _, Write as _},
2526
process::{self, Command, Output},
2627
};
2728

@@ -126,44 +127,143 @@ fn get_toolchain_versions() -> Versions {
126127
}
127128
}
128129

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(());
138138
}
139-
}
140139

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}");
143141
if env::var("GITHUB_RUN_ID").is_ok() {
144142
eprintln!("[cargo-zerocopy] detected GitHub Actions environment; auto-installing without waiting for confirmation");
145143
} else if env::var("CARGO_ZEROCOPY_AUTO_INSTALL_TOOLCHAIN").is_ok() {
146144
eprintln!("[cargo-zerocopy] detected CARGO_ZEROCOPY_AUTO_INSTALL_TOOLCHAIN environment variable; auto-installing without waiting for confirmation");
147145
} 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");
149147
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);
157157
}
158158
}
159159
}
160160

161-
let version = versions.get(name)?;
162-
rustup(["toolchain", "install", version, "-c", "rust-src"], None).execute();
161+
install()?;
163162

164163
Ok(())
165164
}
166165

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+
167267
fn get_rustflags(name: &str) -> String {
168268
// See #1792 for context on zerocopy_derive_union_into_bytes.
169269
let mut flags =
@@ -223,10 +323,40 @@ fn delegate_cargo() -> Result<(), Error> {
223323
if let Some(name) = arg.strip_prefix('+') {
224324
let version = versions.get(name)?;
225325

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;
228350
}
229351

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+
230360
let env_rustflags = env::vars()
231361
.filter_map(|(k, v)| if k == "RUSTFLAGS" { Some(v) } else { None })
232362
.next()

0 commit comments

Comments
 (0)