// Copyright (C) 2024 The Android Open Source Project // // 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. //! Convenience methods for working with Android blueprint files. use std::{ collections::{BTreeSet, HashSet}, sync::LazyLock, }; use android_bp::{BluePrint, Value}; use crate::TestMappingError; /// Extract rust test rules from a blueprint file. pub(crate) trait RustTests { /// Returns the names of all rust_test and rust_test_host rules. fn rust_tests(&self) -> Result, TestMappingError>; } impl RustTests for BluePrint { fn rust_tests(&self) -> Result, TestMappingError> { let mut tests = BTreeSet::new(); for module in &self.modules { if matches!(module.typ.as_str(), "rust_test" | "rust_test_host") { let name = module .get_string("name") .ok_or(TestMappingError::RuleWithoutName(module.typ.clone()))? .clone(); if !EXCLUDED_TESTS.contains(name.as_str()) { tests.insert(name); } } } Ok(tests) } } /// Finds all the rustlib dependencies mentioned in a blueprint file. pub(crate) trait RustDeps { // Finds all the rustlibs mentioned in a blueprint file. // This does a limited amount of evaluation, by doing concatenation and resolving // identifiers. So you can have `common_rustlibs = ["foo", "bar"] and do // rust_library { rustlibs = common_rustlibs + ["baz"] }` fn rust_deps(&self) -> BTreeSet; } impl RustDeps for BluePrint { fn rust_deps(&self) -> BTreeSet { let mut rustlibs = BTreeSet::new(); for module in &self.modules { if let Some(v) = module.get("rustlibs") { match v { Value::Array(_) => rustlibs.extend(v.as_string_vec()), Value::ConcatExpr(_) => rustlibs.extend(v.eval(self)), _ => { println!("Only know how to handle Array and ConcatExpr"); } } } } rustlibs } } // Convenience accessor for arrays of strings. trait AsStringVec { // Interpret a android_bp::Value as an array of strings, and convert it to // an array of owned strings. Any element that isn't a string is skipped. fn as_string_vec(&self) -> Vec; } impl AsStringVec for Value { fn as_string_vec(&self) -> Vec { if let Value::Array(vec) = self { vec.iter() .filter_map(|v| match v { Value::String(s) => Some(s.to_string()), _ => { println!("Array element is not a string"); None } }) .collect() } else { println!("Value is not an array"); vec![] } } } // Evaluate concatenations and resolve identifiers. trait EvalConcat { // Evaluate a concatenation expression, resolving it into a single vector of strings. // The elements being concatenated are assumed to be either identifiers or // arrays of strings. Otherwise, they are skipped. fn eval(&self, bp: &BluePrint) -> Vec; } impl EvalConcat for Value { fn eval(&self, bp: &BluePrint) -> Vec { let mut strings = Vec::new(); if let Value::ConcatExpr(expr) = self { for term in expr { match term { Value::Array(_) => strings.extend(term.as_string_vec()), Value::Ident(ident) => { if let Some(ident_val) = bp.variables.get(ident) { strings.extend(ident_val.as_string_vec()); } } _ => { println!("Concat term is neither ident nor array"); } } } } else { println!("Value is not a ConcatExpr"); } strings } } // Taken from update_crate_tests.py static EXCLUDED_TESTS: LazyLock> = LazyLock::new(|| { HashSet::from([ "ash_test_src_lib", "ash_test_tests_constant_size_arrays", "ash_test_tests_display", "shared_library_test_src_lib", "vulkano_test_src_lib", // These are helper binaries for aidl_integration_test // and aren't actually meant to run as individual tests. "aidl_test_rust_client", "aidl_test_rust_service", "aidl_test_rust_service_async", // This is a helper binary for AuthFsHostTest and shouldn't // be run directly. "open_then_run", // TODO: Remove when b/198197213 is closed. "diced_client_test", "CoverageRustSmokeTest", "libtrusty-rs-tests", "terminal-size_test_src_lib", ]) }); #[cfg(test)] mod tests { use super::*; #[test] fn rust_tests() -> Result<(), TestMappingError> { let bp = BluePrint::parse( r###" rust_test { name: "foo" } rust_test_host { name: "bar" } "###, ) .expect("Blueprint parse error"); assert_eq!(bp.rust_tests()?, BTreeSet::from(["foo".to_string(), "bar".to_string()])); Ok(()) } #[test] fn rust_deps() { let bp = BluePrint::parse( r###" rust_library { rustlibs: ["foo", "bar"] } rust_library { rustlibs: ["bar", "baz"] } "###, ) .expect("Blueprint parse error"); assert_eq!( bp.rust_deps(), BTreeSet::from(["foo".to_string(), "bar".to_string(), "baz".to_string()]) ); } #[test] fn rust_deps_eval() { let bp = BluePrint::parse( r###" foo = ["foo"] rust_library { rustlibs: foo + ["bar"] } "###, ) .expect("Blueprint parse error"); assert_eq!(bp.rust_deps(), BTreeSet::from(["foo".to_string(), "bar".to_string()])); } }