]>
git.proxmox.com Git - rustc.git/blob - src/bootstrap/format.rs
1 //! Runs rustfmt on the repository.
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
;
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");
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
27 let status
= cmd
.wait().unwrap();
28 if !status
.success() {
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.",
35 crate::detail_exit(1);
40 #[derive(serde::Deserialize)]
41 struct RustfmtConfig
{
45 pub fn format(build
: &Builder
<'_
>, check
: bool
, paths
: &[PathBuf
]) {
46 if build
.config
.dry_run
{
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.");
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
);
65 let git_available
= match Command
::new("git")
67 .stdout(Stdio
::null())
68 .stderr(Stdio
::null())
71 Ok(status
) => status
.success(),
75 let in_working_tree
= match build
79 .arg("--is-inside-work-tree")
80 .stdout(Stdio
::null())
81 .stderr(Stdio
::null())
84 Ok(status
) => status
.success(),
88 let untracked_paths_output
= output(
89 build
.config
.git().arg("status").arg("--porcelain").arg("--untracked-files=normal"),
91 let untracked_paths
= untracked_paths_output
93 .filter(|entry
| entry
.starts_with("??"))
95 entry
.split(' '
).nth(1).expect("every git status entry should list a path")
97 for untracked_path
in untracked_paths
{
98 println
!("skip untracked path {} during rustfmt invocations", untracked_path
);
99 // The leading `/` makes it an exact match against the
100 // repository root, rather than a glob. Without that, if you
101 // have `foo.rs` in the repository root it will also match
102 // against anything like `compiler/rustc_foo/src/foo.rs`,
103 // preventing the latter from being formatted.
104 ignore_fmt
.add(&format
!("!/{}", untracked_path
)).expect(&untracked_path
);
107 println
!("Not in git tree. Skipping git-aware format checks");
110 println
!("Could not find usable git. Skipping git-aware format checks");
112 let ignore_fmt
= ignore_fmt
.build().unwrap();
114 let rustfmt_path
= build
.initial_rustfmt().unwrap_or_else(|| {
115 eprintln
!("./x.py fmt is not supported on this channel");
116 crate::detail_exit(1);
118 assert
!(rustfmt_path
.exists(), "{}", rustfmt_path
.display());
119 let src
= build
.src
.clone();
120 let (tx
, rx
): (SyncSender
<PathBuf
>, _
) = std
::sync
::mpsc
::sync_channel(128);
121 let walker
= match paths
.get(0) {
123 let mut walker
= WalkBuilder
::new(first
);
124 for path
in &paths
[1..] {
129 None
=> WalkBuilder
::new(src
.clone()),
132 .overrides(ignore_fmt
)
135 // there is a lot of blocking involved in spawning a child process and reading files to format.
136 // spawn more processes than available concurrency to keep the CPU busy
137 let max_processes
= build
.jobs() as usize * 2;
139 // spawn child processes on a separate thread so we can batch entries we have received from ignore
140 let thread
= std
::thread
::spawn(move || {
141 let mut children
= VecDeque
::new();
142 while let Ok(path
) = rx
.recv() {
143 // try getting a few more paths from the channel to amortize the overhead of spawning processes
144 let paths
: Vec
<_
> = rx
.try_iter().take(7).chain(std
::iter
::once(path
)).collect();
146 let child
= rustfmt(&src
, &rustfmt_path
, paths
.as_slice(), check
);
147 children
.push_back(child
);
149 if children
.len() >= max_processes
{
150 // await oldest child
151 children
.pop_front().unwrap()();
155 // await remaining children
156 for mut child
in children
{
163 Box
::new(move |entry
| {
164 let entry
= t
!(entry
);
165 if entry
.file_type().map_or(false, |t
| t
.is_file()) {
166 t
!(tx
.send(entry
.into_path()));
168 ignore
::WalkState
::Continue
174 thread
.join().unwrap();