Skip to content

bootstrap: Consolidate editor setup into ./x setup editor & add support for vim, emacs & helix #131075

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

Merged
merged 4 commits into from
Oct 6, 2024
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
197 changes: 151 additions & 46 deletions src/bootstrap/src/core/build_steps/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,6 @@ pub enum Profile {

static PROFILE_DIR: &str = "src/bootstrap/defaults";

/// A list of historical hashes of `src/etc/rust_analyzer_settings.json`.
/// New entries should be appended whenever this is updated so we can detect
/// outdated vs. user-modified settings files.
static SETTINGS_HASHES: &[&str] = &[
"ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8",
"56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922",
"af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0",
"3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541",
"47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923",
"b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a",
"828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000",
"811fb3b063c739d261fd8590dd30242e117908f5a095d594fa04585daa18ec4d",
];
static RUST_ANALYZER_SETTINGS: &str = include_str!("../../../../etc/rust_analyzer_settings.json");

impl Profile {
fn include_path(&self, src_path: &Path) -> PathBuf {
PathBuf::from(format!("{}/{PROFILE_DIR}/config.{}.toml", src_path.display(), self))
Expand Down Expand Up @@ -533,46 +518,162 @@ undesirable, simply delete the `pre-push` file from .git/hooks."
Ok(())
}

/// Sets up or displays `src/etc/rust_analyzer_settings.json`
/// Handles editor-specific setup differences
#[derive(Clone, Debug, Eq, PartialEq)]
enum EditorKind {
Vscode,
Vim,
Emacs,
Helix,
}

impl EditorKind {
fn prompt_user() -> io::Result<Option<EditorKind>> {
let prompt_str = "Available editors:
1. vscode
2. vim
3. emacs
4. helix

Select which editor you would like to set up [default: None]: ";

let mut input = String::new();
loop {
print!("{}", prompt_str);
io::stdout().flush()?;
input.clear();
io::stdin().read_line(&mut input)?;
match input.trim().to_lowercase().as_str() {
"1" | "vscode" => return Ok(Some(EditorKind::Vscode)),
"2" | "vim" => return Ok(Some(EditorKind::Vim)),
"3" | "emacs" => return Ok(Some(EditorKind::Emacs)),
"4" | "helix" => return Ok(Some(EditorKind::Helix)),
"" => return Ok(None),
_ => {
eprintln!("ERROR: unrecognized option '{}'", input.trim());
eprintln!("NOTE: press Ctrl+C to exit");
}
};
}
}

/// A list of historical hashes of each LSP settings file
/// New entries should be appended whenever this is updated so we can detect
/// outdated vs. user-modified settings files.
fn hashes(&self) -> Vec<&str> {
match self {
EditorKind::Vscode | EditorKind::Vim => vec![
"ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8",
"56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922",
"af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0",
"3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541",
"47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923",
"b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a",
"828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000",
"811fb3b063c739d261fd8590dd30242e117908f5a095d594fa04585daa18ec4d",
],
EditorKind::Emacs => vec![
"51068d4747a13732440d1a8b8f432603badb1864fa431d83d0fd4f8fa57039e0",
"d29af4d949bbe2371eac928a3c31cf9496b1701aa1c45f11cd6c759865ad5c45",
],
EditorKind::Helix => {
vec!["2d3069b8cf1b977e5d4023965eb6199597755e6c96c185ed5f2854f98b83d233"]
}
}
}

fn settings_path(&self, config: &Config) -> PathBuf {
config.src.join(self.settings_short_path())
}

fn settings_short_path(&self) -> PathBuf {
self.settings_folder().join(match self {
EditorKind::Vscode => "settings.json",
EditorKind::Vim => "coc-settings.json",
EditorKind::Emacs => ".dir-locals.el",
EditorKind::Helix => "languages.toml",
})
}

fn settings_folder(&self) -> PathBuf {
match self {
EditorKind::Vscode => PathBuf::from(".vscode"),
EditorKind::Vim => PathBuf::from(".vim"),
EditorKind::Emacs => PathBuf::new(),
EditorKind::Helix => PathBuf::from(".helix"),
}
}

fn settings_template(&self) -> &str {
match self {
EditorKind::Vscode | EditorKind::Vim => {
include_str!("../../../../etc/rust_analyzer_settings.json")
}
EditorKind::Emacs => include_str!("../../../../etc/rust_analyzer_eglot.el"),
EditorKind::Helix => include_str!("../../../../etc/rust_analyzer_helix.toml"),
}
}

fn backup_extension(&self) -> String {
format!("{}.bak", self.settings_short_path().extension().unwrap().to_str().unwrap())
}
}

/// Sets up or displays the LSP config for one of the supported editors
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Vscode;
pub struct Editor;

impl Step for Vscode {
impl Step for Editor {
type Output = ();
const DEFAULT: bool = true;

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

fn make_run(run: RunConfig<'_>) {
if run.builder.config.dry_run() {
return;
}
if let [cmd] = &run.paths[..] {
if cmd.assert_single_path().path.as_path().as_os_str() == "vscode" {
run.builder.ensure(Vscode);
if cmd.assert_single_path().path.as_path().as_os_str() == "editor" {
run.builder.ensure(Editor);
}
}
}

fn run(self, builder: &Builder<'_>) -> Self::Output {
let config = &builder.config;
if config.dry_run() {
return;
}
while !t!(create_vscode_settings_maybe(config)) {}
match EditorKind::prompt_user() {
Ok(editor_kind) => {
if let Some(editor_kind) = editor_kind {
while !t!(create_editor_settings_maybe(config, editor_kind.clone())) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The logic would be simpler to follow if the loop was directly inside create_editor_settings_maybe, allowing to print the preview without having to call the function again.

But this was pre-existing, so it doesn't need to be changed in this PR.

} else {
println!("Ok, skipping editor setup!");
}
}
Err(e) => eprintln!("Could not determine the editor: {e}"),
}
}
}

/// Create a `.vscode/settings.json` file for rustc development, or just print it
/// Create the recommended editor LSP config file for rustc development, or just print it
/// If this method should be re-called, it returns `false`.
fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
let (current_hash, historical_hashes) = SETTINGS_HASHES.split_last().unwrap();
let vscode_settings = config.src.join(".vscode").join("settings.json");
// If None, no settings.json exists
fn create_editor_settings_maybe(config: &Config, editor: EditorKind) -> io::Result<bool> {
let hashes = editor.hashes();
let (current_hash, historical_hashes) = hashes.split_last().unwrap();
let settings_path = editor.settings_path(config);
let settings_short_path = editor.settings_short_path();
let settings_filename = settings_short_path.to_str().unwrap();
// If None, no settings file exists
// If Some(true), is a previous version of settings.json
// If Some(false), is not a previous version (i.e. user modified)
// If it's up to date we can just skip this
let mut mismatched_settings = None;
if let Ok(current) = fs::read_to_string(&vscode_settings) {
if let Ok(current) = fs::read_to_string(&settings_path) {
let mut hasher = sha2::Sha256::new();
hasher.update(&current);
let hash = hex_encode(hasher.finalize().as_slice());
Expand All @@ -585,20 +686,21 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
}
}
println!(
"\nx.py can automatically install the recommended `.vscode/settings.json` file for rustc development"
"\nx.py can automatically install the recommended `{settings_filename}` file for rustc development"
);

match mismatched_settings {
Some(true) => eprintln!(
"WARNING: existing `.vscode/settings.json` is out of date, x.py will update it"
),
Some(true) => {
eprintln!("WARNING: existing `{settings_filename}` is out of date, x.py will update it")
}
Some(false) => eprintln!(
"WARNING: existing `.vscode/settings.json` has been modified by user, x.py will back it up and replace it"
"WARNING: existing `{settings_filename}` has been modified by user, x.py will back it up and replace it"
),
_ => (),
}
let should_create = match prompt_user(
"Would you like to create/update settings.json? (Press 'p' to preview values): [y/N]",
)? {
let should_create = match prompt_user(&format!(
"Would you like to create/update `{settings_filename}`? (Press 'p' to preview values): [y/N]"
))? {
Some(PromptResult::Yes) => true,
Some(PromptResult::Print) => false,
_ => {
Expand All @@ -607,28 +709,31 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
}
};
if should_create {
let path = config.src.join(".vscode");
if !path.exists() {
fs::create_dir(&path)?;
let settings_folder_path = config.src.join(editor.settings_folder());
if !settings_folder_path.exists() {
fs::create_dir(settings_folder_path)?;
}
let verb = match mismatched_settings {
// exists but outdated, we can replace this
Some(true) => "Updated",
// exists but user modified, back it up
Some(false) => {
// exists and is not current version or outdated, so back it up
let mut backup = vscode_settings.clone();
backup.set_extension("json.bak");
eprintln!("WARNING: copying `settings.json` to `settings.json.bak`");
fs::copy(&vscode_settings, &backup)?;
let backup = settings_path.clone().with_extension(editor.backup_extension());
eprintln!(
"WARNING: copying `{}` to `{}`",
settings_path.file_name().unwrap().to_str().unwrap(),
backup.file_name().unwrap().to_str().unwrap(),
);
fs::copy(&settings_path, &backup)?;
"Updated"
}
_ => "Created",
};
fs::write(&vscode_settings, RUST_ANALYZER_SETTINGS)?;
println!("{verb} `.vscode/settings.json`");
fs::write(&settings_path, editor.settings_template())?;
println!("{verb} `{}`", settings_filename);
} else {
println!("\n{RUST_ANALYZER_SETTINGS}");
println!("\n{}", editor.settings_template());
}
Ok(should_create)
}
9 changes: 5 additions & 4 deletions src/bootstrap/src/core/build_steps/setup/tests.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use sha2::Digest;

use super::{RUST_ANALYZER_SETTINGS, SETTINGS_HASHES};
use super::EditorKind;
use crate::utils::helpers::hex_encode;

#[test]
fn check_matching_settings_hash() {
let editor = EditorKind::Vscode;
let mut hasher = sha2::Sha256::new();
hasher.update(&RUST_ANALYZER_SETTINGS);
hasher.update(&editor.settings_template());
let hash = hex_encode(hasher.finalize().as_slice());
assert_eq!(
&hash,
SETTINGS_HASHES.last().unwrap(),
"Update `SETTINGS_HASHES` with the new hash of `src/etc/rust_analyzer_settings.json`"
editor.hashes().last().unwrap(),
"Update `EditorKind::hashes()` with the new hash of `src/etc/rust_analyzer_settings.json`"
);
}
4 changes: 3 additions & 1 deletion src/bootstrap/src/core/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,9 @@ impl<'a> Builder<'a> {
run::GenerateWindowsSys,
run::GenerateCompletions,
),
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
Kind::Setup => {
describe!(setup::Profile, setup::Hook, setup::Link, setup::Editor)
}
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
Kind::Vendor => describe!(vendor::Vendor),
// special-cased in Build::build()
Expand Down
6 changes: 3 additions & 3 deletions src/bootstrap/src/core/config/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,14 +447,14 @@ Arguments:
The profile is optional and you will be prompted interactively if it is not given.
The following profiles are available:
{}
To only set up the git hook, VS Code config or toolchain link, you may use
To only set up the git hook, editor config or toolchain link, you may use
./x.py setup hook
./x.py setup vscode
./x.py setup editor
./x.py setup link", Profile::all_for_help(" ").trim_end()))]
Setup {
/// Either the profile for `config.toml` or another setup action.
/// May be omitted to set up interactively
#[arg(value_name = "<PROFILE>|hook|vscode|link")]
#[arg(value_name = "<PROFILE>|hook|editor|link")]
profile: Option<PathBuf>,
},
/// Suggest a subset of tests to run, based on modified files
Expand Down
5 changes: 5 additions & 0 deletions src/bootstrap/src/utils/change_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info,
summary: "If `llvm.download-ci-llvm` is not defined, it defaults to `true`.",
},
ChangeInfo {
change_id: 131075,
severity: ChangeSeverity::Info,
summary: "New option `./x setup editor` added, replacing `./x setup vscode` and adding support for vim, emacs and helix.",
},
];
2 changes: 1 addition & 1 deletion src/etc/completions/x.py.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2741,7 +2741,7 @@ _x.py() {
return 0
;;
x.py__setup)
opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [<PROFILE>|hook|vscode|link] [PATHS]... [ARGS]..."
opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [<PROFILE>|hook|editor|link] [PATHS]... [ARGS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand Down
50 changes: 25 additions & 25 deletions src/etc/rust_analyzer_eglot.el
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@
.((eglot-workspace-configuration
. (:rust-analyzer
( :check ( :invocationLocation "root"
:invocationStrategy "once"
:overrideCommand ["python3"
"x.py"
"check"
"--json-output"])
:linkedProjects ["Cargo.toml"
"src/tools/x/Cargo.toml"
"src/bootstrap/Cargo.toml"
"src/tools/rust-analyzer/Cargo.toml"
"compiler/rustc_codegen_cranelift/Cargo.toml"
"compiler/rustc_codegen_gcc/Cargo.toml"]
:rustfmt ( :overrideCommand ["build/host/rustfmt/bin/rustfmt"
"--edition=2021"])
:procMacro ( :server "build/host/stage0/libexec/rust-analyzer-proc-macro-srv"
:enable t)
:cargo ( :buildScripts ( :enable t
:invocationLocation "root"
:invocationStrategy "once"
:overrideCommand ["python3"
"x.py"
"check"
"--json-output"])
:sysrootSrc "./library"
:extraEnv (:RUSTC_BOOTSTRAP "1"))
:rustc ( :source "./Cargo.toml" )))))))
:invocationStrategy "once"
:overrideCommand ["python3"
"x.py"
"check"
"--json-output"])
:linkedProjects ["Cargo.toml"
"src/tools/x/Cargo.toml"
"src/bootstrap/Cargo.toml"
"src/tools/rust-analyzer/Cargo.toml"
"compiler/rustc_codegen_cranelift/Cargo.toml"
"compiler/rustc_codegen_gcc/Cargo.toml"]
:rustfmt ( :overrideCommand ["build/host/rustfmt/bin/rustfmt"
"--edition=2021"])
:procMacro ( :server "build/host/stage0/libexec/rust-analyzer-proc-macro-srv"
:enable t)
:cargo ( :buildScripts ( :enable t
:invocationLocation "root"
:invocationStrategy "once"
:overrideCommand ["python3"
"x.py"
"check"
"--json-output"])
:sysrootSrc "./library"
:extraEnv (:RUSTC_BOOTSTRAP "1"))
:rustc ( :source "./Cargo.toml" )))))))
Loading