]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/setup.rs
Add support for rustc-env and unset-rustc-env for aux-builds
[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(|| src_path.join("config.toml"));
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, Profile)> {
131         ('a'..).map(|c| c.to_string()).zip(Profile::all())
132     }
133
134     fn parse_with_abbrev(input: &str) -> Result<Profile, String> {
135         let input = input.trim().to_lowercase();
136         for (letter, profile) in abbrev_all() {
137             if input == letter {
138                 return Ok(profile);
139             }
140         }
141         input.parse()
142     }
143
144     println!("Welcome to the Rust project! What do you want to do with x.py?");
145     for (letter, profile) in abbrev_all() {
146         println!("{}) {}: {}", letter, profile, profile.purpose());
147     }
148     let template = loop {
149         print!(
150             "Please choose one ({}): ",
151             abbrev_all().map(|(l, _)| l).collect::<Vec<_>>().join("/")
152         );
153         io::stdout().flush()?;
154         let mut input = String::new();
155         io::stdin().read_line(&mut input)?;
156         if input == "" {
157             eprintln!("EOF on stdin, when expecting answer to question.  Giving up.");
158             std::process::exit(1);
159         }
160         break match parse_with_abbrev(&input) {
161             Ok(profile) => profile,
162             Err(err) => {
163                 println!("error: {}", err);
164                 println!("note: press Ctrl+C to exit");
165                 continue;
166             }
167         };
168     };
169     Ok(template)
170 }
171
172 // install a git hook to automatically run tidy --bless, if they want
173 fn install_git_hook_maybe(src_path: &Path) -> io::Result<()> {
174     let mut input = String::new();
175     println!(
176         "Rust's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
177 If you'd like, x.py can install a git hook for you that will automatically run `tidy --bless` on each commit
178 to ensure your code is up to par. If you decide later that this behavior is undesirable,
179 simply delete the `pre-commit` file from .git/hooks."
180     );
181
182     let should_install = loop {
183         print!("Would you like to install the git hook?: [y/N] ");
184         io::stdout().flush()?;
185         input.clear();
186         io::stdin().read_line(&mut input)?;
187         break match input.trim().to_lowercase().as_str() {
188             "y" | "yes" => true,
189             "n" | "no" | "" => false,
190             _ => {
191                 println!("error: unrecognized option '{}'", input.trim());
192                 println!("note: press Ctrl+C to exit");
193                 continue;
194             }
195         };
196     };
197
198     Ok(if should_install {
199         let src = src_path.join("src").join("etc").join("pre-commit.sh");
200         let git = t!(Command::new("git").args(&["rev-parse", "--git-common-dir"]).output().map(
201             |output| {
202                 assert!(output.status.success(), "failed to run `git`");
203                 PathBuf::from(t!(String::from_utf8(output.stdout)).trim())
204             }
205         ));
206         let dst = git.join("hooks").join("pre-commit");
207         match fs::hard_link(src, &dst) {
208             Err(e) => println!(
209                 "error: could not create hook {}: do you already have the git hook installed?\n{}",
210                 dst.display(),
211                 e
212             ),
213             Ok(_) => println!("Linked `src/etc/pre-commit.sh` to `.git/hooks/pre-commit`"),
214         };
215     } else {
216         println!("Ok, skipping installation!");
217     })
218 }