blob: 5e0fc12bce4ca69b0116c978081b2cd322cda78e [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{anyhow, Result};
use argh::FromArgs;
use std::{
env,
fs::{self, File},
io::{self, BufRead, BufReader},
path::{Path, PathBuf},
process::Command,
};
use crate::monorail::Monorail;
mod allow;
mod fix;
mod issues;
mod lint;
mod monorail;
mod owners;
mod rollout;
mod span;
#[derive(Debug, FromArgs)]
/// Silence rustc and clippy lints with allow attributes and autofixes
struct Args {
#[argh(subcommand)]
action: Action,
/// don't modify source files
#[argh(switch)]
dryrun: bool,
/// modify files even if there are local uncommitted changes
#[argh(switch)]
force: bool,
/// lint (or category) to deal with e.g. clippy::needless_return
#[argh(option)]
lint: Vec<String>,
/// the path to the rollout file
#[argh(option)]
rollout: Option<PathBuf>,
/// path to the root dir of the fuchsia source tree
#[argh(option)]
fuchsia_dir: Option<PathBuf>,
/// file containing json lints (uses stdin if not given)
#[argh(positional)]
lint_file: Option<PathBuf>,
/// path to a prpc binary for monorail API calls
#[argh(option)]
prpc: Option<PathBuf>,
/// mock monorail issue creation calls instead of calling prpc
#[argh(switch)]
mock: bool,
/// print details of created issues to the command line
#[argh(switch)]
verbose: bool,
/// print API calls to the command line
#[argh(switch)]
log_api: bool,
}
impl Args {
pub fn try_get_filter(&self) -> Result<&[String]> {
if self.lint.is_empty() {
Err(anyhow!("Must filter on at least one lint or category with '--lint'"))
} else {
Ok(&self.lint)
}
}
pub fn read_lints(&self) -> Box<dyn BufRead> {
if let Some(ref f) = self.lint_file {
Box::new(BufReader::new(File::open(f).unwrap()))
} else {
Box::new(BufReader::new(io::stdin()))
}
}
pub fn monorail_api(&self) -> Result<Box<dyn Monorail>> {
const MONORAIL_API_URL: &'static str = "api-dot-monorail-prod.appspot.com";
let prpc = self.prpc.as_ref().map(|binary_path| {
monorail::Prpc::new(binary_path.clone(), MONORAIL_API_URL.to_string())
});
if self.dryrun || self.mock {
Ok(Box::new(monorail::Mock::new(self.log_api, prpc)))
} else {
Ok(Box::new(
prpc.ok_or_else(|| anyhow!("--prpc is required when monorail is not mocked"))?,
))
}
}
pub fn rollout_path(&self) -> &Path {
self.rollout.as_deref().unwrap_or(Path::new("./rollout.json~"))
}
pub fn change_to_fuchsia_root(&self) -> Result<PathBuf> {
if let Some(fuchsia_dir) =
self.fuchsia_dir.clone().or_else(|| env::var("FUCHSIA_DIR").ok().map(Into::into))
{
env::set_current_dir(&fuchsia_dir)?;
Ok(fuchsia_dir)
} else {
Ok(std::env::current_dir()?.canonicalize()?)
}
}
}
#[derive(Debug, FromArgs)]
#[argh(subcommand)]
enum Action {
Fix(Fix),
Allow(Allow),
Rollout(Rollout),
}
/// use rustfix to auto-fix the lints
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "fix")]
struct Fix {}
/// add allow attributes
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "allow")]
struct Allow {
/// the tag to link to on codesearch
#[argh(option)]
codesearch_tag: Option<String>,
/// path to an issue description template containing "INSERT_DETAILS_HERE"
#[argh(option)]
template: Option<PathBuf>,
/// the issue to mark created issues as blocking
#[argh(option)]
blocking_issue: Option<String>,
/// the labels to add to created issues
#[argh(option)]
labels: Vec<String>,
/// the maximum number of additional users to CC on created issues
#[argh(option, default = "3")]
max_cc_users: usize,
}
impl Allow {
fn load_template(&self) -> Result<Option<String>> {
Ok(self.template.as_ref().map(|path| fs::read_to_string(path)).transpose()?)
}
}
/// roll out lints generated by allow
#[derive(FromArgs, Debug)]
#[argh(subcommand, name = "rollout")]
struct Rollout {}
fn main() -> Result<()> {
let args: Args = argh::from_env();
let git_status =
Command::new("jiri").args(["runp", "git", "status", "--porcelain"]).output()?;
let clean_tree = git_status.status.success() && git_status.stdout.is_empty();
if !(args.dryrun || args.force || clean_tree) {
return Err(anyhow!(
"The current directory is dirty, pass the --force flag or commit the local changes"
));
}
match args.action {
Action::Fix(_) => fix::fix(&mut args.read_lints(), args.try_get_filter()?, args.dryrun),
Action::Allow(ref allow_args) => {
let mut monorail = args.monorail_api()?;
let mut issue_template = issues::IssueTemplate::new(
&args.lint,
allow_args.codesearch_tag.as_deref(),
allow_args.load_template()?,
allow_args.blocking_issue.as_deref(),
allow_args.labels.as_ref(),
allow_args.max_cc_users,
);
let rollout_path = args.rollout_path();
if rollout_path.exists() {
return Err(anyhow!(
"The rollout path {} already exists, delete it or specify an alternate path.",
rollout_path.to_str().unwrap_or("<non-utf8 path>"),
));
}
allow::allow(
&mut args.read_lints(),
args.try_get_filter()?,
&args.change_to_fuchsia_root()?,
&mut *monorail,
&mut issue_template,
rollout_path,
args.dryrun,
args.verbose,
)
}
Action::Rollout(_) => {
let mut monorail = args.monorail_api()?;
let rollout_path = args.rollout_path();
if !rollout_path.exists() {
return Err(anyhow!(
"The rollout path {} does not exist, run shush allow to generate a rollout file.",
rollout_path.to_str().unwrap_or("<non-utf8 path>"),
));
}
rollout::rollout(&mut *monorail, rollout_path, args.verbose)
}
}
}