Skip to content

Commit 2e18605

Browse files
committed
Auto merge of rust-lang#111388 - clubby789:clap-complete, r=jyn514
Generate shell completions for bootstrap with Clap Now that rust-lang#110693 has been merged, we can look at generating shell completions for x.py with `clap_complete`. Leaving this as draft for now as I'm not sure of the best way to integration the completion generator. Additionally, the generated completions for zsh are completely broken (will need to be resolved upstream, it doesn't seem to handle subcommands + global arguments well). I don't have Fish installed and would be interested to know how well completions work there. Alternative to rust-lang#107827
2 parents eb03a3e + a348f8a commit 2e18605

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;
@@ -1138,7 +1140,24 @@ help: to skip test's attempt to check tidiness, pass `--exclude src/tools/tidy`
11381140
builder.info("tidy check");
11391141
try_run(builder, &mut cmd);
11401142

1141-
builder.ensure(ExpandYamlAnchors {});
1143+
builder.ensure(ExpandYamlAnchors);
1144+
1145+
builder.info("x.py completions check");
1146+
let [bash, fish, powershell] = ["x.py.sh", "x.py.fish", "x.py.ps1"]
1147+
.map(|filename| builder.src.join("src/etc/completions").join(filename));
1148+
if builder.config.cmd.bless() {
1149+
builder.ensure(crate::run::GenerateCompletions);
1150+
} else {
1151+
if crate::flags::get_completion(shells::Bash, &bash).is_some()
1152+
|| crate::flags::get_completion(shells::Fish, &fish).is_some()
1153+
|| crate::flags::get_completion(shells::PowerShell, &powershell).is_some()
1154+
{
1155+
eprintln!(
1156+
"x.py completions were changed; run `x.py run generate-completions` to update them"
1157+
);
1158+
crate::detail_exit(1);
1159+
}
1160+
}
11421161
}
11431162

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

0 commit comments

Comments
 (0)