diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs index b3104ae05e88..b2293fdd9b52 100644 --- a/src/bootstrap/src/core/build_steps/run.rs +++ b/src/bootstrap/src/core/build_steps/run.rs @@ -5,6 +5,8 @@ use std::path::PathBuf; +use clap_complete::{Generator, shells}; + use crate::core::build_steps::dist::distdir; use crate::core::build_steps::test; use crate::core::build_steps::tool::{self, SourceType, Tool}; @@ -285,36 +287,35 @@ impl Step for GenerateWindowsSys { } } +/// Return tuples of (shell, file containing completions). +pub fn get_completion_paths(builder: &Builder<'_>) -> Vec<(&'static dyn Generator, PathBuf)> { + vec![ + (&shells::Bash as &'static dyn Generator, builder.src.join("src/etc/completions/x.py.sh")), + (&shells::Zsh, builder.src.join("src/etc/completions/x.py.zsh")), + (&shells::Fish, builder.src.join("src/etc/completions/x.py.fish")), + (&shells::PowerShell, builder.src.join("src/etc/completions/x.py.ps1")), + (&shells::Bash, builder.src.join("src/etc/completions/x.sh")), + (&shells::Zsh, builder.src.join("src/etc/completions/x.zsh")), + (&shells::Fish, builder.src.join("src/etc/completions/x.fish")), + (&shells::PowerShell, builder.src.join("src/etc/completions/x.ps1")), + ] +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GenerateCompletions; -macro_rules! generate_completions { - ( $( ( $shell:ident, $filename:expr ) ),* ) => { - $( - if let Some(comp) = get_completion($shell, &$filename) { - std::fs::write(&$filename, comp).expect(&format!("writing {} completion", stringify!($shell))); - } - )* - }; -} - impl Step for GenerateCompletions { type Output = (); /// Uses `clap_complete` to generate shell completions. fn run(self, builder: &Builder<'_>) { - use clap_complete::shells::{Bash, Fish, PowerShell, Zsh}; - - generate_completions!( - (Bash, builder.src.join("src/etc/completions/x.py.sh")), - (Zsh, builder.src.join("src/etc/completions/x.py.zsh")), - (Fish, builder.src.join("src/etc/completions/x.py.fish")), - (PowerShell, builder.src.join("src/etc/completions/x.py.ps1")), - (Bash, builder.src.join("src/etc/completions/x.sh")), - (Zsh, builder.src.join("src/etc/completions/x.zsh")), - (Fish, builder.src.join("src/etc/completions/x.fish")), - (PowerShell, builder.src.join("src/etc/completions/x.ps1")) - ); + for (shell, path) in get_completion_paths(builder) { + if let Some(comp) = get_completion(shell, &path) { + std::fs::write(&path, comp).unwrap_or_else(|e| { + panic!("writing completion into {} failed: {e:?}", path.display()) + }); + } + } } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 2d4d9e535981..a5b7b22aba85 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -8,12 +8,11 @@ use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use std::{env, fs, iter}; -use clap_complete::shells; - use crate::core::build_steps::compile::{Std, run_cargo}; use crate::core::build_steps::doc::DocumentationFormat; use crate::core::build_steps::gcc::{Gcc, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; +use crate::core::build_steps::run::get_completion_paths; use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; use crate::core::build_steps::tool::{self, COMPILETEST_ALLOW_FEATURES, SourceType, Tool}; use crate::core::build_steps::toolstate::ToolState; @@ -1153,14 +1152,12 @@ HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to cmd.delay_failure().run(builder); builder.info("x.py completions check"); - let [bash, zsh, fish, powershell] = ["x.py.sh", "x.py.zsh", "x.py.fish", "x.py.ps1"] - .map(|filename| builder.src.join("src/etc/completions").join(filename)); + let completion_paths = get_completion_paths(builder); if builder.config.cmd.bless() { builder.ensure(crate::core::build_steps::run::GenerateCompletions); - } else if get_completion(shells::Bash, &bash).is_some() - || get_completion(shells::Fish, &fish).is_some() - || get_completion(shells::PowerShell, &powershell).is_some() - || crate::flags::get_completion(shells::Zsh, &zsh).is_some() + } else if completion_paths + .into_iter() + .any(|(shell, path)| get_completion(shell, &path).is_some()) { eprintln!( "x.py completions were changed; run `x.py run generate-completions` to update them" diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index 79275db64863..bfc06f90d4f2 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use clap::{CommandFactory, Parser, ValueEnum}; +use clap_complete::Generator; #[cfg(feature = "tracing")] use tracing::instrument; @@ -644,7 +645,7 @@ impl Subcommand { /// Returns the shell completion for a given shell, if the result differs from the current /// content of `path`. If `path` does not exist, always returns `Some`. -pub fn get_completion(shell: G, path: &Path) -> Option { +pub fn get_completion(shell: &dyn Generator, path: &Path) -> Option { let mut cmd = Flags::command(); let current = if !path.exists() { String::new() @@ -662,7 +663,12 @@ pub fn get_completion(shell: G, path: &Path) -> Opt .expect("file name should be UTF-8") .rsplit_once('.') .expect("file name should have an extension"); - clap_complete::generate(shell, &mut cmd, bin_name, &mut buf); + + // We sort of replicate `clap_complete::generate` here, because we want to call it with + // `&dyn Generator`, but that function requires `G: Generator` instead. + cmd.set_bin_name(bin_name); + cmd.build(); + shell.generate(&cmd, &mut buf); if buf == current.as_bytes() { return None; }