blob: 4e4419f9d7b332b8fa0fdc1a510bc256b2119cfa [file] [log] [blame]
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".to_owned(),
format!(
"--aspects={}//rust:defs.bzl%rustfmt_aspect",
env!("ASPECT_REPOSITORY")
),
"--output_groups=rustfmt_manifest".to_owned(),
];
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(),
}
}