]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | //! Runs rustfmt on the repository. |
2 | ||
923072b8 | 3 | use crate::builder::Builder; |
5e7ed085 | 4 | use crate::util::{output, t}; |
dfeec247 | 5 | use ignore::WalkBuilder; |
6a06907d XL |
6 | use std::collections::VecDeque; |
7 | use std::path::{Path, PathBuf}; | |
ba9703b0 | 8 | use std::process::{Command, Stdio}; |
6a06907d | 9 | use std::sync::mpsc::SyncSender; |
dfeec247 | 10 | |
6a06907d | 11 | fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl FnMut() { |
dfeec247 XL |
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()); | |
5e7ed085 | 16 | cmd.arg("--edition").arg("2021"); |
dfeec247 XL |
17 | cmd.arg("--unstable-features"); |
18 | cmd.arg("--skip-children"); | |
19 | if check { | |
20 | cmd.arg("--check"); | |
21 | } | |
6a06907d | 22 | cmd.args(paths); |
dfeec247 | 23 | let cmd_debug = format!("{:?}", cmd); |
6a06907d XL |
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 | ); | |
064997fb | 35 | crate::detail_exit(1); |
6a06907d | 36 | } |
dfeec247 XL |
37 | } |
38 | } | |
39 | ||
40 | #[derive(serde::Deserialize)] | |
41 | struct RustfmtConfig { | |
42 | ignore: Vec<String>, | |
43 | } | |
44 | ||
923072b8 | 45 | pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { |
487cf647 | 46 | if build.config.dry_run() { |
ba9703b0 XL |
47 | return; |
48 | } | |
dfeec247 XL |
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 | } | |
ba9703b0 XL |
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 { | |
064997fb FG |
75 | let in_working_tree = match build |
76 | .config | |
77 | .git() | |
ba9703b0 XL |
78 | .arg("rev-parse") |
79 | .arg("--is-inside-work-tree") | |
80 | .stdout(Stdio::null()) | |
81 | .stderr(Stdio::null()) | |
82 | .status() | |
83 | { | |
84 | Ok(status) => status.success(), | |
85 | Err(_) => false, | |
86 | }; | |
87 | if in_working_tree { | |
88 | let untracked_paths_output = output( | |
064997fb | 89 | build.config.git().arg("status").arg("--porcelain").arg("--untracked-files=normal"), |
ba9703b0 XL |
90 | ); |
91 | let untracked_paths = untracked_paths_output | |
92 | .lines() | |
93 | .filter(|entry| entry.starts_with("??")) | |
94 | .map(|entry| { | |
3dfed10e | 95 | entry.split(' ').nth(1).expect("every git status entry should list a path") |
ba9703b0 XL |
96 | }); |
97 | for untracked_path in untracked_paths { | |
923072b8 | 98 | println!("skip untracked path {} during rustfmt invocations", untracked_path); |
5e7ed085 FG |
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); | |
ba9703b0 XL |
105 | } |
106 | } else { | |
923072b8 | 107 | println!("Not in git tree. Skipping git-aware format checks"); |
ba9703b0 XL |
108 | } |
109 | } else { | |
923072b8 | 110 | println!("Could not find usable git. Skipping git-aware format checks"); |
74b04a01 | 111 | } |
dfeec247 XL |
112 | let ignore_fmt = ignore_fmt.build().unwrap(); |
113 | ||
923072b8 FG |
114 | let rustfmt_path = build.initial_rustfmt().unwrap_or_else(|| { |
115 | eprintln!("./x.py fmt is not supported on this channel"); | |
064997fb | 116 | crate::detail_exit(1); |
923072b8 FG |
117 | }); |
118 | assert!(rustfmt_path.exists(), "{}", rustfmt_path.display()); | |
6a06907d XL |
119 | let src = build.src.clone(); |
120 | let (tx, rx): (SyncSender<PathBuf>, _) = std::sync::mpsc::sync_channel(128); | |
17df50a5 XL |
121 | let walker = match paths.get(0) { |
122 | Some(first) => { | |
123 | let mut walker = WalkBuilder::new(first); | |
124 | for path in &paths[1..] { | |
125 | walker.add(path); | |
126 | } | |
127 | walker | |
128 | } | |
129 | None => WalkBuilder::new(src.clone()), | |
130 | } | |
131 | .types(matcher) | |
132 | .overrides(ignore_fmt) | |
133 | .build_parallel(); | |
6a06907d XL |
134 | |
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; | |
138 | ||
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(); | |
145 | ||
146 | let child = rustfmt(&src, &rustfmt_path, paths.as_slice(), check); | |
147 | children.push_back(child); | |
148 | ||
149 | if children.len() >= max_processes { | |
150 | // await oldest child | |
151 | children.pop_front().unwrap()(); | |
152 | } | |
153 | } | |
154 | ||
155 | // await remaining children | |
156 | for mut child in children { | |
157 | child(); | |
158 | } | |
dfeec247 | 159 | }); |
6a06907d | 160 | |
dfeec247 | 161 | walker.run(|| { |
6a06907d | 162 | let tx = tx.clone(); |
dfeec247 XL |
163 | Box::new(move |entry| { |
164 | let entry = t!(entry); | |
165 | if entry.file_type().map_or(false, |t| t.is_file()) { | |
6a06907d | 166 | t!(tx.send(entry.into_path())); |
dfeec247 XL |
167 | } |
168 | ignore::WalkState::Continue | |
169 | }) | |
170 | }); | |
6a06907d XL |
171 | |
172 | drop(tx); | |
173 | ||
174 | thread.join().unwrap(); | |
dfeec247 | 175 | } |