1 use crate::{t, VERSION};
2 use std::fmt::Write as _;
3 use std::path::{Path, PathBuf};
4 use std::process::Command;
11 #[derive(Clone, Copy, Eq, PartialEq)]
20 fn include_path(&self, src_path: &Path) -> PathBuf {
21 PathBuf::from(format!("{}/src/bootstrap/defaults/config.{}.toml", src_path.display(), self))
24 pub fn all() -> impl Iterator<Item = Self> {
26 // N.B. these are ordered by how they are displayed, not alphabetically
27 [Library, Compiler, Codegen, User].iter().copied()
30 pub fn purpose(&self) -> String {
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",
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();
50 impl FromStr for Profile {
53 fn from_str(s: &str) -> Result<Self, Self::Err> {
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)),
64 impl fmt::Display for Profile {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 Profile::Compiler => write!(f, "compiler"),
68 Profile::Codegen => write!(f, "codegen"),
69 Profile::Library => write!(f, "library"),
70 Profile::User => write!(f, "user"),
75 pub fn setup(src_path: &Path, profile: Profile) {
76 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
78 if cfg_file.as_ref().map_or(false, |f| f.exists()) {
79 let file = cfg_file.unwrap();
81 "error: you asked `x.py` to setup a new config file, but one already exists at `{}`",
84 println!("help: try adding `profile = \"{}\"` at the top of {}", profile, file.display());
86 "note: this will use the configuration in {}",
87 profile.include_path(src_path).display()
89 std::process::exit(1);
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\
96 changelog-seen = {}\n",
99 t!(fs::write(path, settings));
101 let include_path = profile.include_path(src_path);
102 println!("`x.py` will now use the configuration at {}", include_path.display());
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"],
112 t!(install_git_hook_maybe(src_path));
116 println!("To get started, try one of the following commands:");
117 for cmd in suggestions {
118 println!("- `x.py {}`", cmd);
121 if profile != Profile::User {
123 "For more suggestions, see https://rustc-dev-guide.rust-lang.org/building/suggested.html"
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)> {
133 .map(|(letter, number)| (letter.to_string(), number.to_string()))
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 {
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());
151 let template = loop {
153 "Please choose one ({}): ",
154 abbrev_all().map(|((l, _), _)| l).collect::<Vec<_>>().join("/")
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);
163 break match parse_with_abbrev(&input) {
164 Ok(profile) => profile,
166 println!("error: {}", err);
167 println!("note: press Ctrl+C to exit");
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();
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."
185 let should_install = loop {
186 print!("Would you like to install the git hook?: [y/N] ");
187 io::stdout().flush()?;
189 io::stdin().read_line(&mut input)?;
190 break match input.trim().to_lowercase().as_str() {
192 "n" | "no" | "" => false,
194 println!("error: unrecognized option '{}'", input.trim());
195 println!("note: press Ctrl+C to exit");
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(
205 assert!(output.status.success(), "failed to run `git`");
206 PathBuf::from(t!(String::from_utf8(output.stdout)).trim())
209 let dst = git.join("hooks").join("pre-commit");
210 match fs::hard_link(src, &dst) {
212 "error: could not create hook {}: do you already have the git hook installed?\n{}",
216 Ok(_) => println!("Linked `src/etc/pre-commit.sh` to `.git/hooks/pre-commit`"),
219 println!("Ok, skipping installation!");