1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 extern crate core;
16 
17 use clap::Parser as _;
18 use cmd_runner::{license_checker::LicenseSubcommand, run_cmd, run_cmd_shell, YellowStderr};
19 use env_logger::Env;
20 use license::LICENSE_CHECKER;
21 use std::{env, ffi::OsString, path};
22 
23 mod crypto_ffi;
24 mod ffi;
25 mod fuzzers;
26 mod jni;
27 mod license;
28 mod ukey2;
29 
main() -> anyhow::Result<()>30 fn main() -> anyhow::Result<()> {
31     env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
32     let cli: Cli = Cli::parse();
33 
34     let root_dir = path::PathBuf::from(
35         env::var("CARGO_MANIFEST_DIR").expect("Must be run via Cargo to establish root directory"),
36     );
37 
38     match cli.subcommand {
39         Subcommand::RunDefaultChecks(ref check_options) => {
40             run_default_checks(&root_dir, check_options)?;
41             print!(concat!(
42                 "Congratulations, the default checks passed. Since you like quality, here are\n",
43                 "some more checks you may like:\n",
44                 "    cargo run -- run-rust-fuzzers\n",
45                 "    cargo run -- check-stack-usage\n",
46             ));
47         }
48         Subcommand::VerifyCi { ref check_options } => verify_ci(&root_dir, check_options)?,
49         Subcommand::CleanEverything => clean_everything(&root_dir)?,
50         Subcommand::CheckFormat(ref options) => check_format(&root_dir, options)?,
51         Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?,
52         Subcommand::CheckAllFfi(ref options) => ffi::check_ffi(&root_dir, options)?,
53         Subcommand::BuildBoringssl => crypto_ffi::build_boringssl(&root_dir)?,
54         Subcommand::CheckBoringssl(ref options) => crypto_ffi::check_boringssl(&root_dir, options)?,
55         Subcommand::CheckBoringsslAtLatest(ref options) => {
56             crypto_ffi::check_boringssl_at_head(&root_dir, options)?
57         }
58         Subcommand::RunRustFuzzers => fuzzers::run_rust_fuzzers(&root_dir)?,
59         Subcommand::CheckFuzztest => fuzzers::build_fuzztest_unit_tests(&root_dir)?,
60         Subcommand::License(license_subcommand) => {
61             license_subcommand.run(&LICENSE_CHECKER, &root_dir)?
62         }
63         Subcommand::CheckUkey2Ffi(ref options) => ukey2::check_ukey2_ffi(&root_dir, options)?,
64         Subcommand::RunUkey2JniTests => jni::run_ukey2_jni_tests(&root_dir)?,
65         Subcommand::RunNpJavaFfiTests => jni::run_np_java_ffi_tests(&root_dir)?,
66         Subcommand::CheckLdtJni => jni::check_ldt_jni(&root_dir)?,
67         Subcommand::CheckLdtCmake(ref options) => ffi::check_ldt_cmake(&root_dir, options)?,
68         Subcommand::CheckNpFfiCmake(ref options) => ffi::check_np_ffi_cmake(&root_dir, options)?,
69         Subcommand::RunKotlinTests => jni::run_kotlin_tests(&root_dir)?,
70     }
71 
72     Ok(())
73 }
74 
check_format(root: &path::Path, options: &FormatterOptions) -> anyhow::Result<()>75 fn check_format(root: &path::Path, options: &FormatterOptions) -> anyhow::Result<()> {
76     // Rust format
77     {
78         let fmt_command = if options.reformat { "cargo fmt" } else { "cargo fmt --check" };
79         run_cmd_shell(root, fmt_command)?;
80     }
81 
82     // Java format. This uses the jar downloaded as part of the CI script or the local
83     // `google-java-format` executable. The jar file path can be overridden with the
84     // `GOOGLE_JAVA_FORMAT_ALL_DEPS_JAR` environment variable. See
85     // <go/google-java-format#installation> for setup instructions for your dev environment if
86     // needed.
87     {
88         let jar_path = std::env::var("GOOGLE_JAVA_FORMAT_ALL_DEPS_JAR").unwrap_or_else(|_| {
89             "/opt/google-java-format/google-java-format-all-deps.jar".to_owned()
90         });
91 
92         let mut fmt_command: Vec<OsString> = Vec::new();
93 
94         if path::PathBuf::from(&jar_path).exists() {
95             fmt_command.extend(["java".into(), "-jar".into(), jar_path.into()]);
96         } else {
97             fmt_command.push("google-java-format".into());
98         }
99 
100         if options.reformat {
101             fmt_command.push("-i".into());
102         } else {
103             fmt_command.extend(["--set-exit-if-changed".into(), "--dry-run".into()]);
104         }
105 
106         let root_str =
107             glob::Pattern::escape(root.to_str().expect("Non-unicode paths are not supported"));
108         let search = format!("{}/**/*.java", root_str);
109         let java_files: Vec<_> = glob::glob(&search).unwrap().filter_map(Result::ok).collect();
110 
111         for file_set in java_files.chunks(100) {
112             let mut args = fmt_command[1..].to_vec();
113             args.extend(file_set.iter().map(OsString::from));
114 
115             run_cmd::<YellowStderr, _, _, _>(root, &fmt_command[0], args)?;
116         }
117     }
118 
119     Ok(())
120 }
121 
check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Result<()>122 pub fn check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Result<()> {
123     log::info!("Running cargo checks on workspace");
124 
125     // ensure formatting is correct (Check for it first because it is fast compared to running tests)
126     check_format(root, &options.formatter_options)?;
127 
128     for cargo_cmd in [
129         // make sure everything compiles
130         "cargo check --workspace --all-targets --quiet",
131         // run all the tests
132         &options.cargo_options.test("check_workspace", "--workspace --quiet"),
133         // Test ukey2 builds with different crypto providers
134         &options.cargo_options.test(
135             "check_workspace_ukey2",
136             "-p ukey2_connections -p ukey2_rs --no-default-features --features test_rustcrypto",
137         ),
138         // ensure the docs are valid (cross-references to other code, etc)
139         concat!(
140             "RUSTDOCFLAGS='--deny warnings' ",
141             "cargo doc --quiet --workspace --no-deps --document-private-items ",
142             "--target-dir target/dist_docs",
143         ),
144         "cargo clippy --all-targets --workspace -- --deny warnings",
145         "cargo deny --workspace check",
146     ] {
147         run_cmd_shell(root, cargo_cmd)?;
148     }
149 
150     Ok(())
151 }
152 
153 /// Runs default checks that are suiable for verifying a local change.
run_default_checks(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()>154 pub fn run_default_checks(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> {
155     license::LICENSE_CHECKER.check(root)?;
156     check_workspace(root, check_options)?;
157     crypto_ffi::check_boringssl(root, &check_options.cargo_options)?;
158     ffi::check_ffi(root, &check_options.cargo_options)?;
159     if !cfg!(target_os = "windows") {
160         fuzzers::build_fuzztest_unit_tests(root)?;
161     }
162     crypto_ffi::check_boringssl_at_head(root, &check_options.cargo_options)?;
163     ukey2::check_ukey2_ffi(root, &check_options.cargo_options)?;
164     if !cfg!(target_os = "windows") {
165         // Test below requires Java SE 9, but on Windows we only have Java SE 8 installed
166         jni::run_np_java_ffi_tests(root)?;
167     }
168     jni::run_ukey2_jni_tests(root)?;
169     Ok(())
170 }
171 
172 /// Runs checks to ensure lints are passing and all targets are building
verify_ci(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()>173 pub fn verify_ci(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> {
174     run_default_checks(root, check_options)?;
175     Ok(())
176 }
177 
clean_everything(root: &path::Path) -> anyhow::Result<()>178 pub fn clean_everything(root: &path::Path) -> anyhow::Result<()> {
179     run_cmd_shell(root, "cargo clean")?;
180     run_cmd_shell(&root.join("presence/np_c_ffi"), "cargo clean")?;
181     run_cmd_shell(&root.join("crypto/crypto_provider_boringssl"), "cargo clean")?;
182     run_cmd_shell(&root.join("connections/ukey2/ukey2_c_ffi"), "cargo clean")?;
183     run_cmd_shell(&root.join("presence/np_java_ffi"), "./gradlew :clean")?;
184     Ok(())
185 }
186 
187 #[derive(clap::Parser)]
188 struct Cli {
189     #[clap(subcommand)]
190     subcommand: Subcommand,
191 }
192 
193 #[derive(clap::Subcommand, Debug, Clone)]
194 enum Subcommand {
195     /// Runs all of the checks that CI runs
196     VerifyCi {
197         #[command(flatten)]
198         check_options: CheckOptions,
199     },
200     /// Runs the default set of checks suitable for verifying local changes.
201     RunDefaultChecks(CheckOptions),
202     /// Cleans the main workspace and all sub projects - useful if upgrading rust compiler version
203     /// and need dependencies to be compiled with the same version
204     CleanEverything,
205     /// Checks code formatting
206     CheckFormat(FormatterOptions),
207     /// Checks everything included in the top level workspace
208     CheckWorkspace(CheckOptions),
209     /// Clones boringssl and uses bindgen to generate the rust crate
210     BuildBoringssl,
211     /// Run crypto provider tests using boringssl backend
212     CheckBoringssl(CargoOptions),
213     /// Checks out latest boringssl commit and runs our tests against it
214     CheckBoringsslAtLatest(CargoOptions),
215     /// Build and run pure Rust fuzzers for 10000 runs
216     RunRustFuzzers,
217     /// Builds and runs fuzztest property based unit tests
218     CheckFuzztest,
219     /// Builds and runs tests for all C/C++ projects. This is a combination of CheckNpFfi,
220     /// CheckLdtFfi, and CheckCmakeBuildAndTests
221     CheckAllFfi(CargoOptions),
222     /// Checks the CMake build and runs all of the C/C++ tests
223     CheckLdtCmake(CargoOptions),
224     /// Checks the CMake build and runs all of the C/C++ tests
225     CheckNpFfiCmake(CargoOptions),
226     #[command(flatten)]
227     License(LicenseSubcommand),
228     /// Builds and runs tests for the UKEY2 FFI
229     CheckUkey2Ffi(CargoOptions),
230     /// Checks the build of ldt_jni wrapper with non default features, ie boringssl
231     CheckLdtJni,
232     /// Runs the kotlin tests of the LDT Jni API
233     RunKotlinTests,
234     /// Checks the build of the ukey2_jni wrapper and runs tests
235     RunUkey2JniTests,
236     /// Checks the build of the np_java_ffi wrapper and runs tests
237     RunNpJavaFfiTests,
238 }
239 
240 #[derive(clap::Args, Debug, Clone, Default)]
241 pub struct FormatterOptions {
242     #[arg(long, help = "reformat files files in the workspace with the code formatter")]
243     reformat: bool,
244 }
245 
246 #[derive(clap::Args, Debug, Clone, Default)]
247 pub struct CheckOptions {
248     #[command(flatten)]
249     formatter_options: FormatterOptions,
250     #[command(flatten)]
251     cargo_options: CargoOptions,
252 }
253 
254 #[derive(clap::Args, Debug, Clone, Default)]
255 pub struct CargoOptions {
256     #[arg(long, help = "whether to run cargo with --locked")]
257     locked: bool,
258     #[arg(long, help = "gather coverage metrics")]
259     coverage: bool,
260 }
261 
262 impl CargoOptions {
263     /// Run `cargo test` or `cargo llvm-cov` depending on the configured options.
test(&self, tag: &str, args: impl AsRef<str>) -> String264     pub fn test(&self, tag: &str, args: impl AsRef<str>) -> String {
265         format!(
266             "cargo {subcommand} {locked} {args} {cov_args} -- --color=always",
267             subcommand = if self.coverage { "llvm-cov" } else { "test" },
268             locked = if self.locked { "--locked" } else { "" },
269             args = args.as_ref(),
270             cov_args = if self.coverage {
271                 format!("--lcov --output-path \"target/{tag}.info\"")
272             } else {
273                 String::default()
274             },
275         )
276     }
277 }
278