-
Notifications
You must be signed in to change notification settings - Fork 440
/
main.rs
182 lines (154 loc) · 5.78 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use std::env;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::str;
fn main() {
// Gather all command line and environment settings
let options = parse_args();
// Gather a list of all formattable targets
let targets = query_rustfmt_targets(&options);
// Run rustfmt on these targets
apply_rustfmt(&options, &targets);
}
/// Perform a `bazel` query to determine a list of Bazel targets which are to be formatted.
fn query_rustfmt_targets(options: &Config) -> Vec<String> {
// Determine what packages to query
let scope = match options.packages.is_empty() {
true => "//...:all".to_owned(),
false => {
// Check to see if all the provided packages are actually targets
let is_all_targets = options
.packages
.iter()
.all(|pkg| match label::analyze(pkg) {
Ok(tgt) => tgt.name != "all",
Err(_) => false,
});
// Early return if a list of targets and not packages were provided
if is_all_targets {
return options.packages.clone();
}
options.packages.join(" + ")
}
};
let query_args = vec![
"query".to_owned(),
format!(
r#"kind('{types}', {scope}) except attr(tags, 'norustfmt', kind('{types}', {scope}))"#,
types = "^rust_",
scope = scope
),
];
let child = Command::new(&options.bazel)
.current_dir(&options.workspace)
.args(query_args)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.expect("Failed to spawn bazel query command");
let output = child
.wait_with_output()
.expect("Failed to wait on spawned command");
if !output.status.success() {
std::process::exit(output.status.code().unwrap_or(1));
}
str::from_utf8(&output.stdout)
.expect("Invalid stream from command")
.split('\n')
.filter(|line| !line.is_empty())
.map(|line| line.to_string())
.collect()
}
/// Build a list of Bazel targets using the `rustfmt_aspect` to produce the
/// arguments to use when formatting the sources of those targets.
fn generate_rustfmt_target_manifests(options: &Config, targets: &[String]) {
let build_args = vec![
"build",
"--aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect",
"--output_groups=rustfmt_manifest",
];
let child = Command::new(&options.bazel)
.current_dir(&options.workspace)
.args(build_args)
.args(targets)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.expect("Failed to spawn command");
let output = child
.wait_with_output()
.expect("Failed to wait on spawned command");
if !output.status.success() {
std::process::exit(output.status.code().unwrap_or(1));
}
}
/// Run rustfmt on a set of Bazel targets
fn apply_rustfmt(options: &Config, targets: &[String]) {
// Ensure the targets are first built and a manifest containing `rustfmt`
// arguments are generated before formatting source files.
generate_rustfmt_target_manifests(&options, &targets);
for target in targets.iter() {
// Replace any `:` characters and strip leading slashes
let target_path = target.replace(":", "/").trim_start_matches('/').to_owned();
// Find a manifest for the current target. Not all targets will have one
let manifest = options.workspace.join("bazel-bin").join(format!(
"{}.{}",
&target_path,
rustfmt_lib::RUSTFMT_MANIFEST_EXTENSION,
));
if !manifest.exists() {
continue;
}
// Load the manifest containing rustfmt arguments
let rustfmt_config = rustfmt_lib::parse_rustfmt_manifest(&manifest);
// Ignore any targets which do not have source files. This can
// occur in cases where all source files are generated.
if rustfmt_config.sources.is_empty() {
continue;
}
// Run rustfmt
let status = Command::new(&options.rustfmt_config.rustfmt)
.current_dir(&options.workspace)
.arg("--edition")
.arg(rustfmt_config.edition)
.arg("--config-path")
.arg(&options.rustfmt_config.config)
.args(rustfmt_config.sources)
.status()
.expect("Failed to run rustfmt");
if !status.success() {
std::process::exit(status.code().unwrap_or(1));
}
}
}
/// A struct containing details used for executing rustfmt.
#[derive(Debug)]
struct Config {
/// The path of the Bazel workspace root.
pub workspace: PathBuf,
/// The Bazel executable to use for builds and queries.
pub bazel: PathBuf,
/// Information about the current rustfmt binary to run.
pub rustfmt_config: rustfmt_lib::RustfmtConfig,
/// Optionally, users can pass a list of targets/packages/scopes
/// (eg `//my:target` or `//my/pkg/...`) to control the targets
/// to be formatted. If empty, all targets in the workspace will
/// be formatted.
pub packages: Vec<String>,
}
/// Parse command line arguments and environment variables to
/// produce config data for running rustfmt.
fn parse_args() -> Config {
Config{
workspace: PathBuf::from(
env::var("BUILD_WORKSPACE_DIRECTORY")
.expect("The environment variable BUILD_WORKSPACE_DIRECTORY is required for finding the workspace root")
),
bazel: PathBuf::from(
env::var("BAZEL_REAL")
.unwrap_or_else(|_| "bazel".to_owned())
),
rustfmt_config: rustfmt_lib::parse_rustfmt_config(),
packages: env::args().skip(1).collect(),
}
}