Skip to content

Commit a348f8a

Browse files
committed
Generate shell completions for bootstrap with Clap
1 parent ecd3dba commit a348f8a

File tree

10 files changed

+2833
-15
lines changed

10 files changed

+2833
-15
lines changed

src/bootstrap/Cargo.lock

+10
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ dependencies = [
4545
"build_helper",
4646
"cc",
4747
"clap",
48+
"clap_complete",
4849
"cmake",
4950
"fd-lock",
5051
"filetime",
@@ -119,6 +120,15 @@ dependencies = [
119120
"clap_lex",
120121
]
121122

123+
[[package]]
124+
name = "clap_complete"
125+
version = "4.2.2"
126+
source = "registry+https://github.com/rust-lang/crates.io-index"
127+
checksum = "36774babb166352bb4f7b9cb16f781ffa3439d2a8f12cd31bea85a38c888fea3"
128+
dependencies = [
129+
"clap",
130+
]
131+
122132
[[package]]
123133
name = "clap_derive"
124134
version = "4.2.0"

src/bootstrap/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ walkdir = "2"
5656
# Dependencies needed by the build-metrics feature
5757
sysinfo = { version = "0.26.0", optional = true }
5858
clap = { version = "4.2.4", default-features = false, features = ["std", "usage", "help", "derive", "error-context"] }
59+
clap_complete = "4.2.2"
5960

6061
# Solaris doesn't support flock() and thus fd-lock is not option now
6162
[target.'cfg(not(target_os = "solaris"))'.dependencies]

src/bootstrap/builder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ impl<'a> Builder<'a> {
839839
run::CollectLicenseMetadata,
840840
run::GenerateCopyright,
841841
run::GenerateWindowsSys,
842+
run::GenerateCompletions,
842843
),
843844
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
844845
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),

src/bootstrap/flags.rs

+35-14
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
//! This module implements the command-line parsing of the build system which
44
//! has various flags to configure how it's run.
55
6-
use std::path::PathBuf;
6+
use std::path::{Path, PathBuf};
77

8-
use clap::{Parser, ValueEnum};
8+
use clap::{CommandFactory, Parser, ValueEnum};
99

