1 // Copyright (C) 2024 The Android Open Source Project 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 //! Convenience methods for working with Android blueprint files. 16 17 use std::{ 18 collections::{BTreeSet, HashSet}, 19 sync::LazyLock, 20 }; 21 22 use android_bp::{BluePrint, Value}; 23 24 use crate::TestMappingError; 25 26 /// Extract rust test rules from a blueprint file. 27 pub(crate) trait RustTests { 28 /// Returns the names of all rust_test and rust_test_host rules. rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError>29 fn rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError>; 30 } 31 32 impl RustTests for BluePrint { rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError>33 fn rust_tests(&self) -> Result<BTreeSet<String>, TestMappingError> { 34 let mut tests = BTreeSet::new(); 35 for module in &self.modules { 36 if matches!(module.typ.as_str(), "rust_test" | "rust_test_host") { 37 let name = module 38 .get_string("name") 39 .ok_or(TestMappingError::RuleWithoutName(module.typ.clone()))? 40 .clone(); 41 if !EXCLUDED_TESTS.contains(name.as_str()) { 42 tests.insert(name); 43 } 44 } 45 } 46 Ok(tests) 47 } 48 } 49 50 /// Finds all the rustlib dependencies mentioned in a blueprint file. 51 pub(crate) trait RustDeps { 52 // Finds all the rustlibs mentioned in a blueprint file. 53 // This does a limited amount of evaluation, by doing concatenation and resolving 54 // identifiers. So you can have `common_rustlibs = ["foo", "bar"] and do 55 // rust_library { rustlibs = common_rustlibs + ["baz"] }` rust_deps(&self) -> BTreeSet<String>56 fn rust_deps(&self) -> BTreeSet<String>; 57 } 58 59 impl RustDeps for BluePrint { rust_deps(&self) -> BTreeSet<String>60 fn rust_deps(&self) -> BTreeSet<String> { 61 let mut rustlibs = BTreeSet::new(); 62 for module in &self.modules { 63 if let Some(v) = module.get("rustlibs") { 64 match v { 65 Value::Array(_) => rustlibs.extend(v.as_string_vec()), 66 Value::ConcatExpr(_) => rustlibs.extend(v.eval(self)), 67 _ => { 68 println!("Only know how to handle Array and ConcatExpr"); 69 } 70 } 71 } 72 } 73 rustlibs 74 } 75 } 76 77 // Convenience accessor for arrays of strings. 78 trait AsStringVec { 79 // Interpret a android_bp::Value as an array of strings, and convert it to 80 // an array of owned strings. Any element that isn't a string is skipped. as_string_vec(&self) -> Vec<String>81 fn as_string_vec(&self) -> Vec<String>; 82 } 83 84 impl AsStringVec for Value { as_string_vec(&self) -> Vec<String>85 fn as_string_vec(&self) -> Vec<String> { 86 if let Value::Array(vec) = self { 87 vec.iter() 88 .filter_map(|v| match v { 89 Value::String(s) => Some(s.to_string()), 90 _ => { 91 println!("Array element is not a string"); 92 None 93 } 94 }) 95 .collect() 96 } else { 97 println!("Value is not an array"); 98 vec![] 99 } 100 } 101 } 102 103 // Evaluate concatenations and resolve identifiers. 104 trait EvalConcat { 105 // Evaluate a concatenation expression, resolving it into a single vector of strings. 106 // The elements being concatenated are assumed to be either identifiers or 107 // arrays of strings. Otherwise, they are skipped. eval(&self, bp: &BluePrint) -> Vec<String>108 fn eval(&self, bp: &BluePrint) -> Vec<String>; 109 } 110 111 impl EvalConcat for Value { eval(&self, bp: &BluePrint) -> Vec<String>112 fn eval(&self, bp: &BluePrint) -> Vec<String> { 113 let mut strings = Vec::new(); 114 if let Value::ConcatExpr(expr) = self { 115 for term in expr { 116 match term { 117 Value::Array(_) => strings.extend(term.as_string_vec()), 118 Value::Ident(ident) => { 119 if let Some(ident_val) = bp.variables.get(ident) { 120 strings.extend(ident_val.as_string_vec()); 121 } 122 } 123 _ => { 124 println!("Concat term is neither ident nor array"); 125 } 126 } 127 } 128 } else { 129 println!("Value is not a ConcatExpr"); 130 } 131 strings 132 } 133 } 134 135 // Taken from update_crate_tests.py 136 static EXCLUDED_TESTS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| { 137 HashSet::from([ 138 "ash_test_src_lib", 139 "ash_test_tests_constant_size_arrays", 140 "ash_test_tests_display", 141 "shared_library_test_src_lib", 142 "vulkano_test_src_lib", 143 // These are helper binaries for aidl_integration_test 144 // and aren't actually meant to run as individual tests. 145 "aidl_test_rust_client", 146 "aidl_test_rust_service", 147 "aidl_test_rust_service_async", 148 // This is a helper binary for AuthFsHostTest and shouldn't 149 // be run directly. 150 "open_then_run", 151 // TODO: Remove when b/198197213 is closed. 152 "diced_client_test", 153 "CoverageRustSmokeTest", 154 "libtrusty-rs-tests", 155 "terminal-size_test_src_lib", 156 ]) 157 }); 158 159 #[cfg(test)] 160 mod tests { 161 use super::*; 162 163 #[test] rust_tests() -> Result<(), TestMappingError>164 fn rust_tests() -> Result<(), TestMappingError> { 165 let bp = BluePrint::parse( 166 r###" 167 rust_test { name: "foo" } 168 rust_test_host { name: "bar" } 169 "###, 170 ) 171 .expect("Blueprint parse error"); 172 assert_eq!(bp.rust_tests()?, BTreeSet::from(["foo".to_string(), "bar".to_string()])); 173 Ok(()) 174 } 175 176 #[test] rust_deps()177 fn rust_deps() { 178 let bp = BluePrint::parse( 179 r###" 180 rust_library { rustlibs: ["foo", "bar"] } 181 rust_library { rustlibs: ["bar", "baz"] } 182 "###, 183 ) 184 .expect("Blueprint parse error"); 185 assert_eq!( 186 bp.rust_deps(), 187 BTreeSet::from(["foo".to_string(), "bar".to_string(), "baz".to_string()]) 188 ); 189 } 190 191 #[test] rust_deps_eval()192 fn rust_deps_eval() { 193 let bp = BluePrint::parse( 194 r###" 195 foo = ["foo"] 196 rust_library { rustlibs: foo + ["bar"] } 197 "###, 198 ) 199 .expect("Blueprint parse error"); 200 assert_eq!(bp.rust_deps(), BTreeSet::from(["foo".to_string(), "bar".to_string()])); 201 } 202 } 203