]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/setup.rs
Rollup merge of #77951 - ehuss:update-books, r=ehuss
[rust.git] / src / bootstrap / setup.rs
1 use crate::{t, VERSION};
2 use std::fmt::Write as _;
3 use std::path::{Path, PathBuf};
4 use std::str::FromStr;
5 use std::{
6     env, fmt, fs,
7     io::{self, Write},
8 };
9
10 #[derive(Clone, Copy, Eq, PartialEq)]
11 pub enum Profile {
12     Compiler,
13     Codegen,
14     Library,
15     User,
16 }
17
18 impl Profile {
19     fn include_path(&self, src_path: &Path) -> PathBuf {
20         PathBuf::from(format!("{}/src/bootstrap/defaults/config.{}.toml", src_path.display(), self))
21     }
22
23     pub fn all() -> impl Iterator<Item = Self> {
24         use Profile::*;
25         // N.B. these are ordered by how they are displayed, not alphabetically
26         [Library, Compiler, Codegen, User].iter().copied()
27     }
28
29     pub fn purpose(&self) -> String {
30         use Profile::*;
31         match self {
32             Library => "Contribute to the standard library",
33             Compiler => "Contribute to the compiler or rustdoc",
34             Codegen => "Contribute to the compiler, and also modify LLVM or codegen",
35             User => "Install Rust from source",
36         }
37         .to_string()
38     }
39
40     pub fn all_for_help(indent: &str) -> String {
41         let mut out = String::new();
42         for choice in Profile::all() {
43             writeln!(&mut out, "{}{}: {}", indent, choice, choice.purpose()).unwrap();
44         }
45         out
46     }
47 }
48
49 impl FromStr for Profile {
50     type Err = String;
51
52     fn from_str(s: &str) -> Result<Self, Self::Err> {
53         match s {
54             "lib" | "library" => Ok(Profile::Library),
55             "compiler" | "rustdoc" => Ok(Profile::Compiler),
56             "llvm" | "codegen" => Ok(Profile::Codegen),
57             "maintainer" | "user" => Ok(Profile::User),
58             _ => Err(format!("unknown profile: '{}'", s)),
59         }
60     }
61 }
62
63 impl fmt::Display for Profile {
64     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65         match self {
66             Profile::Compiler => write!(f, "compiler"),
67             Profile::Codegen => write!(f, "codegen"),
68             Profile::Library => write!(f, "library"),
69             Profile::User => write!(f, "user"),
70         }
71     }
72 }
73
74 pub fn setup(src_path: &Path, profile: Profile) {
75     let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
76
77     if cfg_file.as_ref().map_or(false, |f| f.exists()) {
78         let file = cfg_file.unwrap();
79         println!(
80             "error: you asked `x.py` to setup a new config file, but one already exists at `{}`",
81             file.display()
82         );
83         println!("help: try adding `profile = \"{}\"` at the top of {}", profile, file.display());
84         println!(
85             "note: this will use the configuration in {}",
86             profile.include_path(src_path).display()
87         );
88         std::process::exit(1);
89     }
90
91     let path = cfg_file.unwrap_or_else(|| src_path.join("config.toml"));
92     let settings = format!(
93         "# Includes one of the default files in src/bootstrap/defaults\n\
94     profile = \"{}\"\n\
95     changelog-seen = {}\n",
96         profile, VERSION
97     );
98     t!(fs::write(path, settings));
99
100     let include_path = profile.include_path(src_path);
101     println!("`x.py` will now use the configuration at {}", include_path.display());
102
103     let suggestions = match profile {
104         Profile::Codegen | Profile::Compiler => &["check", "build", "test"][..],
105         Profile::Library => &["check", "build", "test library/std", "doc"],
106         Profile::User => &["dist", "build"],
107     };
108
109     println!();
110
111     t!(install_git_hook_maybe(src_path));
112
113     println!();
114
115     println!("To get started, try one of the following commands:");
116     for cmd in suggestions {
117         println!("- `x.py {}`", cmd);
118     }
119
120     if profile != Profile::User {
121         println!(
122             "For more suggestions, see https://rustc-dev-guide.rust-lang.org/building/suggested.html"
123         );
124     }
125 }
126
127 // Used to get the path for `Subcommand::Setup`
128 pub fn interactive_path() -> io::Result<Profile> {
129     fn abbrev_all() -> impl Iterator<Item = (String, Profile)> {
130         ('a'..).map(|c| c.to_string()).zip(Profile::all())
131     }
132
133     fn parse_with_abbrev(input: &str) -> Result<Profile, String> {
134         let input = input.trim().to_lowercase();
135         for (letter, profile) in abbrev_all() {
136             if input == letter {
137                 return Ok(profile);
138             }
139         }
140         input.parse()
141     }
142
143     println!("Welcome to the Rust project! What do you want to do with x.py?");
144     for (letter, profile) in abbrev_all() {
145         println!("{}) {}: {}", letter, profile, profile.purpose());
146     }
147     let template = loop {
148         print!(
149             "Please choose one ({}): ",
150             abbrev_all().map(|(l, _)| l).collect::<Vec<_>>().join("/")
151         );
152         io::stdout().flush()?;
153         let mut input = String::new();
154         io::stdin().read_line(&mut input)?;
155         if input == "" {
156             eprintln!("EOF on stdin, when expecting answer to question.  Giving up.");
157             std::process::exit(1);
158         }
159         break match parse_with_abbrev(&input) {
160             Ok(profile) => profile,
161             Err(err) => {
162                 println!("error: {}", err);
163                 println!("note: press Ctrl+C to exit");
164                 continue;
165             }
166         };
167     };
168     Ok(template)
169 }
170
171 // install a git hook to automatically run tidy --bless, if they want
172 fn install_git_hook_maybe(src_path: &Path) -> io::Result<()> {
173     let mut input = String::new();
174     println!(
175         "Rust's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
176 If you'd like, x.py can install a git hook for you that will automatically run `tidy --bless` on each commit
177 to ensure your code is up to par. If you decide later that this behavior is undesirable,
178 simply delete the `pre-commit` file from .git/hooks."
179     );
180
181     let should_install = loop {
182         print!("Would you like to install the git hook?: [y/N] ");
183         io::stdout().flush()?;
184         input.clear();
185         io::stdin().read_line(&mut input)?;
186         break match input.trim().to_lowercase().as_str() {
187             "y" | "yes" => true,
188             "n" | "no" | "" => false,
189             _ => {
190                 println!("error: unrecognized option '{}'", input.trim());
191                 println!("note: press Ctrl+C to exit");
192                 continue;
193             }
194         };
195     };
196
197     Ok(if should_install {
198         let src = src_path.join("src").join("etc").join("pre-commit.sh");
199         let dst = src_path.join(".git").join("hooks").join("pre-commit");
200         match fs::hard_link(src, dst) {
201             Err(e) => println!(
202                 "x.py encountered an error -- do you already have the git hook installed?\n{}",
203                 e
204             ),
205             Ok(_) => println!("Linked `src/etc/pre-commit.sh` to `.git/hooks/pre-commit`"),
206         };
207     } else {
208         println!("Ok, skipping installation!");
209     })
210 }