Skip to content

Commit e923446

Browse files
committed
bootstrap: Add support for ./x setup emacs
Add support for automatically setting up the recommended LSP config for Emacs. Additionally, refactor setup.rs to make it easier to add support for more editors in the future.
1 parent e6eb451 commit e923446

File tree

6 files changed

+157
-88
lines changed

6 files changed

+157
-88
lines changed

src/bootstrap/src/core/build_steps/setup.rs

Lines changed: 118 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,6 @@ pub enum Profile {
3535

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

38-
/// A list of historical hashes of `src/etc/rust_analyzer_settings.json`.
39-
/// New entries should be appended whenever this is updated so we can detect
40-
/// outdated vs. user-modified settings files.
41-
static SETTINGS_HASHES: &[&str] = &[
42-
"ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8",
43-
"56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922",
44-
"af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0",
45-
"3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541",
46-
"47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923",
47-
"b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a",
48-
"828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000",
49-
"811fb3b063c739d261fd8590dd30242e117908f5a095d594fa04585daa18ec4d",
50-
];
51-
static RUST_ANALYZER_SETTINGS: &str = include_str!("../../../../etc/rust_analyzer_settings.json");
52-
5338
impl Profile {
5439
fn include_path(&self, src_path: &Path) -> PathBuf {
5540
PathBuf::from(format!("{}/{PROFILE_DIR}/config.{}.toml", src_path.display(), self))
@@ -533,46 +518,116 @@ undesirable, simply delete the `pre-push` file from .git/hooks."
533518
Ok(())
534519
}
535520

536-
/// Sets up or displays `src/etc/rust_analyzer_settings.json`
537-
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
538-
pub struct Vscode;
521+
/// Handles editor-specific setup differences
522+
#[derive(Clone, Debug, Eq, PartialEq)]
523+
enum EditorKind {
524+
Vscode,
525+
Emacs,
526+
}
539527

540-
impl Step for Vscode {
541-
type Output = ();
542-
const DEFAULT: bool = true;
543-
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
544-
run.alias("vscode")
528+
impl EditorKind {
529+
/// A list of historical hashes of each LSP settings file
530+
/// New entries should be appended whenever this is updated so we can detect
531+
/// outdated vs. user-modified settings files.
532+
fn hashes(&self) -> Vec<&str> {
533+
match self {
534+
EditorKind::Vscode => vec![
535+
"ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8",
536+
"56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922",
537+
"af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0",
538+
"3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541",
539+
"47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923",
540+
"b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a",
541+
"828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000",
542+
"811fb3b063c739d261fd8590dd30242e117908f5a095d594fa04585daa18ec4d",
543+
],
544+
EditorKind::Emacs => vec![
545+
"51068d4747a13732440d1a8b8f432603badb1864fa431d83d0fd4f8fa57039e0",
546+
"1783df575969becc95dce9d9a9f180869a7d24545815714e05af10f4f078e5c4",
547+
],
548+
}
545549
}
546-
fn make_run(run: RunConfig<'_>) {
547-
if run.builder.config.dry_run() {
548-
return;
550+
551+
fn settings_path(&self, config: &Config) -> PathBuf {
552+
config.src.join(self.settings_short_path())
553+
}
554+
555+
fn settings_short_path(&self) -> PathBuf {
556+
match self {
557+
EditorKind::Vscode => PathBuf::new().join(".vscode").join("settings.json"),
558+
EditorKind::Emacs => PathBuf::new().join(".dir-locals.el"),
549559
}
550-
if let [cmd] = &run.paths[..] {
551-
if cmd.assert_single_path().path.as_path().as_os_str() == "vscode" {
552-
run.builder.ensure(Vscode);
553-
}
560+
}
561+
562+
fn settings(&self) -> &str {
563+
match self {
564+
EditorKind::Vscode => include_str!("../../../../etc/rust_analyzer_settings.json"),
565+
EditorKind::Emacs => include_str!("../../../../etc/rust_analyzer_eglot.el"),
554566
}
555567
}
556-
fn run(self, builder: &Builder<'_>) -> Self::Output {
557-
let config = &builder.config;
558-
if config.dry_run() {
559-
return;
568+
569+
fn backup_extension(&self) -> &str {
570+
match self {
571+
EditorKind::Vscode => "json.bak",
572+
EditorKind::Emacs => "el.bak",
560573
}
561-
while !t!(create_vscode_settings_maybe(config)) {}
562574
}
563575
}
564576

565-
/// Create a `.vscode/settings.json` file for rustc development, or just print it
577+
/// Helper macro for implementing the necessary Step to support editor LSP setup
578+
/// The first argument must match the argument set up in `flags.rs`
579+
/// The second argument must match the name of some `EditorKind` variant
580+
/// After using the macro, the editor needs to be registered in `builder.rs` with describe!()
581+
macro_rules! impl_editor_support {
582+
( $($editor:ident, $kind:ident),+ ) => {$(
583+
#[doc = concat!(" Sets up or displays the LSP config for ", stringify!($editor), ".")]
584+
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
585+
pub struct $kind;
586+
587+
impl Step for $kind {
588+
type Output = ();
589+
const DEFAULT: bool = true;
590+
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
591+
run.alias(stringify!($editor))
592+
}
593+
fn make_run(run: RunConfig<'_>) {
594+
if run.builder.config.dry_run() {
595+
return;
596+
}
597+
if let [cmd] = &run.paths[..] {
598+
if cmd.assert_single_path().path.as_path().as_os_str() == stringify!($editor) {
599+
run.builder.ensure($kind);
600+
}
601+
}
602+
}
603+
fn run(self, builder: &Builder<'_>) -> Self::Output {
604+
let config = &builder.config;
605+
if config.dry_run() {
606+
return;
607+
}
608+
while !t!(create_editor_settings_maybe(config, EditorKind::$kind)) {}
609+
}
610+
}
611+
)+};
612+
}
613+
614+
impl_editor_support!(vscode, Vscode);
615+
impl_editor_support!(emacs, Emacs);
616+
617+
/// Create the recommended editor LSP config file for rustc development, or just print it
566618
/// If this method should be re-called, it returns `false`.
567-
fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
568-
let (current_hash, historical_hashes) = SETTINGS_HASHES.split_last().unwrap();
569-
let vscode_settings = config.src.join(".vscode").join("settings.json");
570-
// If None, no settings.json exists
619+
fn create_editor_settings_maybe(config: &Config, editor: EditorKind) -> io::Result<bool> {
620+
let hashes = editor.hashes();
621+
let (current_hash, historical_hashes) = hashes.split_last().unwrap();
622+
let settings_path = editor.settings_path(config);
623+
let settings_short_path = editor.settings_short_path();
624+
let settings_filename = settings_short_path.to_str().unwrap();
625+
// If None, no settings file exists
571626
// If Some(true), is a previous version of settings.json
572627
// If Some(false), is not a previous version (i.e. user modified)
573628
// If it's up to date we can just skip this
574629
let mut mismatched_settings = None;
575-
if let Ok(current) = fs::read_to_string(&vscode_settings) {
630+
if let Ok(current) = fs::read_to_string(&settings_path) {
576631
let mut hasher = sha2::Sha256::new();
577632
hasher.update(&current);
578633
let hash = hex_encode(hasher.finalize().as_slice());
@@ -585,20 +640,23 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
585640
}
586641
}
587642
println!(
588-
"\nx.py can automatically install the recommended `.vscode/settings.json` file for rustc development"
643+
"\nx.py can automatically install the recommended `{settings_filename}` file for rustc development"
589644
);
645+
590646
match mismatched_settings {
591647
Some(true) => eprintln!(
592-
"WARNING: existing `.vscode/settings.json` is out of date, x.py will update it"
648+
"WARNING: existing `{}` is out of date, x.py will update it",
649+
settings_filename
593650
),
594651
Some(false) => eprintln!(
595-
"WARNING: existing `.vscode/settings.json` has been modified by user, x.py will back it up and replace it"
652+
"WARNING: existing `{}` has been modified by user, x.py will back it up and replace it",
653+
settings_filename
596654
),
597655
_ => (),
598656
}
599-
let should_create = match prompt_user(
600-
"Would you like to create/update settings.json? (Press 'p' to preview values): [y/N]",
601-
)? {
657+
let should_create = match prompt_user(&format!(
658+
"Would you like to create/update `{settings_filename}`? (Press 'p' to preview values): [y/N]"
659+
))? {
602660
Some(PromptResult::Yes) => true,
603661
Some(PromptResult::Print) => false,
604662
_ => {
@@ -607,28 +665,32 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<bool> {
607665
}
608666
};
609667
if should_create {
610-
let path = config.src.join(".vscode");
668+
let path = settings_path.as_path().parent().unwrap();
611669
if !path.exists() {
612-
fs::create_dir(&path)?;
670+
fs::create_dir(path)?;
613671
}
614672
let verb = match mismatched_settings {
615673
// exists but outdated, we can replace this
616674
Some(true) => "Updated",
617675
// exists but user modified, back it up
618676
Some(false) => {
619677
// exists and is not current version or outdated, so back it up
620-
let mut backup = vscode_settings.clone();
621-
backup.set_extension("json.bak");
622-
eprintln!("WARNING: copying `settings.json` to `settings.json.bak`");
623-
fs::copy(&vscode_settings, &backup)?;
678+
let mut backup = settings_path.clone();
679+
backup.set_extension(editor.backup_extension());
680+
eprintln!(
681+
"WARNING: copying `{}` to `{}`",
682+
settings_path.file_name().unwrap().to_str().unwrap(),
683+
backup.file_name().unwrap().to_str().unwrap(),
684+
);
685+
fs::copy(&settings_path, &backup)?;
624686
"Updated"
625687
}
626688
_ => "Created",
627689
};
628-
fs::write(&vscode_settings, RUST_ANALYZER_SETTINGS)?;
629-
println!("{verb} `.vscode/settings.json`");
690+
fs::write(&settings_path, editor.settings())?;
691+
println!("{verb} `{}`", settings_filename);
630692
} else {
631-
println!("\n{RUST_ANALYZER_SETTINGS}");
693+
println!("\n{}", editor.settings());
632694
}
633695
Ok(should_create)
634696
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
use sha2::Digest;
22

3-
use super::{RUST_ANALYZER_SETTINGS, SETTINGS_HASHES};
3+
use super::EditorKind;
44
use crate::utils::helpers::hex_encode;
55

66
#[test]
77
fn check_matching_settings_hash() {
8+
let editor = EditorKind::Vscode;
89
let mut hasher = sha2::Sha256::new();
9-
hasher.update(&RUST_ANALYZER_SETTINGS);
10+
hasher.update(&editor.settings());
1011
let hash = hex_encode(hasher.finalize().as_slice());
1112
assert_eq!(
1213
&hash,
13-
SETTINGS_HASHES.last().unwrap(),
14-
"Update `SETTINGS_HASHES` with the new hash of `src/etc/rust_analyzer_settings.json`"
14+
editor.hashes().last().unwrap(),
15+
"Update `VSCODE_SETTINGS_HASHES` with the new hash of `src/etc/rust_analyzer_settings.json`"
1516
);
1617
}

src/bootstrap/src/core/builder.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1000,7 +1000,9 @@ impl<'a> Builder<'a> {
10001000
run::GenerateWindowsSys,
10011001
run::GenerateCompletions,
10021002
),
1003-
Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
1003+
Kind::Setup => {
1004+
describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode, setup::Emacs)
1005+
}
10041006
Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
10051007
Kind::Vendor => describe!(vendor::Vendor),
10061008
// special-cased in Build::build()

src/bootstrap/src/core/config/flags.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,11 +450,12 @@ Arguments:
450450
To only set up the git hook, VS Code config or toolchain link, you may use
451451
./x.py setup hook
452452
./x.py setup vscode
453+
./x.py setup emacs
453454
./x.py setup link", Profile::all_for_help(" ").trim_end()))]
454455
Setup {
455456
/// Either the profile for `config.toml` or another setup action.
456457
/// May be omitted to set up interactively
457-
#[arg(value_name = "<PROFILE>|hook|vscode|link")]
458+
#[arg(value_name = "<PROFILE>|hook|vscode|emacs|link")]
458459
profile: Option<PathBuf>,
459460
},
460461
/// Suggest a subset of tests to run, based on modified files

src/etc/completions/x.py.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2741,7 +2741,7 @@ _x.py() {
27412741
return 0
27422742
;;
27432743
x.py__setup)
2744-
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]..."
2744+
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|emacs|link] [PATHS]... [ARGS]..."
27452745
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
27462746
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
27472747
return 0

src/etc/rust_analyzer_eglot.el

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,31 @@
22
.((eglot-workspace-configuration
33
. (:rust-analyzer
44
( :check ( :invocationLocation "root"
5-
:invocationStrategy "once"
6-
:overrideCommand ["python3"
7-
"x.py"
8-
"check"
9-
"--json-output"])
10-
:linkedProjects ["Cargo.toml"
11-
"src/tools/x/Cargo.toml"
12-
"src/bootstrap/Cargo.toml"
13-
"src/tools/rust-analyzer/Cargo.toml"
14-
"compiler/rustc_codegen_cranelift/Cargo.toml"
15-
"compiler/rustc_codegen_gcc/Cargo.toml"]
16-
:rustfmt ( :overrideCommand ["build/host/rustfmt/bin/rustfmt"
17-
"--edition=2021"])
18-
:procMacro ( :server "build/host/stage0/libexec/rust-analyzer-proc-macro-srv"
19-
:enable t)
20-
:cargo ( :buildScripts ( :enable t
21-
:invocationLocation "root"
22-
:invocationStrategy "once"
23-
:overrideCommand ["python3"
24-
"x.py"
25-
"check"
26-
"--json-output"])
27-
:sysrootSrc "./library"
28-
:extraEnv (:RUSTC_BOOTSTRAP "1"))
29-
:rustc ( :source "./Cargo.toml" )))))))
5+
:invocationStrategy "once"
6+
:overrideCommand ["python3"
7+
"x.py"
8+
"check"
9+
"--json-output"])
10+
:linkedProjects ["Cargo.toml"
11+
"src/tools/x/Cargo.toml"
12+
"src/bootstrap/Cargo.toml"
13+
"src/tools/rust-analyzer/Cargo.toml"
14+
"compiler/rustc_codegen_cranelift/Cargo.toml"
15+
"compiler/rustc_codegen_gcc/Cargo.toml"]
16+
:rustfmt ( :overrideCommand [(concat (project-root (eglot--current-project))
17+
"build/host/rustfmt/bin/rustfmt")
18+
"--edition=2021"]
19+
)
20+
:procMacro ( :server (concat (project-root (eglot--current-project))
21+
"build/host/stage0/libexec/rust-analyzer-proc-macro-srv")
22+
:enable t)
23+
:cargo ( :buildScripts ( :enable t
24+
:invocationLocation "root"
25+
:invocationStrategy "once"
26+
:overrideCommand ["python3"
27+
"x.py"
28+
"check"
29+
"--json-output"])
30+
:sysrootSrc "./library"
31+
:extraEnv (:RUSTC_BOOTSTRAP "1"))
32+
:rustc ( :source "./Cargo.toml" )))))))

0 commit comments

Comments
 (0)