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(|| src_path.join("config.toml"));
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, Profile)> {
131 ('a'..).map(|c| c.to_string()).zip(Profile::all())
134 fn parse_with_abbrev(input: &str) -> Result<Profile, String> {
135 let input = input.trim().to_lowercase();
136 for (letter, profile) in abbrev_all() {
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());
148 let template = loop {
150 "Please choose one ({}): ",
151 abbrev_all().map(|(l, _)| l).collect::<Vec<_>>().join("/")
153 io::stdout().flush()?;
154 let mut input = String::new();
155 io::stdin().read_line(&mut input)?;
157 eprintln!("EOF on stdin, when expecting answer to question. Giving up.");
158 std::process::exit(1);
160 break match parse_with_abbrev(&input) {
161 Ok(profile) => profile,
163 println!("error: {}", err);
164 println!("note: press Ctrl+C to exit");
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();
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."
182 let should_install = loop {
183 print!("Would you like to install the git hook?: [y/N] ");
184 io::stdout().flush()?;
186 io::stdin().read_line(&mut input)?;
187 break match input.trim().to_lowercase().as_str() {
189 "n" | "no" | "" => false,
191 println!("error: unrecognized option '{}'", input.trim());
192 println!("note: press Ctrl+C to exit");
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(
202 assert!(output.status.success(), "failed to run `git`");
203 PathBuf::from(t!(String::from_utf8(output.stdout)).trim())
206 let dst = git.join("hooks").join("pre-commit");
207 match fs::hard_link(src, &dst) {
209 "error: could not create hook {}: do you already have the git hook installed?\n{}",
213 Ok(_) => println!("Linked `src/etc/pre-commit.sh` to `.git/hooks/pre-commit`"),
216 println!("Ok, skipping installation!");