1 use crate::{t, VERSION};
2 use std::fmt::Write as _;
3 use std::path::{Path, PathBuf};
10 #[derive(Clone, Copy, Eq, PartialEq)]
19 fn include_path(&self, src_path: &Path) -> PathBuf {
20 PathBuf::from(format!("{}/src/bootstrap/defaults/config.{}.toml", src_path.display(), self))
23 pub fn all() -> impl Iterator<Item = Self> {
25 // N.B. these are ordered by how they are displayed, not alphabetically
26 [Library, Compiler, Codegen, User].iter().copied()
29 pub fn purpose(&self) -> String {
32 Library => "Contribute to the standard library",
33 Compiler => "Contribute to the compiler or rustdoc",
34 Codegen => "Contribute to the compiler, and also modify LLVM or codegen",
35 User => "Install Rust from source",
40 pub fn all_for_help(indent: &str) -> String {
41 let mut out = String::new();
42 for choice in Profile::all() {
43 writeln!(&mut out, "{}{}: {}", indent, choice, choice.purpose()).unwrap();
49 impl FromStr for Profile {
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
54 "lib" | "library" => Ok(Profile::Library),
55 "compiler" | "rustdoc" => Ok(Profile::Compiler),
56 "llvm" | "codegen" => Ok(Profile::Codegen),
57 "maintainer" | "user" => Ok(Profile::User),
58 _ => Err(format!("unknown profile: '{}'", s)),
63 impl fmt::Display for Profile {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 Profile::Compiler => write!(f, "compiler"),
67 Profile::Codegen => write!(f, "codegen"),
68 Profile::Library => write!(f, "library"),
69 Profile::User => write!(f, "user"),
74 pub fn setup(src_path: &Path, profile: Profile) {
75 let cfg_file = env::var_os("BOOTSTRAP_CONFIG").map(PathBuf::from);
77 if cfg_file.as_ref().map_or(false, |f| f.exists()) {
78 let file = cfg_file.unwrap();
80 "error: you asked `x.py` to setup a new config file, but one already exists at `{}`",
83 println!("help: try adding `profile = \"{}\"` at the top of {}", profile, file.display());
85 "note: this will use the configuration in {}",
86 profile.include_path(src_path).display()
88 std::process::exit(1);
91 let path = cfg_file.unwrap_or_else(|| src_path.join("config.toml"));
92 let settings = format!(
93 "# Includes one of the default files in src/bootstrap/defaults\n\
95 changelog-seen = {}\n",
98 t!(fs::write(path, settings));
100 let include_path = profile.include_path(src_path);
101 println!("`x.py` will now use the configuration at {}", include_path.display());
103 let suggestions = match profile {
104 Profile::Codegen | Profile::Compiler => &["check", "build", "test"][..],
105 Profile::Library => &["check", "build", "test library/std", "doc"],
106 Profile::User => &["dist", "build"],
111 t!(install_git_hook_maybe(src_path));
115 println!("To get started, try one of the following commands:");
116 for cmd in suggestions {
117 println!("- `x.py {}`", cmd);
120 if profile != Profile::User {
122 "For more suggestions, see https://rustc-dev-guide.rust-lang.org/building/suggested.html"
127 // Used to get the path for `Subcommand::Setup`
128 pub fn interactive_path() -> io::Result<Profile> {
129 fn abbrev_all() -> impl Iterator<Item = (String, Profile)> {
130 ('a'..).map(|c| c.to_string()).zip(Profile::all())
133 fn parse_with_abbrev(input: &str) -> Result<Profile, String> {
134 let input = input.trim().to_lowercase();
135 for (letter, profile) in abbrev_all() {
143 println!("Welcome to the Rust project! What do you want to do with x.py?");
144 for (letter, profile) in abbrev_all() {
145 println!("{}) {}: {}", letter, profile, profile.purpose());
147 let template = loop {
149 "Please choose one ({}): ",
150 abbrev_all().map(|(l, _)| l).collect::<Vec<_>>().join("/")
152 io::stdout().flush()?;
153 let mut input = String::new();
154 io::stdin().read_line(&mut input)?;
156 eprintln!("EOF on stdin, when expecting answer to question. Giving up.");
157 std::process::exit(1);
159 break match parse_with_abbrev(&input) {
160 Ok(profile) => profile,
162 println!("error: {}", err);
163 println!("note: press Ctrl+C to exit");
171 // install a git hook to automatically run tidy --bless, if they want
172 fn install_git_hook_maybe(src_path: &Path) -> io::Result<()> {
173 let mut input = String::new();
175 "Rust's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
176 If you'd like, x.py can install a git hook for you that will automatically run `tidy --bless` on each commit
177 to ensure your code is up to par. If you decide later that this behavior is undesirable,
178 simply delete the `pre-commit` file from .git/hooks."
181 let should_install = loop {
182 print!("Would you like to install the git hook?: [y/N] ");
183 io::stdout().flush()?;
185 io::stdin().read_line(&mut input)?;
186 break match input.trim().to_lowercase().as_str() {
188 "n" | "no" | "" => false,
190 println!("error: unrecognized option '{}'", input.trim());
191 println!("note: press Ctrl+C to exit");
197 Ok(if should_install {
198 let src = src_path.join("src").join("etc").join("pre-commit.sh");
199 let dst = src_path.join(".git").join("hooks").join("pre-commit");
200 match fs::hard_link(src, dst) {
202 "x.py encountered an error -- do you already have the git hook installed?\n{}",
205 Ok(_) => println!("Linked `src/etc/pre-commit.sh` to `.git/hooks/pre-commit`"),
208 println!("Ok, skipping installation!");