1 use crate::builder
::{Builder, RunConfig, ShouldRun, Step}
;
3 use crate::{t, VERSION}
;
5 use std
::env
::consts
::EXE_SUFFIX
;
6 use std
::fmt
::Write
as _
;
9 use std
::path
::{Path, PathBuf, MAIN_SEPARATOR}
;
10 use std
::process
::Command
;
11 use std
::str::FromStr
;
12 use std
::{fmt, fs, io}
;
17 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
27 /// A list of historical hashes of `src/etc/vscode_settings.json`.
28 /// New entries should be appended whenever this is updated so we can detect
29 /// outdated vs. user-modified settings files.
30 static SETTINGS_HASHES
: &[&str] = &[
31 "ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8",
32 "56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922",
33 "af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0",
35 static VSCODE_SETTINGS
: &str = include_str
!("../etc/vscode_settings.json");
38 fn include_path(&self, src_path
: &Path
) -> PathBuf
{
39 PathBuf
::from(format
!("{}/src/bootstrap/defaults/config.{}.toml", src_path
.display(), self))
42 pub fn all() -> impl Iterator
<Item
= Self> {
44 // N.B. these are ordered by how they are displayed, not alphabetically
45 [Library
, Compiler
, Codegen
, Tools
, User
, None
].iter().copied()
48 pub fn purpose(&self) -> String
{
51 Library
=> "Contribute to the standard library",
52 Compiler
=> "Contribute to the compiler itself",
53 Codegen
=> "Contribute to the compiler, and also modify LLVM or codegen",
54 Tools
=> "Contribute to tools which depend on the compiler, but do not modify it directly (e.g. rustdoc, clippy, miri)",
55 User
=> "Install Rust from source",
56 None
=> "Do not modify `config.toml`"
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();
69 pub fn as_str(&self) -> &'
static str {
71 Profile
::Compiler
=> "compiler",
72 Profile
::Codegen
=> "codegen",
73 Profile
::Library
=> "library",
74 Profile
::Tools
=> "tools",
75 Profile
::User
=> "user",
76 Profile
::None
=> "none",
81 impl FromStr
for Profile
{
84 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
86 "lib" | "library" => Ok(Profile
::Library
),
87 "compiler" => Ok(Profile
::Compiler
),
88 "llvm" | "codegen" => Ok(Profile
::Codegen
),
89 "maintainer" | "user" => Ok(Profile
::User
),
90 "tools" | "tool" | "rustdoc" | "clippy" | "miri" | "rustfmt" | "rls" => {
93 "none" => Ok(Profile
::None
),
94 _
=> Err(format
!("unknown profile: '{}'", s
)),
99 impl fmt
::Display
for Profile
{
100 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
101 f
.write_str(self.as_str())
105 impl Step
for Profile
{
107 const DEFAULT
: bool
= true;
109 fn should_run(mut run
: ShouldRun
<'_
>) -> ShouldRun
<'_
> {
110 for choice
in Profile
::all() {
111 run
= run
.alias(choice
.as_str());
116 fn make_run(run
: RunConfig
<'_
>) {
117 if run
.builder
.config
.dry_run() {
121 // for Profile, `run.paths` will have 1 and only 1 element
122 // this is because we only accept at most 1 path from user input.
123 // If user calls `x.py setup` without arguments, the interactive TUI
124 // will guide user to provide one.
125 let profile
= if run
.paths
.len() > 1 {
126 // HACK: `builder` runs this step with all paths if no path was passed.
127 t
!(interactive_path())
132 .assert_single_path()
142 run
.builder
.ensure(profile
);
145 fn run(self, builder
: &Builder
<'_
>) {
146 setup(&builder
.build
.config
, self)
150 pub fn setup(config
: &Config
, profile
: Profile
) {
151 let suggestions
: &[&str] = match profile
{
152 Profile
::Codegen
| Profile
::Compiler
| Profile
::None
=> &["check", "build", "test"],
156 "test tests/rustdoc*",
157 "test src/tools/clippy",
158 "test src/tools/miri",
159 "test src/tools/rustfmt",
161 Profile
::Library
=> &["check", "build", "test library/std", "doc"],
162 Profile
::User
=> &["dist", "build"],
167 println
!("To get started, try one of the following commands:");
168 for cmd
in suggestions
{
169 println
!("- `x.py {}`", cmd
);
172 if profile
!= Profile
::User
{
174 "For more suggestions, see https://rustc-dev-guide.rust-lang.org/building/suggested.html"
178 let path
= &config
.config
.clone().unwrap_or(PathBuf
::from("config.toml"));
179 setup_config_toml(path
, profile
, config
);
182 fn setup_config_toml(path
: &PathBuf
, profile
: Profile
, config
: &Config
) {
183 if profile
== Profile
::None
{
189 "error: you asked `x.py` to setup a new config file, but one already exists at `{}`",
192 eprintln
!("help: try adding `profile = \"{}\"` at the top of {}", profile
, path
.display());
194 "note: this will use the configuration in {}",
195 profile
.include_path(&config
.src
).display()
197 crate::detail_exit(1);
200 let settings
= format
!(
201 "# Includes one of the default files in src/bootstrap/defaults\n\
203 changelog-seen = {}\n",
207 t
!(fs
::write(path
, settings
));
209 let include_path
= profile
.include_path(&config
.src
);
210 println
!("`x.py` will now use the configuration at {}", include_path
.display());
213 /// Creates a toolchain link for stage1 using `rustup`
214 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
218 const DEFAULT
: bool
= true;
219 fn should_run(run
: ShouldRun
<'_
>) -> ShouldRun
<'_
> {
222 fn make_run(run
: RunConfig
<'_
>) {
223 if run
.builder
.config
.dry_run() {
226 if let [cmd
] = &run
.paths
[..] {
227 if cmd
.assert_single_path().path
.as_path().as_os_str() == "link" {
228 run
.builder
.ensure(Link
);
232 fn run(self, builder
: &Builder
<'_
>) -> Self::Output
{
233 let config
= &builder
.config
;
234 if config
.dry_run() {
238 ["build", config
.build
.rustc_target_arg(), "stage1"].join(&MAIN_SEPARATOR
.to_string());
240 if !rustup_installed() {
241 eprintln
!("`rustup` is not installed; cannot link `stage1` toolchain");
242 } else if stage_dir_exists(&stage_path
[..]) && !config
.dry_run() {
243 attempt_toolchain_link(&stage_path
[..]);
248 fn rustup_installed() -> bool
{
249 Command
::new("rustup")
251 .stdout(std
::process
::Stdio
::null())
253 .map_or(false, |output
| output
.status
.success())
256 fn stage_dir_exists(stage_path
: &str) -> bool
{
257 match fs
::create_dir(&stage_path
) {
259 Err(_
) => Path
::new(&stage_path
).exists(),
263 fn attempt_toolchain_link(stage_path
: &str) {
264 if toolchain_is_linked() {
268 if !ensure_stage1_toolchain_placeholder_exists(stage_path
) {
270 "Failed to create a template for stage 1 toolchain or confirm that it already exists"
275 if try_link_toolchain(&stage_path
) {
277 "Added `stage1` rustup toolchain; try `cargo +stage1 build` on a separate rust project to run a newly-built toolchain"
280 eprintln
!("`rustup` failed to link stage 1 build to `stage1` toolchain");
282 "To manually link stage 1 build to `stage1` toolchain, run:\n
283 `rustup toolchain link stage1 {}`",
289 fn toolchain_is_linked() -> bool
{
290 match Command
::new("rustup")
291 .args(&["toolchain", "list"])
292 .stdout(std
::process
::Stdio
::piped())
295 Ok(toolchain_list
) => {
296 if !String
::from_utf8_lossy(&toolchain_list
.stdout
).contains("stage1") {
299 // The toolchain has already been linked.
301 "`stage1` toolchain already linked; not attempting to link `stage1` toolchain"
305 // In this case, we don't know if the `stage1` toolchain has been linked;
306 // but `rustup` failed, so let's not go any further.
308 "`rustup` failed to list current toolchains; not attempting to link `stage1` toolchain"
315 fn try_link_toolchain(stage_path
: &str) -> bool
{
316 Command
::new("rustup")
317 .stdout(std
::process
::Stdio
::null())
318 .args(&["toolchain", "link", "stage1", &stage_path
])
320 .map_or(false, |output
| output
.status
.success())
323 fn ensure_stage1_toolchain_placeholder_exists(stage_path
: &str) -> bool
{
324 let pathbuf
= PathBuf
::from(stage_path
);
326 if fs
::create_dir_all(pathbuf
.join("lib")).is_err() {
330 let pathbuf
= pathbuf
.join("bin");
331 if fs
::create_dir_all(&pathbuf
).is_err() {
335 let pathbuf
= pathbuf
.join(format
!("rustc{}", EXE_SUFFIX
));
337 if pathbuf
.exists() {
341 // Take care not to overwrite the file
342 let result
= File
::options().append(true).create(true).open(&pathbuf
);
350 // Used to get the path for `Subcommand::Setup`
351 pub fn interactive_path() -> io
::Result
<Profile
> {
352 fn abbrev_all() -> impl Iterator
<Item
= ((String
, String
), Profile
)> {
355 .map(|(letter
, number
)| (letter
.to_string(), number
.to_string()))
359 fn parse_with_abbrev(input
: &str) -> Result
<Profile
, String
> {
360 let input
= input
.trim().to_lowercase();
361 for ((letter
, number
), profile
) in abbrev_all() {
362 if input
== letter
|| input
== number
{
369 println
!("Welcome to the Rust project! What do you want to do with x.py?");
370 for ((letter
, _
), profile
) in abbrev_all() {
371 println
!("{}) {}: {}", letter
, profile
, profile
.purpose());
373 let template
= loop {
375 "Please choose one ({}): ",
376 abbrev_all().map(|((l
, _
), _
)| l
).collect
::<Vec
<_
>>().join("/")
378 io
::stdout().flush()?
;
379 let mut input
= String
::new();
380 io
::stdin().read_line(&mut input
)?
;
381 if input
.is_empty() {
382 eprintln
!("EOF on stdin, when expecting answer to question. Giving up.");
383 crate::detail_exit(1);
385 break match parse_with_abbrev(&input
) {
386 Ok(profile
) => profile
,
388 eprintln
!("error: {}", err
);
389 eprintln
!("note: press Ctrl+C to exit");
404 /// Prompt a user for a answer, looping until they enter an accepted input or nothing
405 fn prompt_user(prompt
: &str) -> io
::Result
<Option
<PromptResult
>> {
406 let mut input
= String
::new();
409 io
::stdout().flush()?
;
411 io
::stdin().read_line(&mut input
)?
;
412 match input
.trim().to_lowercase().as_str() {
413 "y" | "yes" => return Ok(Some(PromptResult
::Yes
)),
414 "n" | "no" => return Ok(Some(PromptResult
::No
)),
415 "p" | "print" => return Ok(Some(PromptResult
::Print
)),
416 "" => return Ok(None
),
418 eprintln
!("error: unrecognized option '{}'", input
.trim());
419 eprintln
!("note: press Ctrl+C to exit");
425 /// Installs `src/etc/pre-push.sh` as a Git hook
426 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
431 const DEFAULT
: bool
= true;
432 fn should_run(run
: ShouldRun
<'_
>) -> ShouldRun
<'_
> {
435 fn make_run(run
: RunConfig
<'_
>) {
436 if run
.builder
.config
.dry_run() {
439 if let [cmd
] = &run
.paths
[..] {
440 if cmd
.assert_single_path().path
.as_path().as_os_str() == "hook" {
441 run
.builder
.ensure(Hook
);
445 fn run(self, builder
: &Builder
<'_
>) -> Self::Output
{
446 let config
= &builder
.config
;
447 if config
.dry_run() {
450 t
!(install_git_hook_maybe(&config
));
454 // install a git hook to automatically run tidy, if they want
455 fn install_git_hook_maybe(config
: &Config
) -> io
::Result
<()> {
456 let git
= t
!(config
.git().args(&["rev-parse", "--git-common-dir"]).output().map(|output
| {
457 assert
!(output
.status
.success(), "failed to run `git`");
458 PathBuf
::from(t
!(String
::from_utf8(output
.stdout
)).trim())
460 let dst
= git
.join("hooks").join("pre-push");
462 // The git hook has already been set up, or the user already has a custom hook.
467 "\nRust's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
468 If you'd like, x.py can install a git hook for you that will automatically run `test tidy` before
469 pushing your code to ensure your code is up to par. If you decide later that this behavior is
470 undesirable, simply delete the `pre-push` file from .git/hooks."
473 if prompt_user("Would you like to install the git hook?: [y/N]")?
!= Some(PromptResult
::Yes
) {
474 println
!("Ok, skipping installation!");
477 let src
= config
.src
.join("src").join("etc").join("pre-push.sh");
478 match fs
::hard_link(src
, &dst
) {
481 "error: could not create hook {}: do you already have the git hook installed?\n{}",
487 Ok(_
) => println
!("Linked `src/etc/pre-push.sh` to `.git/hooks/pre-push`"),
492 /// Sets up or displays `src/etc/vscode_settings.json`
493 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
496 impl Step
for Vscode
{
498 const DEFAULT
: bool
= true;
499 fn should_run(run
: ShouldRun
<'_
>) -> ShouldRun
<'_
> {
502 fn make_run(run
: RunConfig
<'_
>) {
503 if run
.builder
.config
.dry_run() {
506 if let [cmd
] = &run
.paths
[..] {
507 if cmd
.assert_single_path().path
.as_path().as_os_str() == "vscode" {
508 run
.builder
.ensure(Vscode
);
512 fn run(self, builder
: &Builder
<'_
>) -> Self::Output
{
513 let config
= &builder
.config
;
514 if config
.dry_run() {
517 t
!(create_vscode_settings_maybe(&config
));
521 /// Create a `.vscode/settings.json` file for rustc development, or just print it
522 fn create_vscode_settings_maybe(config
: &Config
) -> io
::Result
<()> {
523 let (current_hash
, historical_hashes
) = SETTINGS_HASHES
.split_last().unwrap();
524 let vscode_settings
= config
.src
.join(".vscode").join("settings.json");
525 // If None, no settings.json exists
526 // If Some(true), is a previous version of settings.json
527 // If Some(false), is not a previous version (i.e. user modified)
528 // If it's up to date we can just skip this
529 let mut mismatched_settings
= None
;
530 if let Ok(current
) = fs
::read_to_string(&vscode_settings
) {
531 let mut hasher
= sha2
::Sha256
::new();
532 hasher
.update(¤t
);
533 let hash
= hex
::encode(hasher
.finalize().as_slice());
534 if hash
== *current_hash
{
536 } else if historical_hashes
.contains(&hash
.as_str()) {
537 mismatched_settings
= Some(true);
539 mismatched_settings
= Some(false);
543 "\nx.py can automatically install the recommended `.vscode/settings.json` file for rustc development"
545 match mismatched_settings
{
546 Some(true) => eprintln
!(
547 "warning: existing `.vscode/settings.json` is out of date, x.py will update it"
549 Some(false) => eprintln
!(
550 "warning: existing `.vscode/settings.json` has been modified by user, x.py will back it up and replace it"
554 let should_create
= match prompt_user(
555 "Would you like to create/update `settings.json`, or only print suggested settings?: [y/p/N]",
557 Some(PromptResult
::Yes
) => true,
558 Some(PromptResult
::Print
) => false,
560 println
!("Ok, skipping settings!");
565 let path
= config
.src
.join(".vscode");
567 fs
::create_dir(&path
)?
;
569 let verb
= match mismatched_settings
{
570 // exists but outdated, we can replace this
571 Some(true) => "Updated",
572 // exists but user modified, back it up
574 // exists and is not current version or outdated, so back it up
575 let mut backup
= vscode_settings
.clone();
576 backup
.set_extension("bak");
577 eprintln
!("warning: copying `settings.json` to `settings.json.bak`");
578 fs
::copy(&vscode_settings
, &backup
)?
;
583 fs
::write(&vscode_settings
, &VSCODE_SETTINGS
)?
;
584 println
!("{verb} `.vscode/settings.json`");
586 println
!("\n{VSCODE_SETTINGS}");