bootstrap/core/config/
flags.rs

1//! Command-line interface of the bootstrap build system.
2//!
3//! This module implements the command-line parsing of the build system which
4//! has various flags to configure how it's run.
5
6use std::path::{Path, PathBuf};
7
8use clap::{CommandFactory, Parser, ValueEnum};
9use clap_complete::Generator;
10#[cfg(feature = "tracing")]
11use tracing::instrument;
12
13use crate::core::build_steps::perf::PerfArgs;
14use crate::core::build_steps::setup::Profile;
15use crate::core::builder::{Builder, Kind};
16use crate::core::config::Config;
17use crate::core::config::target_selection::{TargetSelectionList, target_selection_list};
18use crate::{Build, DocTests};
19
20#[derive(Copy, Clone, Default, Debug, ValueEnum)]
21pub enum Color {
22    Always,
23    Never,
24    #[default]
25    Auto,
26}
27
28/// Whether to deny warnings, emit them as warnings, or use the default behavior
29#[derive(Copy, Clone, Default, Debug, ValueEnum)]
30pub enum Warnings {
31    Deny,
32    Warn,
33    #[default]
34    Default,
35}
36
37/// Deserialized version of all flags for this compile.
38#[derive(Debug, Parser)]
39#[command(
40    override_usage = "x.py <subcommand> [options] [<paths>...]",
41    disable_help_subcommand(true),
42    about = "",
43    next_line_help(false)
44)]
45pub struct Flags {
46    #[command(subcommand)]
47    pub cmd: Subcommand,
48
49    #[arg(global = true, short, long, action = clap::ArgAction::Count)]
50    /// use verbose output (-vv for very verbose)
51    pub verbose: u8, // each extra -v after the first is passed to Cargo
52    #[arg(global = true, short, long)]
53    /// use incremental compilation
54    pub incremental: bool,
55    #[arg(global = true, long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
56    /// TOML configuration file for build
57    pub config: Option<PathBuf>,
58    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
59    /// Build directory, overrides `build.build-dir` in `bootstrap.toml`
60    pub build_dir: Option<PathBuf>,
61
62    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
63    /// host target of the stage0 compiler
64    pub build: Option<String>,
65
66    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
67    /// host targets to build
68    pub host: Option<TargetSelectionList>,
69
70    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
71    /// target targets to build
72    pub target: Option<TargetSelectionList>,
73
74    #[arg(global = true, long, value_name = "PATH")]
75    /// build paths to exclude
76    pub exclude: Vec<PathBuf>, // keeping for client backward compatibility
77    #[arg(global = true, long, value_name = "PATH")]
78    /// build paths to skip
79    pub skip: Vec<PathBuf>,
80    #[arg(global = true, long)]
81    /// include default paths in addition to the provided ones
82    pub include_default_paths: bool,
83
84    /// rustc error format
85    #[arg(global = true, value_hint = clap::ValueHint::Other, long)]
86    pub rustc_error_format: Option<String>,
87
88    #[arg(global = true, long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
89    /// command to run on failure
90    pub on_fail: Option<String>,
91    #[arg(global = true, long)]
92    /// dry run; don't build anything
93    pub dry_run: bool,
94    /// Indicates whether to dump the work done from bootstrap shims
95    #[arg(global = true, long)]
96    pub dump_bootstrap_shims: bool,
97    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
98    /// stage to build (indicates compiler to use/test, e.g., stage 0 uses the
99    /// bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)
100    pub stage: Option<u32>,
101
102    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
103    /// stage(s) to keep without recompiling
104    /// (pass multiple times to keep e.g., both stages 0 and 1)
105    pub keep_stage: Vec<u32>,
106    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
107    /// stage(s) of the standard library to keep without recompiling
108    /// (pass multiple times to keep e.g., both stages 0 and 1)
109    pub keep_stage_std: Vec<u32>,
110    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
111    /// path to the root of the rust checkout
112    pub src: Option<PathBuf>,
113
114    #[arg(
115        global = true,
116        short,
117        long,
118        value_hint = clap::ValueHint::Other,
119        value_name = "JOBS"
120    )]
121    /// number of jobs to run in parallel
122    pub jobs: Option<u32>,
123    // This overrides the deny-warnings configuration option,
124    // which passes -Dwarnings to the compiler invocations.
125    #[arg(global = true, long)]
126    #[arg(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
127    /// if value is deny, will deny warnings
128    /// if value is warn, will emit warnings
129    /// otherwise, use the default configured behaviour
130    pub warnings: Warnings,
131
132    #[arg(global = true, long)]
133    /// use message-format=json
134    pub json_output: bool,
135
136    #[arg(global = true, long, value_name = "STYLE")]
137    #[arg(value_enum, default_value_t = Color::Auto)]
138    /// whether to use color in cargo and rustc output
139    pub color: Color,
140
141    #[arg(global = true, long)]
142    /// Bootstrap uses this value to decide whether it should bypass locking the build process.
143    /// This is rarely needed (e.g., compiling the std library for different targets in parallel).
144    ///
145    /// Unless you know exactly what you are doing, you probably don't need this.
146    pub bypass_bootstrap_lock: bool,
147
148    /// generate PGO profile with rustc build
149    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
150    pub rust_profile_generate: Option<String>,
151    /// use PGO profile for rustc build
152    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
153    pub rust_profile_use: Option<String>,
154    /// use PGO profile for LLVM build
155    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
156    pub llvm_profile_use: Option<String>,
157    // LLVM doesn't support a custom location for generating profile
158    // information.
159    //
160    // llvm_out/build/profiles/ is the location this writes to.
161    /// generate PGO profile with llvm built for rustc
162    #[arg(global = true, long)]
163    pub llvm_profile_generate: bool,
164    /// Enable BOLT link flags
165    #[arg(global = true, long)]
166    pub enable_bolt_settings: bool,
167    /// Skip stage0 compiler validation
168    #[arg(global = true, long)]
169    pub skip_stage0_validation: bool,
170    /// Additional reproducible artifacts that should be added to the reproducible artifacts archive.
171    #[arg(global = true, long)]
172    pub reproducible_artifact: Vec<String>,
173    #[arg(global = true)]
174    /// paths for the subcommand
175    pub paths: Vec<PathBuf>,
176    /// override options in bootstrap.toml
177    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
178    pub set: Vec<String>,
179    /// arguments passed to subcommands
180    #[arg(global = true, last(true), value_name = "ARGS")]
181    pub free_args: Vec<String>,
182    /// Make bootstrap to behave as it's running on the CI environment or not.
183    #[arg(global = true, long, value_name = "bool")]
184    pub ci: Option<bool>,
185    /// Skip checking the standard library if `rust.download-rustc` isn't available.
186    /// This is mostly for RA as building the stage1 compiler to check the library tree
187    /// on each code change might be too much for some computers.
188    #[arg(global = true, long)]
189    pub skip_std_check_if_no_download_rustc: bool,
190}
191
192impl Flags {
193    /// Check if `<cmd> -h -v` was passed.
194    /// If yes, print the available paths and return `true`.
195    pub fn try_parse_verbose_help(args: &[String]) -> bool {
196        // We need to check for `<cmd> -h -v`, in which case we list the paths
197        #[derive(Parser)]
198        #[command(disable_help_flag(true))]
199        struct HelpVerboseOnly {
200            #[arg(short, long)]
201            help: bool,
202            #[arg(global = true, short, long, action = clap::ArgAction::Count)]
203            pub verbose: u8,
204            #[arg(value_enum)]
205            cmd: Kind,
206        }
207        if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
208            HelpVerboseOnly::try_parse_from(normalize_args(args))
209        {
210            println!("NOTE: updating submodules before printing available paths");
211            let flags = Self::parse(&[String::from("build")]);
212            let config = Config::parse(flags);
213            let build = Build::new(config);
214            let paths = Builder::get_help(&build, subcommand);
215            if let Some(s) = paths {
216                println!("{s}");
217            } else {
218                panic!("No paths available for subcommand `{}`", subcommand.as_str());
219            }
220            true
221        } else {
222            false
223        }
224    }
225
226    #[cfg_attr(
227        feature = "tracing",
228        instrument(level = "trace", name = "Flags::parse", skip_all, fields(args = ?args))
229    )]
230    pub fn parse(args: &[String]) -> Self {
231        Flags::parse_from(normalize_args(args))
232    }
233}
234
235fn normalize_args(args: &[String]) -> Vec<String> {
236    let first = String::from("x.py");
237    let it = std::iter::once(first).chain(args.iter().cloned());
238    it.collect()
239}
240
241#[derive(Debug, Clone, Default, clap::Subcommand)]
242pub enum Subcommand {
243    #[command(aliases = ["b"], long_about = "\n
244    Arguments:
245        This subcommand accepts a number of paths to directories to the crates
246        and/or artifacts to compile. For example, for a quick build of a usable
247        compiler:
248            ./x.py build --stage 1 library/std
249        This will build a compiler and standard library from the local source code.
250        Once this is done, build/$ARCH/stage1 contains a usable compiler.
251        If no arguments are passed then the default artifacts for that stage are
252        compiled. For example:
253            ./x.py build --stage 0
254            ./x.py build ")]
255    /// Compile either the compiler or libraries
256    #[default]
257    Build,
258    #[command(aliases = ["c"], long_about = "\n
259    Arguments:
260        This subcommand accepts a number of paths to directories to the crates
261        and/or artifacts to compile. For example:
262            ./x.py check library/std
263        If no arguments are passed then many artifacts are checked.")]
264    /// Compile either the compiler or libraries, using cargo check
265    Check {
266        #[arg(long)]
267        /// Check all targets
268        all_targets: bool,
269    },
270    /// Run Clippy (uses rustup/cargo-installed clippy binary)
271    #[command(long_about = "\n
272    Arguments:
273        This subcommand accepts a number of paths to directories to the crates
274        and/or artifacts to run clippy against. For example:
275            ./x.py clippy library/core
276            ./x.py clippy library/core library/proc_macro")]
277    Clippy {
278        #[arg(long)]
279        fix: bool,
280        #[arg(long, requires = "fix")]
281        allow_dirty: bool,
282        #[arg(long, requires = "fix")]
283        allow_staged: bool,
284        /// clippy lints to allow
285        #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
286        allow: Vec<String>,
287        /// clippy lints to deny
288        #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
289        deny: Vec<String>,
290        /// clippy lints to warn on
291        #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
292        warn: Vec<String>,
293        /// clippy lints to forbid
294        #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
295        forbid: Vec<String>,
296    },
297    /// Run cargo fix
298    #[command(long_about = "\n
299    Arguments:
300        This subcommand accepts a number of paths to directories to the crates
301        and/or artifacts to run `cargo fix` against. For example:
302            ./x.py fix library/core
303            ./x.py fix library/core library/proc_macro")]
304    Fix,
305    #[command(
306        name = "fmt",
307        long_about = "\n
308    Arguments:
309        This subcommand optionally accepts a `--check` flag which succeeds if
310        formatting is correct and fails if it is not. For example:
311            ./x.py fmt
312            ./x.py fmt --check"
313    )]
314    /// Run rustfmt
315    Format {
316        /// check formatting instead of applying
317        #[arg(long)]
318        check: bool,
319
320        /// apply to all appropriate files, not just those that have been modified
321        #[arg(long)]
322        all: bool,
323    },
324    #[command(aliases = ["d"], long_about = "\n
325    Arguments:
326        This subcommand accepts a number of paths to directories of documentation
327        to build. For example:
328            ./x.py doc src/doc/book
329            ./x.py doc src/doc/nomicon
330            ./x.py doc src/doc/book library/std
331            ./x.py doc library/std --json
332            ./x.py doc library/std --open
333        If no arguments are passed then everything is documented:
334            ./x.py doc
335            ./x.py doc --stage 1")]
336    /// Build documentation
337    Doc {
338        #[arg(long)]
339        /// open the docs in a browser
340        open: bool,
341        #[arg(long)]
342        /// render the documentation in JSON format in addition to the usual HTML format
343        json: bool,
344    },
345    #[command(aliases = ["t"], long_about = "\n
346    Arguments:
347        This subcommand accepts a number of paths to test directories that
348        should be compiled and run. For example:
349            ./x.py test tests/ui
350            ./x.py test library/std --test-args hash_map
351            ./x.py test library/std --stage 0 --no-doc
352            ./x.py test tests/ui --bless
353            ./x.py test tests/ui --compare-mode next-solver
354        Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`;
355        just like `build library/std --stage N` it tests the compiler produced by the previous
356        stage.
357        Execute tool tests with a tool name argument:
358            ./x.py test tidy
359        If no arguments are passed then the complete artifacts for that stage are
360        compiled and tested.
361            ./x.py test
362            ./x.py test --stage 1")]
363    /// Build and run some test suites
364    Test {
365        #[arg(long)]
366        /// run all tests regardless of failure
367        no_fail_fast: bool,
368        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
369        /// extra arguments to be passed for the test tool being used
370        /// (e.g. libtest, compiletest or rustdoc)
371        test_args: Vec<String>,
372        /// extra options to pass the compiler when running compiletest tests
373        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
374        compiletest_rustc_args: Vec<String>,
375        #[arg(long)]
376        /// do not run doc tests
377        no_doc: bool,
378        #[arg(long)]
379        /// only run doc tests
380        doc: bool,
381        #[arg(long)]
382        /// whether to automatically update stderr/stdout files
383        bless: bool,
384        #[arg(long)]
385        /// comma-separated list of other files types to check (accepts py, py:lint,
386        /// py:fmt, shell, shell:lint, cpp, cpp:fmt, spellcheck, spellcheck:fix)
387        extra_checks: Option<String>,
388        #[arg(long)]
389        /// rerun tests even if the inputs are unchanged
390        force_rerun: bool,
391        #[arg(long)]
392        /// only run tests that result has been changed
393        only_modified: bool,
394        #[arg(long, value_name = "COMPARE MODE")]
395        /// mode describing what file the actual ui output will be compared to
396        compare_mode: Option<String>,
397        #[arg(long, value_name = "check | build | run")]
398        /// force {check,build,run}-pass tests to this mode.
399        pass: Option<String>,
400        #[arg(long, value_name = "auto | always | never")]
401        /// whether to execute run-* tests
402        run: Option<String>,
403        #[arg(long)]
404        /// enable this to generate a Rustfix coverage file, which is saved in
405        /// `/<build_base>/rustfix_missing_coverage.txt`
406        rustfix_coverage: bool,
407        #[arg(long)]
408        /// don't capture stdout/stderr of tests
409        no_capture: bool,
410    },
411    /// Build and run some test suites *in Miri*
412    Miri {
413        #[arg(long)]
414        /// run all tests regardless of failure
415        no_fail_fast: bool,
416        #[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
417        /// extra arguments to be passed for the test tool being used
418        /// (e.g. libtest, compiletest or rustdoc)
419        test_args: Vec<String>,
420        #[arg(long)]
421        /// do not run doc tests
422        no_doc: bool,
423        #[arg(long)]
424        /// only run doc tests
425        doc: bool,
426    },
427    /// Build and run some benchmarks
428    Bench {
429        #[arg(long, allow_hyphen_values(true))]
430        test_args: Vec<String>,
431    },
432    /// Clean out build directories
433    Clean {
434        #[arg(long)]
435        /// Clean the entire build directory (not used by default)
436        all: bool,
437        #[arg(long, value_name = "N")]
438        /// Clean a specific stage without touching other artifacts. By default, every stage is cleaned if this option is not used.
439        stage: Option<u32>,
440    },
441    /// Build distribution artifacts
442    Dist,
443    /// Install distribution artifacts
444    Install,
445    #[command(aliases = ["r"], long_about = "\n
446    Arguments:
447        This subcommand accepts a number of paths to tools to build and run. For
448        example:
449            ./x.py run src/tools/bump-stage0
450        At least a tool needs to be called.")]
451    /// Run tools contained in this repository
452    Run {
453        /// arguments for the tool
454        #[arg(long, allow_hyphen_values(true))]
455        args: Vec<String>,
456    },
457    /// Set up the environment for development
458    #[command(long_about = format!(
459        "\n
460x.py setup creates a `bootstrap.toml` which changes the defaults for x.py itself,
461as well as setting up a git pre-push hook, VS Code config and toolchain link.
462Arguments:
463    This subcommand accepts a 'profile' to use for builds. For example:
464        ./x.py setup library
465    The profile is optional and you will be prompted interactively if it is not given.
466    The following profiles are available:
467{}
468    To only set up the git hook, editor config or toolchain link, you may use
469        ./x.py setup hook
470        ./x.py setup editor
471        ./x.py setup link", Profile::all_for_help("        ").trim_end()))]
472    Setup {
473        /// Either the profile for `bootstrap.toml` or another setup action.
474        /// May be omitted to set up interactively
475        #[arg(value_name = "<PROFILE>|hook|editor|link")]
476        profile: Option<PathBuf>,
477    },
478    /// Suggest a subset of tests to run, based on modified files
479    #[command(long_about = "\n")]
480    Suggest {
481        /// run suggested tests
482        #[arg(long)]
483        run: bool,
484    },
485    /// Vendor dependencies
486    Vendor {
487        /// Additional `Cargo.toml` to sync and vendor
488        #[arg(long)]
489        sync: Vec<PathBuf>,
490        /// Always include version in subdir name
491        #[arg(long)]
492        versioned_dirs: bool,
493    },
494    /// Perform profiling and benchmarking of the compiler using `rustc-perf`.
495    Perf(PerfArgs),
496}
497
498impl Subcommand {
499    pub fn kind(&self) -> Kind {
500        match self {
501            Subcommand::Bench { .. } => Kind::Bench,
502            Subcommand::Build => Kind::Build,
503            Subcommand::Check { .. } => Kind::Check,
504            Subcommand::Clippy { .. } => Kind::Clippy,
505            Subcommand::Doc { .. } => Kind::Doc,
506            Subcommand::Fix => Kind::Fix,
507            Subcommand::Format { .. } => Kind::Format,
508            Subcommand::Test { .. } => Kind::Test,
509            Subcommand::Miri { .. } => Kind::Miri,
510            Subcommand::Clean { .. } => Kind::Clean,
511            Subcommand::Dist => Kind::Dist,
512            Subcommand::Install => Kind::Install,
513            Subcommand::Run { .. } => Kind::Run,
514            Subcommand::Setup { .. } => Kind::Setup,
515            Subcommand::Suggest { .. } => Kind::Suggest,
516            Subcommand::Vendor { .. } => Kind::Vendor,
517            Subcommand::Perf { .. } => Kind::Perf,
518        }
519    }
520
521    pub fn compiletest_rustc_args(&self) -> Vec<&str> {
522        match *self {
523            Subcommand::Test { ref compiletest_rustc_args, .. } => {
524                compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
525            }
526            _ => vec![],
527        }
528    }
529
530    pub fn fail_fast(&self) -> bool {
531        match *self {
532            Subcommand::Test { no_fail_fast, .. } | Subcommand::Miri { no_fail_fast, .. } => {
533                !no_fail_fast
534            }
535            _ => false,
536        }
537    }
538
539    pub fn doc_tests(&self) -> DocTests {
540        match *self {
541            Subcommand::Test { doc, no_doc, .. } | Subcommand::Miri { no_doc, doc, .. } => {
542                if doc {
543                    DocTests::Only
544                } else if no_doc {
545                    DocTests::No
546                } else {
547                    DocTests::Yes
548                }
549            }
550            _ => DocTests::Yes,
551        }
552    }
553
554    pub fn bless(&self) -> bool {
555        match *self {
556            Subcommand::Test { bless, .. } => bless,
557            _ => false,
558        }
559    }
560
561    pub fn extra_checks(&self) -> Option<&str> {
562        match *self {
563            Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str),
564            _ => None,
565        }
566    }
567
568    pub fn only_modified(&self) -> bool {
569        match *self {
570            Subcommand::Test { only_modified, .. } => only_modified,
571            _ => false,
572        }
573    }
574
575    pub fn force_rerun(&self) -> bool {
576        match *self {
577            Subcommand::Test { force_rerun, .. } => force_rerun,
578            _ => false,
579        }
580    }
581
582    pub fn no_capture(&self) -> bool {
583        match *self {
584            Subcommand::Test { no_capture, .. } => no_capture,
585            _ => false,
586        }
587    }
588
589    pub fn rustfix_coverage(&self) -> bool {
590        match *self {
591            Subcommand::Test { rustfix_coverage, .. } => rustfix_coverage,
592            _ => false,
593        }
594    }
595
596    pub fn compare_mode(&self) -> Option<&str> {
597        match *self {
598            Subcommand::Test { ref compare_mode, .. } => compare_mode.as_ref().map(|s| &s[..]),
599            _ => None,
600        }
601    }
602
603    pub fn pass(&self) -> Option<&str> {
604        match *self {
605            Subcommand::Test { ref pass, .. } => pass.as_ref().map(|s| &s[..]),
606            _ => None,
607        }
608    }
609
610    pub fn run(&self) -> Option<&str> {
611        match *self {
612            Subcommand::Test { ref run, .. } => run.as_ref().map(|s| &s[..]),
613            _ => None,
614        }
615    }
616
617    pub fn open(&self) -> bool {
618        match *self {
619            Subcommand::Doc { open, .. } => open,
620            _ => false,
621        }
622    }
623
624    pub fn json(&self) -> bool {
625        match *self {
626            Subcommand::Doc { json, .. } => json,
627            _ => false,
628        }
629    }
630
631    pub fn vendor_versioned_dirs(&self) -> bool {
632        match *self {
633            Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
634            _ => false,
635        }
636    }
637
638    pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
639        match self {
640            Subcommand::Vendor { sync, .. } => sync.clone(),
641            _ => vec![],
642        }
643    }
644}
645
646/// Returns the shell completion for a given shell, if the result differs from the current
647/// content of `path`. If `path` does not exist, always returns `Some`.
648pub fn get_completion(shell: &dyn Generator, path: &Path) -> Option<String> {
649    let mut cmd = Flags::command();
650    let current = if !path.exists() {
651        String::new()
652    } else {
653        std::fs::read_to_string(path).unwrap_or_else(|_| {
654            eprintln!("couldn't read {}", path.display());
655            crate::exit!(1)
656        })
657    };
658    let mut buf = Vec::new();
659    let (bin_name, _) = path
660        .file_name()
661        .expect("path should be a regular file")
662        .to_str()
663        .expect("file name should be UTF-8")
664        .rsplit_once('.')
665        .expect("file name should have an extension");
666
667    // We sort of replicate `clap_complete::generate` here, because we want to call it with
668    // `&dyn Generator`, but that function requires `G: Generator` instead.
669    cmd.set_bin_name(bin_name);
670    cmd.build();
671    shell.generate(&cmd, &mut buf);
672    if buf == current.as_bytes() {
673        return None;
674    }
675    Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
676}