| // 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() |
| }, |
| ) |
| } |
| } |