blob: e3d23c3b3dd2874c9394c860c7ebd894abbaf3ed [file] [log] [blame]
// 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<BTreeSet<String>, TestMappingError>;
}
impl RustTests for BluePrint {
fn rust_tests(&self) -> Result<BTreeSet<String>, 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<String>;
}
impl RustDeps for BluePrint {
fn rust_deps(&self) -> BTreeSet<String> {
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<String>;
}
impl AsStringVec for Value {
fn as_string_vec(&self) -> Vec<String> {
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<String>;
}
impl EvalConcat for Value {
fn eval(&self, bp: &BluePrint) -> Vec<String> {
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<HashSet<&'static str>> = 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()]));
}
}