1use std::env::consts::EXE_SUFFIX;
9use std::fmt::Write as _;
10use std::fs::File;
11use std::io::Write;
12use std::path::{MAIN_SEPARATOR_STR, Path, PathBuf};
13use std::str::FromStr;
14use std::{fmt, fs, io};
15
16use sha2::Digest;
17
18use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
19use crate::utils::change_tracker::CONFIG_CHANGE_HISTORY;
20use crate::utils::exec::command;
21use crate::utils::helpers::{self, hex_encode};
22use crate::{Config, t};
23
24#[cfg(test)]
25mod tests;
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
28pub enum Profile {
29 Compiler,
30 Library,
31 Tools,
32 Dist,
33 None,
34}
35
36static PROFILE_DIR: &str = "src/bootstrap/defaults";
37
38impl Profile {
39 fn include_path(&self, src_path: &Path) -> PathBuf {
40 PathBuf::from(format!("{}/{PROFILE_DIR}/bootstrap.{}.toml", src_path.display(), self))
41 }
42
43 pub fn all() -> impl Iterator<Item = Self> {
44 use Profile::*;
45 [Library, Compiler, Tools, Dist, None].iter().copied()
47 }
48
49 pub fn purpose(&self) -> String {
50 use Profile::*;
51 match self {
52 Library => "Contribute to the standard library",
53 Compiler => "Contribute to the compiler itself",
54 Tools => "Contribute to tools which depend on the compiler, but do not modify it directly (e.g. rustdoc, clippy, miri)",
55 Dist => "Install Rust from source",
56 None => "Do not modify `bootstrap.toml`"
57 }
58 .to_string()
59 }
60
61 pub fn all_for_help(indent: &str) -> String {
62 let mut out = String::new();
63 for choice in Profile::all() {
64 writeln!(&mut out, "{}{}: {}", indent, choice, choice.purpose()).unwrap();
65 }
66 out
67 }
68
69 pub fn as_str(&self) -> &'static str {
70 match self {
71 Profile::Compiler => "compiler",
72 Profile::Library => "library",
73 Profile::Tools => "tools",
74 Profile::Dist => "dist",
75 Profile::None => "none",
76 }
77 }
78}
79
80impl FromStr for Profile {
81 type Err = String;
82
83 fn from_str(s: &str) -> Result<Self, Self::Err> {
84 match s {
85 "lib" | "library" => Ok(Profile::Library),
86 "compiler" => Ok(Profile::Compiler),
87 "maintainer" | "dist" | "user" => Ok(Profile::Dist),
88 "tools" | "tool" | "rustdoc" | "clippy" | "miri" | "rustfmt" => Ok(Profile::Tools),
89 "none" => Ok(Profile::None),
90 "llvm" | "codegen" => Err("the \"llvm\" and \"codegen\" profiles have been removed,\
91 use \"compiler\" instead which has the same functionality"
92 .to_string()),
93 _ => Err(format!("unknown profile: '{s}'")),
94 }
95 }
96}
97
98impl fmt::Display for Profile {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 f.write_str(self.as_str())
101 }
102}
103
104impl Step for Profile {
105 type Output = ();
106
107 fn should_run(mut run: ShouldRun<'_>) -> ShouldRun<'_> {
108 for choice in Profile::all() {
109 run = run.alias(choice.as_str());
110 }
111 run
112 }
113
114 fn is_default_step(_builder: &Builder<'_>) -> bool {
115 true
116 }
117
118 fn make_run(run: RunConfig<'_>) {
119 if run.builder.config.dry_run() {
120 return;
121 }
122
123 let path = &run.builder.config.config.clone().unwrap_or(PathBuf::from("bootstrap.toml"));
124 if path.exists() {
125 eprintln!();
126 eprintln!(
127 "ERROR: you asked for a new config file, but one already exists at `{}`",
128 t!(path.canonicalize()).display()
129 );
130
131 match prompt_user(
132 "Do you wish to override the existing configuration (which will allow the setup process to continue)?: [y/N]",
133 ) {
134 Ok(Some(PromptResult::Yes)) => {
135 t!(fs::remove_file(path));
136 }
137 _ => {
138 println!("Exiting.");
139 crate::exit!(1);
140 }
141 }
142 }
143
144 let profile = if run.paths.len() > 1 {
149 t!(interactive_path())
151 } else {
152 run.paths
153 .first()
154 .unwrap()
155 .assert_single_path()
156 .path
157 .as_path()
158 .as_os_str()
159 .to_str()
160 .unwrap()
161 .parse()
162 .unwrap()
163 };
164
165 run.builder.ensure(profile);
166 }
167
168 fn run(self, builder: &Builder<'_>) {
169 setup(&builder.build.config, self);
170 }
171}
172
173pub fn setup(config: &Config, profile: Profile) {
174 let suggestions: &[&str] = match profile {
175 Profile::Compiler | Profile::None => &["check", "build", "test"],
176 Profile::Tools => &[
177 "check",
178 "build",
179 "test tests/rustdoc*",
180 "test src/tools/clippy",
181 "test src/tools/miri",
182 "test src/tools/rustfmt",
183 ],
184 Profile::Library => &["check", "build", "test library/std", "doc"],
185 Profile::Dist => &["dist", "build"],
186 };
187
188 println!();
189
190 println!("To get started, try one of the following commands:");
191 for cmd in suggestions {
192 println!("- `x.py {cmd}`");
193 }
194
195 if profile != Profile::Dist {
196 println!(
197 "For more suggestions, see https://rustc-dev-guide.rust-lang.org/building/suggested.html"
198 );
199 }
200
201 if profile == Profile::Tools {
202 eprintln!();
203 eprintln!(
204 "NOTE: the `tools` profile sets up the `stage2` toolchain (use \
205 `rustup toolchain link 'name' build/host/stage2` to use rustc)"
206 )
207 }
208
209 let path = &config.config.clone().unwrap_or(PathBuf::from("bootstrap.toml"));
210 setup_config_toml(path, profile, config);
211}
212
213fn setup_config_toml(path: &Path, profile: Profile, config: &Config) {
214 if profile == Profile::None {
215 return;
216 }
217
218 let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id;
219 let settings = format!(
220 "# See bootstrap.example.toml for documentation of available options\n\
221 #\n\
222 profile = \"{profile}\" # Includes one of the default files in {PROFILE_DIR}\n\
223 change-id = {latest_change_id}\n"
224 );
225
226 t!(fs::write(path, settings));
227
228 let include_path = profile.include_path(&config.src);
229 println!("`x.py` will now use the configuration at {}", include_path.display());
230}
231
232#[derive(Clone, Debug, Eq, PartialEq, Hash)]
234pub struct Link;
235impl Step for Link {
236 type Output = ();
237
238 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
239 run.alias("link")
240 }
241
242 fn is_default_step(_builder: &Builder<'_>) -> bool {
243 true
244 }
245
246 fn make_run(run: RunConfig<'_>) {
247 if run.builder.config.dry_run() {
248 return;
249 }
250 if let [cmd] = &run.paths[..]
251 && cmd.assert_single_path().path.as_path().as_os_str() == "link"
252 {
253 run.builder.ensure(Link);
254 }
255 }
256 fn run(self, builder: &Builder<'_>) -> Self::Output {
257 let config = &builder.config;
258
259 if config.dry_run() {
260 return;
261 }
262
263 if !rustup_installed(builder) {
264 println!("WARNING: `rustup` is not installed; Skipping `stage1` toolchain linking.");
265 return;
266 }
267
268 let stage_path =
269 ["build", config.host_target.rustc_target_arg(), "stage1"].join(MAIN_SEPARATOR_STR);
270
271 if stage_dir_exists(&stage_path[..]) && !config.dry_run() {
272 attempt_toolchain_link(builder, &stage_path[..]);
273 }
274 }
275}
276
277fn rustup_installed(builder: &Builder<'_>) -> bool {
278 let mut rustup = command("rustup");
279 rustup.arg("--version");
280
281 rustup.allow_failure().run_in_dry_run().run_capture_stdout(builder).is_success()
282}
283
284fn stage_dir_exists(stage_path: &str) -> bool {
285 match fs::create_dir(stage_path) {
286 Ok(_) => true,
287 Err(_) => Path::new(&stage_path).exists(),
288 }
289}
290
291fn attempt_toolchain_link(builder: &Builder<'_>, stage_path: &str) {
292 if toolchain_is_linked(builder) {
293 return;
294 }
295
296 if !ensure_stage1_toolchain_placeholder_exists(stage_path) {
297 eprintln!(
298 "Failed to create a template for stage 1 toolchain or confirm that it already exists"
299 );
300 return;
301 }
302
303 if try_link_toolchain(builder, stage_path) {
304 println!(
305 "Added `stage1` rustup toolchain; try `cargo +stage1 build` on a separate rust project to run a newly-built toolchain"
306 );
307 } else {
308 eprintln!("`rustup` failed to link stage 1 build to `stage1` toolchain");
309 eprintln!(
310 "To manually link stage 1 build to `stage1` toolchain, run:\n
311 `rustup toolchain link stage1 {}`",
312 &stage_path
313 );
314 }
315}
316
317fn toolchain_is_linked(builder: &Builder<'_>) -> bool {
318 match command("rustup")
319 .allow_failure()
320 .args(["toolchain", "list"])
321 .run_capture_stdout(builder)
322 .stdout_if_ok()
323 {
324 Some(toolchain_list) => {
325 if !toolchain_list.contains("stage1") {
326 return false;
327 }
328 println!(
330 "`stage1` toolchain already linked; not attempting to link `stage1` toolchain"
331 );
332 }
333 None => {
334 println!(
337 "`rustup` failed to list current toolchains; not attempting to link `stage1` toolchain"
338 );
339 }
340 }
341 true
342}
343
344fn try_link_toolchain(builder: &Builder<'_>, stage_path: &str) -> bool {
345 command("rustup")
346 .args(["toolchain", "link", "stage1", stage_path])
347 .run_capture_stdout(builder)
348 .is_success()
349}
350
351fn ensure_stage1_toolchain_placeholder_exists(stage_path: &str) -> bool {
352 let pathbuf = PathBuf::from(stage_path);
353
354 if fs::create_dir_all(pathbuf.join("lib")).is_err() {
355 return false;
356 };
357
358 let pathbuf = pathbuf.join("bin");
359 if fs::create_dir_all(&pathbuf).is_err() {
360 return false;
361 };
362
363 let pathbuf = pathbuf.join(format!("rustc{EXE_SUFFIX}"));
364
365 if pathbuf.exists() {
366 return true;
367 }
368
369 let result = File::options().append(true).create(true).open(&pathbuf);
371 if result.is_err() {
372 return false;
373 }
374
375 true
376}
377
378pub fn interactive_path() -> io::Result<Profile> {
380 fn abbrev_all() -> impl Iterator<Item = ((String, String), Profile)> {
381 ('a'..)
382 .zip(1..)
383 .map(|(letter, number)| (letter.to_string(), number.to_string()))
384 .zip(Profile::all())
385 }
386
387 fn parse_with_abbrev(input: &str) -> Result<Profile, String> {
388 let input = input.trim().to_lowercase();
389 for ((letter, number), profile) in abbrev_all() {
390 if input == letter || input == number {
391 return Ok(profile);
392 }
393 }
394 input.parse()
395 }
396
397 println!("Welcome to the Rust project! What do you want to do with x.py?");
398 for ((letter, _), profile) in abbrev_all() {
399 println!("{}) {}: {}", letter, profile, profile.purpose());
400 }
401 let template = loop {
402 print!(
403 "Please choose one ({}): ",
404 abbrev_all().map(|((l, _), _)| l).collect::<Vec<_>>().join("/")
405 );
406 io::stdout().flush()?;
407 let mut input = String::new();
408 io::stdin().read_line(&mut input)?;
409 if input.is_empty() {
410 eprintln!("EOF on stdin, when expecting answer to question. Giving up.");
411 crate::exit!(1);
412 }
413 break match parse_with_abbrev(&input) {
414 Ok(profile) => profile,
415 Err(err) => {
416 eprintln!("ERROR: {err}");
417 eprintln!("NOTE: press Ctrl+C to exit");
418 continue;
419 }
420 };
421 };
422 Ok(template)
423}
424
425#[derive(PartialEq)]
426enum PromptResult {
427 Yes, No, Print, }
431
432fn prompt_user(prompt: &str) -> io::Result<Option<PromptResult>> {
434 let mut input = String::new();
435 loop {
436 print!("{prompt} ");
437 io::stdout().flush()?;
438 input.clear();
439 io::stdin().read_line(&mut input)?;
440 match input.trim().to_lowercase().as_str() {
441 "y" | "yes" => return Ok(Some(PromptResult::Yes)),
442 "n" | "no" => return Ok(Some(PromptResult::No)),
443 "p" | "print" => return Ok(Some(PromptResult::Print)),
444 "" => return Ok(None),
445 _ => {
446 eprintln!("ERROR: unrecognized option '{}'", input.trim());
447 eprintln!("NOTE: press Ctrl+C to exit");
448 }
449 };
450 }
451}
452
453#[derive(Clone, Debug, Eq, PartialEq, Hash)]
455pub struct Hook;
456
457impl Step for Hook {
458 type Output = ();
459
460 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
461 run.alias("hook")
462 }
463
464 fn is_default_step(_builder: &Builder<'_>) -> bool {
465 true
466 }
467
468 fn make_run(run: RunConfig<'_>) {
469 if let [cmd] = &run.paths[..]
470 && cmd.assert_single_path().path.as_path().as_os_str() == "hook"
471 {
472 run.builder.ensure(Hook);
473 }
474 }
475
476 fn run(self, builder: &Builder<'_>) -> Self::Output {
477 let config = &builder.config;
478
479 if config.dry_run() || !config.rust_info.is_managed_git_subrepository() {
480 return;
481 }
482
483 t!(install_git_hook_maybe(builder, config));
484 }
485}
486
487fn install_git_hook_maybe(builder: &Builder<'_>, config: &Config) -> io::Result<()> {
489 let git = helpers::git(Some(&config.src))
490 .args(["rev-parse", "--git-common-dir"])
491 .run_capture(builder)
492 .stdout();
493 let git = PathBuf::from(git.trim());
494 let hooks_dir = git.join("hooks");
495 let dst = hooks_dir.join("pre-push");
496 if dst.exists() {
497 return Ok(());
499 }
500
501 println!(
502 "\nRust's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
503If you'd like, x.py can install a git hook for you that will automatically run `test tidy` before
504pushing your code to ensure your code is up to par. If you decide later that this behavior is
505undesirable, simply delete the `pre-push` file from .git/hooks."
506 );
507
508 if prompt_user("Would you like to install the git hook?: [y/N]")? != Some(PromptResult::Yes) {
509 println!("Ok, skipping installation!");
510 return Ok(());
511 }
512 if !hooks_dir.exists() {
513 let _ = fs::create_dir(hooks_dir);
515 }
516 let src = config.src.join("src").join("etc").join("pre-push.sh");
517 match fs::hard_link(src, &dst) {
518 Err(e) => {
519 eprintln!(
520 "ERROR: could not create hook {}: do you already have the git hook installed?\n{}",
521 dst.display(),
522 e
523 );
524 return Err(e);
525 }
526 Ok(_) => println!("Linked `src/etc/pre-push.sh` to `.git/hooks/pre-push`"),
527 };
528 Ok(())
529}
530
531#[derive(Clone, Debug, Eq, PartialEq)]
533enum EditorKind {
534 Emacs,
535 Helix,
536 Vim,
537 VsCode,
538 Zed,
539}
540
541impl EditorKind {
542 #[cfg(test)]
544 pub const ALL: &[EditorKind] = &[
545 EditorKind::Emacs,
546 EditorKind::Helix,
547 EditorKind::Vim,
548 EditorKind::VsCode,
549 EditorKind::Zed,
550 ];
551
552 fn prompt_user() -> io::Result<Option<EditorKind>> {
553 let prompt_str = "Available editors:
5541. Emacs
5552. Helix
5563. Vim
5574. VS Code
5585. Zed
559
560Select which editor you would like to set up [default: None]: ";
561
562 let mut input = String::new();
563 loop {
564 print!("{prompt_str}");
565 io::stdout().flush()?;
566 io::stdin().read_line(&mut input)?;
567
568 let mut modified_input = input.to_lowercase();
569 modified_input.retain(|ch| !ch.is_whitespace());
570 match modified_input.as_str() {
571 "1" | "emacs" => return Ok(Some(EditorKind::Emacs)),
572 "2" | "helix" => return Ok(Some(EditorKind::Helix)),
573 "3" | "vim" => return Ok(Some(EditorKind::Vim)),
574 "4" | "vscode" => return Ok(Some(EditorKind::VsCode)),
575 "5" | "zed" => return Ok(Some(EditorKind::Zed)),
576 "" | "none" => return Ok(None),
577 _ => {
578 eprintln!("ERROR: unrecognized option '{}'", input.trim());
579 eprintln!("NOTE: press Ctrl+C to exit");
580 }
581 }
582
583 input.clear();
584 }
585 }
586
587 fn hashes(&self) -> &'static [&'static str] {
591 match self {
592 EditorKind::Emacs => &[
593 "51068d4747a13732440d1a8b8f432603badb1864fa431d83d0fd4f8fa57039e0",
594 "d29af4d949bbe2371eac928a3c31cf9496b1701aa1c45f11cd6c759865ad5c45",
595 "b5dd299b93dca3ceeb9b335f929293cb3d4bf4977866fbe7ceeac2a8a9f99088",
596 "631c837b0e98ae35fd48b0e5f743b1ca60adadf2d0a2b23566ba25df372cf1a9",
597 "080955765db84bb6cbf178879f489c4e2369397626a6ecb3debedb94a9d0b3ce",
598 "f501475c6654187091c924ae26187fa5791d74d4a8ab3fb61fbbe4c0275aade1",
599 "54bc48fe1996177f5eef86d7231b33978e6d8b737cb0a899e622b7e975c95308",
600 "08d30e455ceec6e01d9bcef8b9449f2ddd14d278ca8627cdad90e02d9f44e938",
601 ],
602 EditorKind::Helix => &[
603 "2d3069b8cf1b977e5d4023965eb6199597755e6c96c185ed5f2854f98b83d233",
604 "6736d61409fbebba0933afd2e4c44ff2f97c1cb36cf0299a7f4a7819b8775040",
605 "f252dcc30ca85a193a699581e5e929d5bd6c19d40d7a7ade5e257a9517a124a5",
606 "198c195ed0c070d15907b279b8b4ea96198ca71b939f5376454f3d636ab54da5",
607 "1c43ead340b20792b91d02b08494ee68708e7e09f56b6766629b4b72079208f1",
608 "eec09a09452682060afd23dd5d3536ccac5615b3cdbf427366446901215fb9f6",
609 "cb653043852d9d5ff4a5be56407b859ff9928be055ad3f307eb309aad04765e6",
610 ],
611 EditorKind::Vim | EditorKind::VsCode => &[
612 "ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8",
613 "56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922",
614 "af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0",
615 "3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541",
616 "47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923",
617 "b526bd58d0262dd4dda2bff5bc5515b705fb668a46235ace3e057f807963a11a",
618 "828666b021d837a33e78d870b56d34c88a5e2c85de58b693607ec574f0c27000",
619 "811fb3b063c739d261fd8590dd30242e117908f5a095d594fa04585daa18ec4d",
620 "4eecb58a2168b252077369da446c30ed0e658301efe69691979d1ef0443928f4",
621 "c394386e6133bbf29ffd32c8af0bb3d4aac354cba9ee051f29612aa9350f8f8d",
622 "e53e9129ca5ee5dcbd6ec8b68c2d87376474eb154992deba3c6d9ab1703e0717",
623 "f954316090936c7e590c253ca9d524008375882fa13c5b41d7e2547a896ff893",
624 "701b73751efd7abd6487f2c79348dab698af7ac4427b79fa3d2087c867144b12",
625 "a61df796c0c007cb6512127330564e49e57d558dec715703916a928b072a1054",
626 "02a49ac2d31f00ef6e4531c44e00dac51cea895112e480553f1ba060b3942a47",
627 "0aa4748848de0d1cb7ece92a0123c8897fef6de2f58aff8fda1426f098b7a798",
628 "e5e357862e5d6d0d9da335e9823c07b8a7dc42bbf18d72cc5206ad1049cd8fcc",
629 ],
630 EditorKind::Zed => &[
631 "bbce727c269d1bd0c98afef4d612eb4ce27aea3c3a8968c5f10b31affbc40b6c",
632 "a5380cf5dd9328731aecc5dfb240d16dac46ed272126b9728006151ef42f5909",
633 "2e96bf0d443852b12f016c8fc9840ab3d0a2b4fe0b0fb3a157e8d74d5e7e0e26",
634 "4fadd4c87389a601a27db0d3d74a142fa3a2e656ae78982e934dbe24bee32ad6",
635 "f0bb3d23ab1a49175ab0ef5c4071af95bb03d01d460776cdb716d91333443382",
636 "5ef83292111d9a8bb63b6afc3abf42d0bc78fe24985f0d2e039e73258b5dab8f",
637 "74420c13094b530a986b37c4f1d23cb58c0e8e2295f5858ded129fb1574e66f9",
638 "2d3b592c089b2ad2c528686a1e371af49922edad1c59accd5d5f31612a441568",
639 ],
640 }
641 }
642
643 fn settings_path(&self, config: &Config) -> PathBuf {
644 config.src.join(self.settings_short_path())
645 }
646
647 fn settings_short_path(&self) -> PathBuf {
648 self.settings_folder().join(match self {
649 EditorKind::Emacs => ".dir-locals.el",
650 EditorKind::Helix => "languages.toml",
651 EditorKind::Vim => "coc-settings.json",
652 EditorKind::VsCode | EditorKind::Zed => "settings.json",
653 })
654 }
655
656 fn settings_folder(&self) -> PathBuf {
657 match self {
658 EditorKind::Emacs => PathBuf::new(),
659 EditorKind::Helix => PathBuf::from(".helix"),
660 EditorKind::Vim => PathBuf::from(".vim"),
661 EditorKind::VsCode => PathBuf::from(".vscode"),
662 EditorKind::Zed => PathBuf::from(".zed"),
663 }
664 }
665
666 fn settings_template(&self) -> &'static str {
667 match self {
668 EditorKind::Emacs => include_str!("../../../../etc/rust_analyzer_eglot.el"),
669 EditorKind::Helix => include_str!("../../../../etc/rust_analyzer_helix.toml"),
670 EditorKind::Vim | EditorKind::VsCode => {
671 include_str!("../../../../etc/rust_analyzer_settings.json")
672 }
673 EditorKind::Zed => include_str!("../../../../etc/rust_analyzer_zed.json"),
674 }
675 }
676
677 fn backup_extension(&self) -> String {
678 format!("{}.bak", self.settings_short_path().extension().unwrap().to_str().unwrap())
679 }
680}
681
682#[derive(Clone, Debug, Eq, PartialEq, Hash)]
684pub struct Editor;
685
686impl Step for Editor {
687 type Output = ();
688
689 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
690 run.alias("editor")
691 }
692
693 fn is_default_step(_builder: &Builder<'_>) -> bool {
694 true
695 }
696
697 fn make_run(run: RunConfig<'_>) {
698 if run.builder.config.dry_run() {
699 return;
700 }
701 if let [cmd] = &run.paths[..]
702 && cmd.assert_single_path().path.as_path().as_os_str() == "editor"
703 {
704 run.builder.ensure(Editor);
705 }
706 }
707
708 fn run(self, builder: &Builder<'_>) -> Self::Output {
709 let config = &builder.config;
710 if config.dry_run() {
711 return;
712 }
713 match EditorKind::prompt_user() {
714 Ok(editor_kind) => {
715 if let Some(editor_kind) = editor_kind {
716 while !t!(create_editor_settings_maybe(config, &editor_kind)) {}
717 } else {
718 println!("Ok, skipping editor setup!");
719 }
720 }
721 Err(e) => eprintln!("Could not determine the editor: {e}"),
722 }
723 }
724}
725
726fn create_editor_settings_maybe(config: &Config, editor: &EditorKind) -> io::Result<bool> {
729 let hashes = editor.hashes();
730 let (current_hash, historical_hashes) = hashes.split_last().unwrap();
731 let settings_path = editor.settings_path(config);
732 let settings_short_path = editor.settings_short_path();
733 let settings_filename = settings_short_path.to_str().unwrap();
734 let mut mismatched_settings = None;
739 if let Ok(current) = fs::read_to_string(&settings_path) {
740 let mut hasher = sha2::Sha256::new();
741 hasher.update(¤t);
742 let hash = hex_encode(hasher.finalize().as_slice());
743 if hash == *current_hash {
744 return Ok(true);
745 } else if historical_hashes.contains(&hash.as_str()) {
746 mismatched_settings = Some(true);
747 } else {
748 mismatched_settings = Some(false);
749 }
750 }
751 println!(
752 "\nx.py can automatically install the recommended `{settings_filename}` file for rustc development"
753 );
754
755 match mismatched_settings {
756 Some(true) => {
757 eprintln!("WARNING: existing `{settings_filename}` is out of date, x.py will update it")
758 }
759 Some(false) => eprintln!(
760 "WARNING: existing `{settings_filename}` has been modified by user, x.py will back it up and replace it"
761 ),
762 _ => (),
763 }
764 let should_create = match prompt_user(&format!(
765 "Would you like to create/update `{settings_filename}`? (Press 'p' to preview values): [y/N]"
766 ))? {
767 Some(PromptResult::Yes) => true,
768 Some(PromptResult::Print) => false,
769 _ => {
770 println!("Ok, skipping settings!");
771 return Ok(true);
772 }
773 };
774 if should_create {
775 let settings_folder_path = config.src.join(editor.settings_folder());
776 if !settings_folder_path.exists() {
777 fs::create_dir(settings_folder_path)?;
778 }
779 let verb = match mismatched_settings {
780 Some(true) => "Updated",
782 Some(false) => {
784 let backup = settings_path.with_extension(editor.backup_extension());
786 eprintln!(
787 "WARNING: copying `{}` to `{}`",
788 settings_path.file_name().unwrap().to_str().unwrap(),
789 backup.file_name().unwrap().to_str().unwrap(),
790 );
791 fs::copy(&settings_path, &backup)?;
792 "Updated"
793 }
794 _ => "Created",
795 };
796 fs::write(&settings_path, editor.settings_template())?;
797 println!("{verb} `{settings_filename}`");
798 } else {
799 println!("\n{}", editor.settings_template());
800 }
801 Ok(should_create)
802}