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