Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate shell completions for bootstrap with Clap #111388

Merged
merged 1 commit into from
May 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1121,7 +1123,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