1010
use crate::builder::{Builder, Kind};
1111
use crate::config::{target_selection_list, Config, TargetSelectionList};
@@ -54,15 +54,15 @@ pub struct Flags {
5454
/// Build directory, overrides `build.build-dir` in `config.toml`
5555
pub build_dir: Option<PathBuf>,
5656

57-
#[arg(global(true), long, value_name = "BUILD")]
57+
#[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
5858
/// build target of the stage0 compiler
5959
pub build: Option<String>,
6060

61-
#[arg(global(true), long, value_name = "HOST", value_parser = target_selection_list)]
61+
#[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
6262
/// host targets to build
6363
pub host: Option<TargetSelectionList>,
6464

65-
#[arg(global(true), long, value_name = "TARGET", value_parser = target_selection_list)]
65+
#[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
6666
/// target targets to build
6767
pub target: Option<TargetSelectionList>,
6868

@@ -73,7 +73,7 @@ pub struct Flags {
7373
/// include default paths in addition to the provided ones
7474
pub include_default_paths: bool,
7575

76-
#[arg(global(true), long)]
76+
#[arg(global(true), value_hint = clap::ValueHint::Other, long)]
7777
pub rustc_error_format: Option<String>,
7878

7979
#[arg(global(true), long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
@@ -82,16 +82,16 @@ pub struct Flags {
8282
#[arg(global(true), long)]
8383
/// dry run; don't build anything
8484
pub dry_run: bool,
85-
#[arg(global(true), long, value_name = "N")]
85+
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
8686
/// stage to build (indicates compiler to use/test, e.g., stage 0 uses the
8787
/// bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)
8888
pub stage: Option<u32>,
8989

90-
#[arg(global(true), long, value_name = "N")]
90+
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
9191
/// stage(s) to keep without recompiling
9292
/// (pass multiple times to keep e.g., both stages 0 and 1)
9393
pub keep_stage: Vec<u32>,
94-
#[arg(global(true), long, value_name = "N")]
94+
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
9595
/// stage(s) of the standard library to keep without recompiling
9696
/// (pass multiple times to keep e.g., both stages 0 and 1)
9797
pub keep_stage_std: Vec<u32>,
@@ -103,6 +103,7 @@ pub struct Flags {
103103
global(true),
104104
short,
105105
long,
106+
value_hint = clap::ValueHint::Other,
106107
default_value_t = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get),
107108
value_name = "JOBS"
108109
)]
@@ -117,7 +118,7 @@ pub struct Flags {
117118
/// otherwise, use the default configured behaviour
118119
pub warnings: Warnings,
119120

120-
#[arg(global(true), long, value_name = "FORMAT")]
121+
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")]
121122
/// rustc error format
122123
pub error_format: Option<String>,
123124
#[arg(global(true), long)]
@@ -133,13 +134,13 @@ pub struct Flags {
133134
#[arg(global(true), long, value_name = "VALUE")]
134135
pub llvm_skip_rebuild: Option<bool>,
135136
/// generate PGO profile with rustc build
136-
#[arg(global(true), long, value_name = "PROFILE")]
137+
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
137138
pub rust_profile_generate: Option<String>,
138139
/// use PGO profile for rustc build
139-
#[arg(global(true), long, value_name = "PROFILE")]
140+
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
140141
pub rust_profile_use: Option<String>,
141142
/// use PGO profile for LLVM build
142-
#[arg(global(true), long, value_name = "PROFILE")]
143+
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
143144
pub llvm_profile_use: Option<String>,
144145
// LLVM doesn't support a custom location for generating profile
145146
// information.
@@ -152,7 +153,7 @@ pub struct Flags {
152153
#[arg(global(true), long)]
153154
pub llvm_bolt_profile_generate: bool,
154155
/// use BOLT profile for LLVM build
155-
#[arg(global(true), long, value_name = "PROFILE")]
156+
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
156157
pub llvm_bolt_profile_use: Option<String>,
157158
#[arg(global(true))]
158159
/// paths for the subcommand
@@ -524,3 +525,23 @@ impl Subcommand {
524525
}
525526
}
526527
}
528+
529+
/// Returns the shell completion for a given shell, if the result differs from the current
530+
/// content of `path`. If `path` does not exist, always returns `Some`.
531+
pub fn get_completion<G: clap_complete::Generator>(shell: G, path: &Path) -> Option<String> {
532+
let mut cmd = Flags::command();
533+
let current = if !path.exists() {
534+
String::new()
535+
} else {
536+
std::fs::read_to_string(path).unwrap_or_else(|_| {
537+
eprintln!("couldn't read {}", path.display());
538+
crate::detail_exit(1)
539+
})
540+
};
541+
let mut buf = Vec::new();
542+
clap_complete::generate(shell, &mut cmd, "x.py", &mut buf);
543+
if buf == current.as_bytes() {
544+
return None;
545+
}
546+
Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
547+
}

src/bootstrap/run.rs

+34
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use std::path::PathBuf;
22
use std::process::Command;
33

4+
use clap_complete::shells;
5+
46
use crate::builder::{Builder, RunConfig, ShouldRun, Step};
57
use crate::config::TargetSelection;
68
use crate::dist::distdir;
9+
use crate::flags::get_completion;
710
use crate::test;
811
use crate::tool::{self, SourceType, Tool};
912
use crate::util::output;
@@ -275,3 +278,34 @@ impl Step for GenerateWindowsSys {
275278
builder.run(&mut cmd);
276279
}
277280
}
281+
282+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
283+
pub struct GenerateCompletions;
284+
285+
impl Step for GenerateCompletions {
286+
type Output = ();
287+
288+
/// Uses `clap_complete` to generate shell completions.
289+
fn run(self, builder: &Builder<'_>) {
290+
// FIXME(clubby789): enable zsh when clap#4898 is fixed
291+
let [bash, fish, powershell] = ["x.py.sh", "x.py.fish", "x.py.ps1"]
292+
.map(|filename| builder.src.join("src/etc/completions").join(filename));
293+
if let Some(comp) = get_completion(shells::Bash, &bash) {
294+
std::fs::write(&bash, comp).expect("writing bash completion");
295+
}
296+
if let Some(comp) = get_completion(shells::Fish, &fish) {
297+
std::fs::write(&fish, comp).expect("writing fish completion");
298+
}
299+
if let Some(comp) = get_completion(shells::PowerShell, &powershell) {
300+
std::fs::write(&powershell, comp).expect("writing powershell completion");
301+
}
302+
}
303+
304+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
305+
run.alias("generate-completions")
306+
}
307+
308+
fn make_run(run: RunConfig<'_>) {
309+
run.builder.ensure(GenerateCompletions);
310+
}
311+
}

src/bootstrap/test.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use std::iter;
1010
use std::path::{Path, PathBuf};
1111
use std::process::{Command, Stdio};
1212

13+
use clap_complete::shells;
14+
1315
use crate::builder::crate_description;
1416
use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
1517
use crate::cache::Interned;
@@ -1121,7 +1123,24 @@ help: to skip test's attempt to check tidiness, pass `--exclude src/tools/tidy`
11211123
builder.info("tidy check");
11221124
try_run(builder, &mut cmd);
11231125

1124-
builder.ensure(ExpandYamlAnchors {});
1126+
builder.ensure(ExpandYamlAnchors);
1127+
1128+
builder.info("x.py completions check");
1129+
let [bash, fish, powershell] = ["x.py.sh", "x.py.fish", "x.py.ps1"]
1130+
.map(|filename| builder.src.join("src/etc/completions").join(filename));
1131+
if builder.config.cmd.bless() {
1132+
builder.ensure(crate::run::GenerateCompletions);
1133+
} else {
1134+
if crate::flags::get_completion(shells::Bash, &bash).is_some()
1135+
|| crate::flags::get_completion(shells::Fish, &fish).is_some()
1136+
|| crate::flags::get_completion(shells::PowerShell, &powershell).is_some()
1137+
{
1138+
eprintln!(
1139+
"x.py completions were changed; run `x.py run generate-completions` to update them"
1140+
);
1141+
crate::detail_exit(1);
1142+
}
1143+
}
11251144
}
11261145

11271146
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {

0 commit comments

Comments
 (0)