Skip to content

Commit

Permalink
Auto merge of rust-lang#111388 - clubby789:clap-complete, r=jyn514
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bors committed May 14, 2023
2 parents eb03a3e + a348f8a commit 2e18605
Show file tree
Hide file tree
Showing 10 changed files with 2,833 additions and 15 deletions.
10 changes: 10 additions & 0 deletions src/bootstrap/Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies = [
"build_helper",
"cc",
"clap",
"clap_complete",
"cmake",
"fd-lock",
"filetime",
Expand Down Expand Up @@ -119,6 +120,15 @@ dependencies = [
"clap_lex",
]

[[package]]
name = "clap_complete"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36774babb166352bb4f7b9cb16f781ffa3439d2a8f12cd31bea85a38c888fea3"
dependencies = [
"clap",
]

[[package]]
name = "clap_derive"
version = "4.2.0"
Expand Down
1 change: 1 addition & 0 deletions src/bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ walkdir = "2"
# Dependencies needed by the build-metrics feature
sysinfo = { version = "0.26.0", optional = true }
clap = { version = "4.2.4", default-features = false, features = ["std", "usage", "help", "derive", "error-context"] }
clap_complete = "4.2.2"

# Solaris doesn't support flock() and thus fd-lock is not option now
[target.'cfg(not(target_os = "solaris"))'.dependencies]
Expand Down
1 change: 1 addition & 0 deletions src/bootstrap/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ impl<'a> Builder<'a> {
run::CollectLicenseMetadata,
run::GenerateCopyright,
run::GenerateWindowsSys,
run::GenerateCompletions,
),
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
Expand Down
49 changes: 35 additions & 14 deletions src/bootstrap/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
//! This module implements the command-line parsing of the build system which
//! has various flags to configure how it's run.

use std::path::PathBuf;
use std::path::{Path, PathBuf};

use clap::{Parser, ValueEnum};
use clap::{CommandFactory, Parser, ValueEnum};

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

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

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

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

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

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

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

#[arg(global(true), long, value_name = "N")]
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
/// stage(s) to keep without recompiling
/// (pass multiple times to keep e.g., both stages 0 and 1)
pub keep_stage: Vec<u32>,
#[arg(global(true), long, value_name = "N")]
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
/// stage(s) of the standard library to keep without recompiling
/// (pass multiple times to keep e.g., both stages 0 and 1)
pub keep_stage_std: Vec<u32>,
Expand All @@ -103,6 +103,7 @@ pub struct Flags {
global(true),
short,
long,
value_hint = clap::ValueHint::Other,
default_value_t = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get),
value_name = "JOBS"
)]
Expand All @@ -117,7 +118,7 @@ pub struct Flags {
/// otherwise, use the default configured behaviour
pub warnings: Warnings,

#[arg(global(true), long, value_name = "FORMAT")]
#[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")]
/// rustc error format
pub error_format: Option<String>,
#[arg(global(true), long)]
Expand All @@ -133,13 +134,13 @@ pub struct Flags {
#[arg(global(true), long, value_name = "VALUE")]
pub llvm_skip_rebuild: Option<bool>,
/// generate PGO profile with rustc build
#[arg(global(true), long, value_name = "PROFILE")]
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
pub rust_profile_generate: Option<String>,
/// use PGO profile for rustc build
#[arg(global(true), long, value_name = "PROFILE")]
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
pub rust_profile_use: Option<String>,
/// use PGO profile for LLVM build
#[arg(global(true), long, value_name = "PROFILE")]
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
pub llvm_profile_use: Option<String>,
// LLVM doesn't support a custom location for generating profile
// information.
Expand All @@ -152,7 +153,7 @@ pub struct Flags {
#[arg(global(true), long)]
pub llvm_bolt_profile_generate: bool,
/// use BOLT profile for LLVM build
#[arg(global(true), long, value_name = "PROFILE")]
#[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
pub llvm_bolt_profile_use: Option<String>,
#[arg(global(true))]
/// paths for the subcommand
Expand Down Expand Up @@ -524,3 +525,23 @@ 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<G: clap_complete::Generator>(shell: G, path: &Path) -> Option<String> {
let mut cmd = Flags::command();
let current = if !path.exists() {
String::new()
} else {
std::fs::read_to_string(path).unwrap_or_else(|_| {
eprintln!("couldn't read {}", path.display());
crate::detail_exit(1)
})
};
let mut buf = Vec::new();
clap_complete::generate(shell, &mut cmd, "x.py", &mut buf);
if buf == current.as_bytes() {
return None;
}
Some(String::from_utf8(buf).expect("completion script should be UTF-8"))
}
34 changes: 34 additions & 0 deletions src/bootstrap/run.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::path::PathBuf;
use std::process::Command;

use clap_complete::shells;

use crate::builder::{Builder, RunConfig, ShouldRun, Step};
use crate::config::TargetSelection;
use crate::dist::distdir;
use crate::flags::get_completion;
use crate::test;
use crate::tool::{self, SourceType, Tool};
use crate::util::output;
Expand Down Expand Up @@ -275,3 +278,34 @@ impl Step for GenerateWindowsSys {
builder.run(&mut cmd);
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct GenerateCompletions;

impl Step for GenerateCompletions {
type Output = ();

/// Uses `clap_complete` to generate shell completions.
fn run(self, builder: &Builder<'_>) {
// FIXME(clubby789): enable zsh when clap#4898 is fixed
let [bash, fish, powershell] = ["x.py.sh", "x.py.fish", "x.py.ps1"]
.map(|filename| builder.src.join("src/etc/completions").join(filename));
if let Some(comp) = get_completion(shells::Bash, &bash) {
std::fs::write(&bash, comp).expect("writing bash completion");
}
if let Some(comp) = get_completion(shells::Fish, &fish) {
std::fs::write(&fish, comp).expect("writing fish completion");
}
if let Some(comp) = get_completion(shells::PowerShell, &powershell) {
std::fs::write(&powershell, comp).expect("writing powershell completion");
}
}

fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.alias("generate-completions")
}

fn make_run(run: RunConfig<'_>) {
run.builder.ensure(GenerateCompletions);
}
}
21 changes: 20 additions & 1 deletion src/bootstrap/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use std::iter;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

use clap_complete::shells;

use crate::builder::crate_description;
use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
use crate::cache::Interned;
Expand Down Expand Up @@ -1138,7 +1140,24 @@ help: to skip test's attempt to check tidiness, pass `--exclude src/tools/tidy`
builder.info("tidy check");
try_run(builder, &mut cmd);

builder.ensure(ExpandYamlAnchors {});
builder.ensure(ExpandYamlAnchors);

builder.info("x.py completions check");
let [bash, fish, powershell] = ["x.py.sh", "x.py.fish", "x.py.ps1"]
.map(|filename| builder.src.join("src/etc/completions").join(filename));
if builder.config.cmd.bless() {
builder.ensure(crate::run::GenerateCompletions);
} else {
if crate::flags::get_completion(shells::Bash, &bash).is_some()
|| crate::flags::get_completion(shells::Fish, &fish).is_some()
|| crate::flags::get_completion(shells::PowerShell, &powershell).is_some()
{
eprintln!(
"x.py completions were changed; run `x.py run generate-completions` to update them"
);
crate::detail_exit(1);
}
}
}

fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
Expand Down
Loading

0 comments on commit 2e18605

Please sign in to comment.