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