use crate::ops::{clean_files, confirm, remove_dir};
use anyhow::{Context, Result as AnyResult};
use derive_builder::Builder;
use duct::cmd;
use std::fs::create_dir_all;
pub fn docs() -> AnyResult<()> {
cmd!("cargo", "watch", "-s", "cargo doc --no-deps").run()?;
Ok(())
}
#[derive(Builder)]
#[builder(setter(into))]
pub struct CI {
#[builder(default = "false")]
pub nightly: bool,
#[builder(default = "true")]
pub clippy_max: bool,
}
impl CIBuilder {
pub fn run(&self) -> AnyResult<()> {
let t = self.build()?;
let mut check_args = vec!["fmt", "--all", "--", "--check"];
if t.nightly {
check_args.insert(0, "+nightly");
}
let mut clippy_args = vec!["clippy", "--", "-D", "warnings"];
if t.clippy_max {
clippy_args.extend([
"-W",
"clippy::pedantic",
"-W",
"clippy::nursery",
"-W",
"rust-2018-idioms",
]);
}
cmd("cargo", check_args.as_slice()).run()?;
cmd("cargo", clippy_args.as_slice()).run()?;
cmd!("cargo", "test").run()?;
cmd!("cargo", "test", "--doc").run()?;
Ok(())
}
}
pub fn ci() -> AnyResult<()> {
CIBuilder::default().run()
}
pub fn coverage(devmode: bool) -> AnyResult<()> {
remove_dir("coverage")?;
create_dir_all("coverage")?;
println!("=== running coverage ===");
cmd!("cargo", "test")
.env("CARGO_INCREMENTAL", "0")
.env("RUSTFLAGS", "-Cinstrument-coverage")
.env("LLVM_PROFILE_FILE", "cargo-test-%p-%m.profraw")
.run()?;
println!("ok.");
println!("=== generating report ===");
let (fmt, file) = if devmode {
("html", "coverage/html")
} else {
("lcov", "coverage/tests.lcov")
};
cmd!(
"grcov",
".",
"--binary-path",
"./target/debug/deps",
"-s",
".",
"-t",
fmt,
"--branch",
"--ignore-not-existing",
"--ignore",
"../*",
"--ignore",
"/*",
"--ignore",
"xtask/*",
"--ignore",
"*/src/tests/*",
"-o",
file,
)
.run()?;
println!("ok.");
println!("=== cleaning up ===");
clean_files("**/*.profraw")?;
println!("ok.");
if devmode {
if confirm("open report folder?") {
cmd!("open", file).run()?;
} else {
println!("report location: {file}");
}
}
Ok(())
}
#[derive(Builder)]
#[builder(setter(into))]
pub struct Powerset {
#[builder(default = "2")]
pub depth: i32,
#[builder(default = "false")]
pub exclude_no_default_features: bool,
}
impl PowersetBuilder {
pub fn run(&self) -> AnyResult<()> {
let t = self.build()?;
let depth = format!("{}", t.depth);
let mut common = vec![
"--workspace",
"--exclude",
"xtask",
"--feature-powerset",
"--depth",
&depth,
];
if t.exclude_no_default_features {
common.push("--exclude-no-default-features");
}
cmd(
"cargo",
&[
&["hack", "clippy"],
common.as_slice(),
&["--", "-D", "warnings"],
]
.concat(),
)
.run()?;
cmd("cargo", &[&["hack"], common.as_slice(), &["test"]].concat()).run()?;
cmd(
"cargo",
&[&["hack", "test"], common.as_slice(), &["--doc"]].concat(),
)
.run()?;
Ok(())
}
}
pub fn powerset() -> AnyResult<()> {
PowersetBuilder::default().run()
}
pub fn bloat_deps(package: &str) -> AnyResult<()> {
cmd!("cargo", "bloat", "--release", "--crates", "-p", package).run()?;
Ok(())
}
pub fn bloat_time(package: &str) -> AnyResult<()> {
cmd!("cargo", "bloat", "--time", "-j", "1", "-p", package).run()?;
Ok(())
}
pub fn dev() -> AnyResult<()> {
cmd!("cargo", "watch", "-x", "check", "-x", "test").run()?;
Ok(())
}
pub fn install() -> AnyResult<()> {
cmd!("cargo", "install", "cargo-watch").run()?;
cmd!("cargo", "install", "cargo-hack").run()?;
cmd!("cargo", "install", "cargo-bloat").run()?;
Ok(())
}
#[cfg(feature = "clap")]
pub fn main() -> AnyResult<()> {
use clap::{AppSettings, Arg, Command};
let cli = Command::new("xtask")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
Command::new("coverage").arg(
Arg::new("dev")
.short('d')
.long("dev")
.help("generate an html report")
.takes_value(false),
),
)
.subcommand(Command::new("vars"))
.subcommand(Command::new("ci"))
.subcommand(Command::new("powerset"))
.subcommand(
Command::new("bloat-deps").arg(
Arg::new("package")
.short('p')
.long("package")
.help("package to build")
.required(true)
.takes_value(true),
),
)
.subcommand(
Command::new("bloat-time").arg(
Arg::new("package")
.short('p')
.long("package")
.help("package to build")
.required(true)
.takes_value(true),
),
)
.subcommand(Command::new("docs"));
let matches = cli.get_matches();
let root = crate::ops::root_dir();
let res = match matches.subcommand() {
Some(("coverage", sm)) => crate::tasks::coverage(sm.is_present("dev")),
Some(("vars", _)) => {
println!("root: {root:?}");
Ok(())
}
Some(("ci", _)) => crate::tasks::ci(),
Some(("docs", _)) => crate::tasks::docs(),
Some(("powerset", _)) => crate::tasks::powerset(),
Some(("bloat-deps", sm)) => crate::tasks::bloat_deps(
sm.get_one::<String>("package")
.context("please provide a package with -p")?,
),
Some(("bloat-time", sm)) => crate::tasks::bloat_time(
sm.get_one::<String>("package")
.context("please provide a package with -p")?,
),
_ => unreachable!("unreachable branch"),
};
res
}