]>
Commit | Line | Data |
---|---|---|
29967ef6 | 1 | use crate::{t, VERSION}; |
5e7ed085 | 2 | use crate::{Config, TargetSelection}; |
5099ac24 | 3 | use std::env::consts::EXE_SUFFIX; |
29967ef6 | 4 | use std::fmt::Write as _; |
5099ac24 FG |
5 | use std::fs::File; |
6 | use std::path::{Path, PathBuf, MAIN_SEPARATOR}; | |
29967ef6 XL |
7 | use std::process::Command; |
8 | use std::str::FromStr; | |
1b1a35ee | 9 | use std::{ |
29967ef6 | 10 | env, fmt, fs, |
1b1a35ee XL |
11 | io::{self, Write}, |
12 | }; | |
13 | ||
29967ef6 XL |
14 | #[derive(Clone, Copy, Eq, PartialEq)] |
15 | pub enum Profile { | |
16 | Compiler, | |
17 | Codegen, | |
18 | Library, | |
cdc7bbd5 | 19 | Tools, |
29967ef6 XL |
20 | User, |
21 | } | |
22 | ||
23 | impl Profile { | |
24 | fn include_path(&self, src_path: &Path) -> PathBuf { | |
25 | PathBuf::from(format!("{}/src/bootstrap/defaults/config.{}.toml", src_path.display(), self)) | |
26 | } | |
27 | ||
28 | pub fn all() -> impl Iterator<Item = Self> { | |
29 | use Profile::*; | |
30 | // N.B. these are ordered by how they are displayed, not alphabetically | |
cdc7bbd5 | 31 | [Library, Compiler, Codegen, Tools, User].iter().copied() |
29967ef6 XL |
32 | } |
33 | ||
34 | pub fn purpose(&self) -> String { | |
35 | use Profile::*; | |
36 | match self { | |
37 | Library => "Contribute to the standard library", | |
cdc7bbd5 | 38 | Compiler => "Contribute to the compiler itself", |
29967ef6 | 39 | Codegen => "Contribute to the compiler, and also modify LLVM or codegen", |
cdc7bbd5 | 40 | Tools => "Contribute to tools which depend on the compiler, but do not modify it directly (e.g. rustdoc, clippy, miri)", |
29967ef6 XL |
41 | User => "Install Rust from source", |
42 | } | |
43 | .to_string() | |
44 | } | |
45 | ||
46 | pub fn all_for_help(indent: &str) -> String { | |
47 | let mut out = String::new(); | |
48 | for choice in Profile::all() { | |
49 | writeln!(&mut out, "{}{}: {}", indent, choice, choice.purpose()).unwrap(); | |
50 | } | |
51 | out | |
52 | } | |
53 | } | |
54 | ||
55 | impl FromStr for Profile { | |
56 | type Err = String; | |
57 | ||
58 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
59 | match s { | |
60 | "lib" | "library" => Ok(Profile::Library), | |
cdc7bbd5 | 61 | "compiler" => Ok(Profile::Compiler), |
29967ef6 XL |
62 | "llvm" | "codegen" => Ok(Profile::Codegen), |
63 | "maintainer" | "user" => Ok(Profile::User), | |
cdc7bbd5 XL |
64 | "tools" | "tool" | "rustdoc" | "clippy" | "miri" | "rustfmt" | "rls" => { |
65 | Ok(Profile::Tools) | |
66 | } | |
29967ef6 XL |
67 | _ => Err(format!("unknown profile: '{}'", s)), |
68 | } | |
69 | } | |
70 | } | |
71 | ||
72 | impl fmt::Display for Profile { | |
73 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
74 | match self { | |
75 | Profile::Compiler => write!(f, "compiler"), | |
76 | Profile::Codegen => write!(f, "codegen"), | |
77 | Profile::Library => write!(f, "library"), | |
78 | Profile::User => write!(f, "user"), | |
cdc7bbd5 | 79 | Profile::Tools => write!(f, "tools"), |
29967ef6 XL |
80 | } |
81 | } | |
82 | } | |
83 | ||
5e7ed085 FG |
84 | pub fn setup(config: &Config, profile: Profile) { |
85 | let path = &config.config; | |
1b1a35ee | 86 | |
5e7ed085 | 87 | if path.exists() { |
1b1a35ee XL |
88 | println!( |
89 | "error: you asked `x.py` to setup a new config file, but one already exists at `{}`", | |
5e7ed085 | 90 | path.display() |
1b1a35ee | 91 | ); |
5e7ed085 | 92 | println!("help: try adding `profile = \"{}\"` at the top of {}", profile, path.display()); |
1b1a35ee | 93 | println!( |
29967ef6 | 94 | "note: this will use the configuration in {}", |
5e7ed085 | 95 | profile.include_path(&config.src).display() |
1b1a35ee XL |
96 | ); |
97 | std::process::exit(1); | |
98 | } | |
99 | ||
1b1a35ee XL |
100 | let settings = format!( |
101 | "# Includes one of the default files in src/bootstrap/defaults\n\ | |
29967ef6 XL |
102 | profile = \"{}\"\n\ |
103 | changelog-seen = {}\n", | |
104 | profile, VERSION | |
1b1a35ee XL |
105 | ); |
106 | t!(fs::write(path, settings)); | |
107 | ||
5e7ed085 | 108 | let include_path = profile.include_path(&config.src); |
29967ef6 | 109 | println!("`x.py` will now use the configuration at {}", include_path.display()); |
1b1a35ee | 110 | |
c295e0f8 | 111 | let build = TargetSelection::from_user(&env!("BUILD_TRIPLE")); |
5099ac24 FG |
112 | let stage_path = |
113 | ["build", build.rustc_target_arg(), "stage1"].join(&MAIN_SEPARATOR.to_string()); | |
c295e0f8 XL |
114 | |
115 | println!(); | |
116 | ||
117 | if !rustup_installed() && profile != Profile::User { | |
118 | println!("`rustup` is not installed; cannot link `stage1` toolchain"); | |
119 | } else if stage_dir_exists(&stage_path[..]) { | |
120 | attempt_toolchain_link(&stage_path[..]); | |
121 | } | |
122 | ||
29967ef6 XL |
123 | let suggestions = match profile { |
124 | Profile::Codegen | Profile::Compiler => &["check", "build", "test"][..], | |
cdc7bbd5 XL |
125 | Profile::Tools => &[ |
126 | "check", | |
127 | "build", | |
128 | "test src/test/rustdoc*", | |
129 | "test src/tools/clippy", | |
130 | "test src/tools/miri", | |
131 | "test src/tools/rustfmt", | |
132 | ], | |
29967ef6 XL |
133 | Profile::Library => &["check", "build", "test library/std", "doc"], |
134 | Profile::User => &["dist", "build"], | |
1b1a35ee XL |
135 | }; |
136 | ||
29967ef6 XL |
137 | println!(); |
138 | ||
5e7ed085 | 139 | t!(install_git_hook_maybe(&config.src)); |
29967ef6 XL |
140 | |
141 | println!(); | |
142 | ||
1b1a35ee XL |
143 | println!("To get started, try one of the following commands:"); |
144 | for cmd in suggestions { | |
145 | println!("- `x.py {}`", cmd); | |
146 | } | |
147 | ||
29967ef6 | 148 | if profile != Profile::User { |
1b1a35ee XL |
149 | println!( |
150 | "For more suggestions, see https://rustc-dev-guide.rust-lang.org/building/suggested.html" | |
151 | ); | |
152 | } | |
153 | } | |
154 | ||
c295e0f8 XL |
155 | fn rustup_installed() -> bool { |
156 | Command::new("rustup") | |
157 | .arg("--version") | |
158 | .stdout(std::process::Stdio::null()) | |
159 | .output() | |
160 | .map_or(false, |output| output.status.success()) | |
161 | } | |
162 | ||
163 | fn stage_dir_exists(stage_path: &str) -> bool { | |
5e7ed085 | 164 | match fs::create_dir(&stage_path) { |
c295e0f8 | 165 | Ok(_) => true, |
5e7ed085 | 166 | Err(_) => Path::new(&stage_path).exists(), |
c295e0f8 XL |
167 | } |
168 | } | |
169 | ||
170 | fn attempt_toolchain_link(stage_path: &str) { | |
171 | if toolchain_is_linked() { | |
172 | return; | |
173 | } | |
174 | ||
5099ac24 FG |
175 | if !ensure_stage1_toolchain_placeholder_exists(stage_path) { |
176 | println!( | |
177 | "Failed to create a template for stage 1 toolchain or confirm that it already exists" | |
178 | ); | |
179 | return; | |
180 | } | |
181 | ||
5e7ed085 | 182 | if try_link_toolchain(&stage_path) { |
c295e0f8 XL |
183 | println!( |
184 | "Added `stage1` rustup toolchain; try `cargo +stage1 build` on a separate rust project to run a newly-built toolchain" | |
185 | ); | |
186 | } else { | |
187 | println!("`rustup` failed to link stage 1 build to `stage1` toolchain"); | |
188 | println!( | |
189 | "To manually link stage 1 build to `stage1` toolchain, run:\n | |
190 | `rustup toolchain link stage1 {}`", | |
5e7ed085 | 191 | &stage_path |
c295e0f8 XL |
192 | ); |
193 | } | |
194 | } | |
195 | ||
196 | fn toolchain_is_linked() -> bool { | |
197 | match Command::new("rustup") | |
198 | .args(&["toolchain", "list"]) | |
199 | .stdout(std::process::Stdio::piped()) | |
200 | .output() | |
201 | { | |
202 | Ok(toolchain_list) => { | |
203 | if !String::from_utf8_lossy(&toolchain_list.stdout).contains("stage1") { | |
204 | return false; | |
205 | } | |
206 | // The toolchain has already been linked. | |
207 | println!( | |
208 | "`stage1` toolchain already linked; not attempting to link `stage1` toolchain" | |
209 | ); | |
210 | } | |
211 | Err(_) => { | |
212 | // In this case, we don't know if the `stage1` toolchain has been linked; | |
213 | // but `rustup` failed, so let's not go any further. | |
214 | println!( | |
215 | "`rustup` failed to list current toolchains; not attempting to link `stage1` toolchain" | |
216 | ); | |
217 | } | |
218 | } | |
219 | true | |
220 | } | |
221 | ||
222 | fn try_link_toolchain(stage_path: &str) -> bool { | |
223 | Command::new("rustup") | |
224 | .stdout(std::process::Stdio::null()) | |
5e7ed085 | 225 | .args(&["toolchain", "link", "stage1", &stage_path]) |
c295e0f8 XL |
226 | .output() |
227 | .map_or(false, |output| output.status.success()) | |
228 | } | |
229 | ||
5099ac24 FG |
230 | fn ensure_stage1_toolchain_placeholder_exists(stage_path: &str) -> bool { |
231 | let pathbuf = PathBuf::from(stage_path); | |
232 | ||
233 | if fs::create_dir_all(pathbuf.join("lib")).is_err() { | |
234 | return false; | |
235 | }; | |
236 | ||
237 | let pathbuf = pathbuf.join("bin"); | |
238 | if fs::create_dir_all(&pathbuf).is_err() { | |
239 | return false; | |
240 | }; | |
241 | ||
242 | let pathbuf = pathbuf.join(format!("rustc{}", EXE_SUFFIX)); | |
243 | ||
244 | if pathbuf.exists() { | |
245 | return true; | |
246 | } | |
247 | ||
248 | // Take care not to overwrite the file | |
249 | let result = File::options().append(true).create(true).open(&pathbuf); | |
250 | if result.is_err() { | |
251 | return false; | |
252 | } | |
253 | ||
254 | return true; | |
255 | } | |
256 | ||
1b1a35ee | 257 | // Used to get the path for `Subcommand::Setup` |
29967ef6 XL |
258 | pub fn interactive_path() -> io::Result<Profile> { |
259 | fn abbrev_all() -> impl Iterator<Item = ((String, String), Profile)> { | |
260 | ('a'..) | |
261 | .zip(1..) | |
262 | .map(|(letter, number)| (letter.to_string(), number.to_string())) | |
263 | .zip(Profile::all()) | |
264 | } | |
265 | ||
266 | fn parse_with_abbrev(input: &str) -> Result<Profile, String> { | |
267 | let input = input.trim().to_lowercase(); | |
268 | for ((letter, number), profile) in abbrev_all() { | |
269 | if input == letter || input == number { | |
270 | return Ok(profile); | |
271 | } | |
272 | } | |
273 | input.parse() | |
274 | } | |
275 | ||
276 | println!("Welcome to the Rust project! What do you want to do with x.py?"); | |
277 | for ((letter, _), profile) in abbrev_all() { | |
278 | println!("{}) {}: {}", letter, profile, profile.purpose()); | |
279 | } | |
280 | let template = loop { | |
281 | print!( | |
282 | "Please choose one ({}): ", | |
283 | abbrev_all().map(|((l, _), _)| l).collect::<Vec<_>>().join("/") | |
284 | ); | |
285 | io::stdout().flush()?; | |
286 | let mut input = String::new(); | |
287 | io::stdin().read_line(&mut input)?; | |
5869c6ff | 288 | if input.is_empty() { |
29967ef6 XL |
289 | eprintln!("EOF on stdin, when expecting answer to question. Giving up."); |
290 | std::process::exit(1); | |
291 | } | |
292 | break match parse_with_abbrev(&input) { | |
293 | Ok(profile) => profile, | |
294 | Err(err) => { | |
295 | println!("error: {}", err); | |
296 | println!("note: press Ctrl+C to exit"); | |
297 | continue; | |
298 | } | |
299 | }; | |
300 | }; | |
301 | Ok(template) | |
302 | } | |
303 | ||
304 | // install a git hook to automatically run tidy --bless, if they want | |
305 | fn install_git_hook_maybe(src_path: &Path) -> io::Result<()> { | |
1b1a35ee XL |
306 | let mut input = String::new(); |
307 | println!( | |
29967ef6 | 308 | "Rust's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality. |
5099ac24 FG |
309 | If you'd like, x.py can install a git hook for you that will automatically run `tidy --bless` before |
310 | pushing your code to ensure your code is up to par. If you decide later that this behavior is | |
311 | undesirable, simply delete the `pre-push` file from .git/hooks." | |
1b1a35ee | 312 | ); |
29967ef6 XL |
313 | |
314 | let should_install = loop { | |
315 | print!("Would you like to install the git hook?: [y/N] "); | |
1b1a35ee | 316 | io::stdout().flush()?; |
29967ef6 | 317 | input.clear(); |
1b1a35ee XL |
318 | io::stdin().read_line(&mut input)?; |
319 | break match input.trim().to_lowercase().as_str() { | |
29967ef6 XL |
320 | "y" | "yes" => true, |
321 | "n" | "no" | "" => false, | |
1b1a35ee XL |
322 | _ => { |
323 | println!("error: unrecognized option '{}'", input.trim()); | |
324 | println!("note: press Ctrl+C to exit"); | |
325 | continue; | |
326 | } | |
327 | }; | |
328 | }; | |
29967ef6 | 329 | |
fc512014 | 330 | if should_install { |
5099ac24 | 331 | let src = src_path.join("src").join("etc").join("pre-push.sh"); |
29967ef6 XL |
332 | let git = t!(Command::new("git").args(&["rev-parse", "--git-common-dir"]).output().map( |
333 | |output| { | |
334 | assert!(output.status.success(), "failed to run `git`"); | |
335 | PathBuf::from(t!(String::from_utf8(output.stdout)).trim()) | |
336 | } | |
337 | )); | |
5099ac24 | 338 | let dst = git.join("hooks").join("pre-push"); |
29967ef6 XL |
339 | match fs::hard_link(src, &dst) { |
340 | Err(e) => println!( | |
341 | "error: could not create hook {}: do you already have the git hook installed?\n{}", | |
342 | dst.display(), | |
343 | e | |
344 | ), | |
04454e1e | 345 | Ok(_) => println!("Linked `src/etc/pre-push.sh` to `.git/hooks/pre-push`"), |
29967ef6 XL |
346 | }; |
347 | } else { | |
348 | println!("Ok, skipping installation!"); | |
fc512014 XL |
349 | } |
350 | Ok(()) | |
1b1a35ee | 351 | } |