]> git.proxmox.com Git - rustc.git/blob - src/bootstrap/format.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / src / bootstrap / format.rs
1 //! Runs rustfmt on the repository.
2
3 use crate::builder::Builder;
4 use crate::util::{output, t};
5 use ignore::WalkBuilder;
6 use std::collections::VecDeque;
7 use std::path::{Path, PathBuf};
8 use std::process::{Command, Stdio};
9 use std::sync::mpsc::SyncSender;
10
11 fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl FnMut() {
12 let mut cmd = Command::new(&rustfmt);
13 // avoid the submodule config paths from coming into play,
14 // we only allow a single global config for the workspace for now
15 cmd.arg("--config-path").arg(&src.canonicalize().unwrap());
16 cmd.arg("--edition").arg("2021");
17 cmd.arg("--unstable-features");
18 cmd.arg("--skip-children");
19 if check {
20 cmd.arg("--check");
21 }
22 cmd.args(paths);
23 let cmd_debug = format!("{:?}", cmd);
24 let mut cmd = cmd.spawn().expect("running rustfmt");
25 // poor man's async: return a closure that'll wait for rustfmt's completion
26 move || {
27 let status = cmd.wait().unwrap();
28 if !status.success() {
29 eprintln!(
30 "Running `{}` failed.\nIf you're running `tidy`, \
31 try again with `--bless`. Or, if you just want to format \
32 code, run `./x.py fmt` instead.",
33 cmd_debug,
34 );
35 std::process::exit(1);
36 }
37 }
38 }
39
40 #[derive(serde::Deserialize)]
41 struct RustfmtConfig {
42 ignore: Vec<String>,
43 }
44
45 pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
46 if build.config.dry_run {
47 return;
48 }
49 let mut builder = ignore::types::TypesBuilder::new();
50 builder.add_defaults();
51 builder.select("rust");
52 let matcher = builder.build().unwrap();
53 let rustfmt_config = build.src.join("rustfmt.toml");
54 if !rustfmt_config.exists() {
55 eprintln!("Not running formatting checks; rustfmt.toml does not exist.");
56 eprintln!("This may happen in distributed tarballs.");
57 return;
58 }
59 let rustfmt_config = t!(std::fs::read_to_string(&rustfmt_config));
60 let rustfmt_config: RustfmtConfig = t!(toml::from_str(&rustfmt_config));
61 let mut ignore_fmt = ignore::overrides::OverrideBuilder::new(&build.src);
62 for ignore in rustfmt_config.ignore {
63 ignore_fmt.add(&format!("!{}", ignore)).expect(&ignore);
64 }
65 let git_available = match Command::new("git")
66 .arg("--version")
67 .stdout(Stdio::null())
68 .stderr(Stdio::null())
69 .status()
70 {
71 Ok(status) => status.success(),
72 Err(_) => false,
73 };
74 if git_available {
75 let in_working_tree = match Command::new("git")
76 .arg("rev-parse")
77 .arg("--is-inside-work-tree")
78 .stdout(Stdio::null())
79 .stderr(Stdio::null())
80 .status()
81 {
82 Ok(status) => status.success(),
83 Err(_) => false,
84 };
85 if in_working_tree {
86 let untracked_paths_output = output(
87 Command::new("git")
88 .arg("status")
89 .arg("--porcelain")
90 .arg("--untracked-files=normal"),
91 );
92 let untracked_paths = untracked_paths_output
93 .lines()
94 .filter(|entry| entry.starts_with("??"))
95 .map(|entry| {
96 entry.split(' ').nth(1).expect("every git status entry should list a path")
97 });
98 for untracked_path in untracked_paths {
99 println!("skip untracked path {} during rustfmt invocations", untracked_path);
100 // The leading `/` makes it an exact match against the
101 // repository root, rather than a glob. Without that, if you
102 // have `foo.rs` in the repository root it will also match
103 // against anything like `compiler/rustc_foo/src/foo.rs`,
104 // preventing the latter from being formatted.
105 ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path);
106 }
107 } else {
108 println!("Not in git tree. Skipping git-aware format checks");
109 }
110 } else {
111 println!("Could not find usable git. Skipping git-aware format checks");
112 }
113 let ignore_fmt = ignore_fmt.build().unwrap();
114
115 let rustfmt_path = build.initial_rustfmt().unwrap_or_else(|| {
116 eprintln!("./x.py fmt is not supported on this channel");
117 std::process::exit(1);
118 });
119 assert!(rustfmt_path.exists(), "{}", rustfmt_path.display());
120 let src = build.src.clone();
121 let (tx, rx): (SyncSender<PathBuf>, _) = std::sync::mpsc::sync_channel(128);
122 let walker = match paths.get(0) {
123 Some(first) => {
124 let mut walker = WalkBuilder::new(first);
125 for path in &paths[1..] {
126 walker.add(path);
127 }
128 walker
129 }
130 None => WalkBuilder::new(src.clone()),
131 }
132 .types(matcher)
133 .overrides(ignore_fmt)
134 .build_parallel();
135
136 // there is a lot of blocking involved in spawning a child process and reading files to format.
137 // spawn more processes than available concurrency to keep the CPU busy
138 let max_processes = build.jobs() as usize * 2;
139
140 // spawn child processes on a separate thread so we can batch entries we have received from ignore
141 let thread = std::thread::spawn(move || {
142 let mut children = VecDeque::new();
143 while let Ok(path) = rx.recv() {
144 // try getting a few more paths from the channel to amortize the overhead of spawning processes
145 let paths: Vec<_> = rx.try_iter().take(7).chain(std::iter::once(path)).collect();
146
147 let child = rustfmt(&src, &rustfmt_path, paths.as_slice(), check);
148 children.push_back(child);
149
150 if children.len() >= max_processes {
151 // await oldest child
152 children.pop_front().unwrap()();
153 }
154 }
155
156 // await remaining children
157 for mut child in children {
158 child();
159 }
160 });
161
162 walker.run(|| {
163 let tx = tx.clone();
164 Box::new(move |entry| {
165 let entry = t!(entry);
166 if entry.file_type().map_or(false, |t| t.is_file()) {
167 t!(tx.send(entry.into_path()));
168 }
169 ignore::WalkState::Continue
170 })
171 });
172
173 drop(tx);
174
175 thread.join().unwrap();
176 }