blob: 25fe34834835580d5e153017c7616de2bc93edf9 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
extern crate core;
use clap::Parser as _;
use cmd_runner::{license_checker::LicenseSubcommand, run_cmd, run_cmd_shell, YellowStderr};
use env_logger::Env;
use license::LICENSE_CHECKER;
use std::{env, ffi::OsString, path};
mod crypto_ffi;
mod ffi;
mod fuzzers;
mod jni;
mod license;
mod ukey2;
fn main() -> anyhow::Result<()> {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let cli: Cli = Cli::parse();
let root_dir = path::PathBuf::from(
env::var("CARGO_MANIFEST_DIR").expect("Must be run via Cargo to establish root directory"),
);
match cli.subcommand {
Subcommand::RunDefaultChecks(ref check_options) => {
run_default_checks(&root_dir, check_options)?;
print!(concat!(
"Congratulations, the default checks passed. Since you like quality, here are\n",
"some more checks you may like:\n",
" cargo run -- run-rust-fuzzers\n",
" cargo run -- check-stack-usage\n",
));
}
Subcommand::VerifyCi { ref check_options } => verify_ci(&root_dir, check_options)?,
Subcommand::CleanEverything => clean_everything(&root_dir)?,
Subcommand::CheckFormat(ref options) => check_format(&root_dir, options)?,
Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?,
Subcommand::CheckAllFfi(ref options) => ffi::check_ffi(&root_dir, options)?,
Subcommand::BuildBoringssl => crypto_ffi::build_boringssl(&root_dir)?,
Subcommand::CheckBoringssl(ref options) => crypto_ffi::check_boringssl(&root_dir, options)?,
Subcommand::CheckBoringsslAtLatest(ref options) => {
crypto_ffi::check_boringssl_at_head(&root_dir, options)?
}
Subcommand::RunRustFuzzers => fuzzers::run_rust_fuzzers(&root_dir)?,
Subcommand::CheckFuzztest => fuzzers::build_fuzztest_unit_tests(&root_dir)?,
Subcommand::License(license_subcommand) => {
license_subcommand.run(&LICENSE_CHECKER, &root_dir)?
}
Subcommand::CheckUkey2Ffi(ref options) => ukey2::check_ukey2_ffi(&root_dir, options)?,
Subcommand::RunUkey2JniTests => jni::run_ukey2_jni_tests(&root_dir)?,
Subcommand::RunNpJavaFfiTests => jni::run_np_java_ffi_tests(&root_dir)?,
Subcommand::CheckLdtJni => jni::check_ldt_jni(&root_dir)?,
Subcommand::CheckLdtCmake(ref options) => ffi::check_ldt_cmake(&root_dir, options)?,
Subcommand::CheckNpFfiCmake(ref options) => ffi::check_np_ffi_cmake(&root_dir, options)?,
Subcommand::RunKotlinTests => jni::run_kotlin_tests(&root_dir)?,
}
Ok(())
}
fn check_format(root: &path::Path, options: &FormatterOptions) -> anyhow::Result<()> {
// Rust format
{
let fmt_command = if options.reformat { "cargo fmt" } else { "cargo fmt --check" };
run_cmd_shell(root, fmt_command)?;
}
// Java format. This uses the jar downloaded as part of the CI script or the local
// `google-java-format` executable. The jar file path can be overridden with the
// `GOOGLE_JAVA_FORMAT_ALL_DEPS_JAR` environment variable. See
// <go/google-java-format#installation> for setup instructions for your dev environment if
// needed.
{
let jar_path = std::env::var("GOOGLE_JAVA_FORMAT_ALL_DEPS_JAR").unwrap_or_else(|_| {
"/opt/google-java-format/google-java-format-all-deps.jar".to_owned()
});
let mut fmt_command: Vec<OsString> = Vec::new();
if path::PathBuf::from(&jar_path).exists() {
fmt_command.extend(["java".into(), "-jar".into(), jar_path.into()]);
} else {
fmt_command.push("google-java-format".into());
}
if options.reformat {
fmt_command.push("-i".into());
} else {
fmt_command.extend(["--set-exit-if-changed".into(), "--dry-run".into()]);
}
let root_str =
glob::Pattern::escape(root.to_str().expect("Non-unicode paths are not supported"));
let search = format!("{}/**/*.java", root_str);
let java_files: Vec<_> = glob::glob(&search).unwrap().filter_map(Result::ok).collect();
for file_set in java_files.chunks(100) {
let mut args = fmt_command[1..].to_vec();
args.extend(file_set.iter().map(OsString::from));
run_cmd::<YellowStderr, _, _, _>(root, &fmt_command[0], args)?;
}
}
Ok(())
}
pub fn check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Result<()> {
log::info!("Running cargo checks on workspace");
// ensure formatting is correct (Check for it first because it is fast compared to running tests)
check_format(root, &options.formatter_options)?;
for cargo_cmd in [
// make sure everything compiles
"cargo check --workspace --all-targets --quiet",
// run all the tests
&options.cargo_options.test("check_workspace", "--workspace --quiet"),
// Test ukey2 builds with different crypto providers
&options.cargo_options.test(
"check_workspace_ukey2",
"-p ukey2_connections -p ukey2_rs --no-default-features --features test_rustcrypto",
),
// ensure the docs are valid (cross-references to other code, etc)
concat!(
"RUSTDOCFLAGS='--deny warnings' ",
"cargo doc --quiet --workspace --no-deps --document-private-items ",
"--target-dir target/dist_docs",
),
"cargo clippy --all-targets --workspace -- --deny warnings",
"cargo deny --workspace check",
] {
run_cmd_shell(root, cargo_cmd)?;
}
Ok(())
}
/// Runs default checks that are suiable for verifying a local change.
pub fn run_default_checks(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> {
license::LICENSE_CHECKER.check(root)?;
check_workspace(root, check_options)?;
crypto_ffi::check_boringssl(root, &check_options.cargo_options)?;
ffi::check_ffi(root, &check_options.cargo_options)?;
if !cfg!(target_os = "windows") {
fuzzers::build_fuzztest_unit_tests(root)?;
}
crypto_ffi::check_boringssl_at_head(root, &check_options.cargo_options)?;
ukey2::check_ukey2_ffi(root, &check_options.cargo_options)?;
if !cfg!(target_os = "windows") {
// Test below requires Java SE 9, but on Windows we only have Java SE 8 installed
jni::run_np_java_ffi_tests(root)?;
}
jni::run_ukey2_jni_tests(root)?;
Ok(())
}
/// Runs checks to ensure lints are passing and all targets are building
pub fn verify_ci(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> {
run_default_checks(root, check_options)?;
Ok(())
}
pub fn clean_everything(root: &path::Path) -> anyhow::Result<()> {
run_cmd_shell(root, "cargo clean")?;
run_cmd_shell(&root.join("presence/np_c_ffi"), "cargo clean")?;
run_cmd_shell(&root.join("crypto/crypto_provider_boringssl"), "cargo clean")?;
run_cmd_shell(&root.join("connections/ukey2/ukey2_c_ffi"), "cargo clean")?;
run_cmd_shell(&root.join("presence/np_java_ffi"), "./gradlew :clean")?;
Ok(())
}
#[derive(clap::Parser)]
struct Cli {
#[clap(subcommand)]
subcommand: Subcommand,
}
#[derive(clap::Subcommand, Debug, Clone)]
enum Subcommand {
/// Runs all of the checks that CI runs
VerifyCi {
#[command(flatten)]
check_options: CheckOptions,
},
/// Runs the default set of checks suitable for verifying local changes.
RunDefaultChecks(CheckOptions),
/// Cleans the main workspace and all sub projects - useful if upgrading rust compiler version
/// and need dependencies to be compiled with the same version
CleanEverything,
/// Checks code formatting
CheckFormat(FormatterOptions),
/// Checks everything included in the top level workspace
CheckWorkspace(CheckOptions),
/// Clones boringssl and uses bindgen to generate the rust crate
BuildBoringssl,
/// Run crypto provider tests using boringssl backend
CheckBoringssl(CargoOptions),
/// Checks out latest boringssl commit and runs our tests against it
CheckBoringsslAtLatest(CargoOptions),
/// Build and run pure Rust fuzzers for 10000 runs
RunRustFuzzers,
/// Builds and runs fuzztest property based unit tests
CheckFuzztest,
/// Builds and runs tests for all C/C++ projects. This is a combination of CheckNpFfi,
/// CheckLdtFfi, and CheckCmakeBuildAndTests
CheckAllFfi(CargoOptions),
/// Checks the CMake build and runs all of the C/C++ tests
CheckLdtCmake(CargoOptions),
/// Checks the CMake build and runs all of the C/C++ tests
CheckNpFfiCmake(CargoOptions),
#[command(flatten)]
License(LicenseSubcommand),
/// Builds and runs tests for the UKEY2 FFI
CheckUkey2Ffi(CargoOptions),
/// Checks the build of ldt_jni wrapper with non default features, ie boringssl
CheckLdtJni,
/// Runs the kotlin tests of the LDT Jni API
RunKotlinTests,
/// Checks the build of the ukey2_jni wrapper and runs tests
RunUkey2JniTests,
/// Checks the build of the np_java_ffi wrapper and runs tests
RunNpJavaFfiTests,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct FormatterOptions {
#[arg(long, help = "reformat files files in the workspace with the code formatter")]
reformat: bool,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct CheckOptions {
#[command(flatten)]
formatter_options: FormatterOptions,
#[command(flatten)]
cargo_options: CargoOptions,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct CargoOptions {
#[arg(long, help = "whether to run cargo with --locked")]
locked: bool,
#[arg(long, help = "gather coverage metrics")]
coverage: bool,
}
impl CargoOptions {
/// Run `cargo test` or `cargo llvm-cov` depending on the configured options.
pub fn test(&self, tag: &str, args: impl AsRef<str>) -> String {
format!(
"cargo {subcommand} {locked} {args} {cov_args} -- --color=always",
subcommand = if self.coverage { "llvm-cov" } else { "test" },
locked = if self.locked { "--locked" } else { "" },
args = args.as_ref(),
cov_args = if self.coverage {
format!("--lcov --output-path \"target/{tag}.info\"")
} else {
String::default()
},
)
}
}