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 //! Find reverse dependencies of Rust crates by searching blueprint files 16 //! for rustlibs. 17 18 use std::{ 19 collections::{BTreeSet, HashMap}, 20 path::{Path, PathBuf}, 21 process::Command, 22 str::from_utf8, 23 sync::{LazyLock, Mutex}, 24 }; 25 26 use android_bp::BluePrint; 27 use owning_ref::MutexGuardRef; 28 29 use crate::{blueprint::RustDeps, TestMappingError}; 30 31 pub(crate) struct ReverseDeps { 32 // Mapping from Rust build rule target name => list of paths that depend on it. 33 rdeps: HashMap<String, BTreeSet<String>>, 34 } 35 36 impl ReverseDeps { 37 /// Returns a reverse dependency lookup for the Android source repo 38 /// at the specified absolute path. Each lookup is created once 39 /// and cached. for_repo( repo_root: &Path, ) -> MutexGuardRef<'static, HashMap<PathBuf, ReverseDeps>, ReverseDeps>40 pub fn for_repo( 41 repo_root: &Path, 42 ) -> MutexGuardRef<'static, HashMap<PathBuf, ReverseDeps>, ReverseDeps> { 43 static RDEPS: LazyLock<Mutex<HashMap<PathBuf, ReverseDeps>>> = 44 LazyLock::new(|| Mutex::new(HashMap::new())); 45 46 RDEPS 47 .lock() 48 .unwrap() 49 .entry(repo_root.to_path_buf()) 50 .or_insert_with(|| ReverseDeps::grep_and_parse(repo_root).unwrap()); 51 MutexGuardRef::new(RDEPS.lock().unwrap()).map(|rdeps| rdeps.get(repo_root).unwrap()) 52 } 53 /// Get the paths that depend on a rust library. get(&self, name: &str) -> Option<&BTreeSet<String>>54 pub fn get(&self, name: &str) -> Option<&BTreeSet<String>> { 55 self.rdeps.get(name) 56 } grep_and_parse<P: Into<PathBuf>>(repo_root: P) -> Result<ReverseDeps, TestMappingError>57 fn grep_and_parse<P: Into<PathBuf>>(repo_root: P) -> Result<ReverseDeps, TestMappingError> { 58 let repo_root = repo_root.into(); 59 // Empirically, TEST_MAPPING files for 3rd party crates only 60 // have imports from external, packages, system, and tools. 61 let output = Command::new("grep") 62 .args([ 63 "-r", 64 "-l", 65 "--include=*.bp", 66 "rustlibs", 67 "external", 68 "packages", 69 "system", 70 "tools", 71 ]) 72 .current_dir(&repo_root) 73 .output()?; 74 let stdout = from_utf8(&output.stdout)?; 75 let mut rdeps = HashMap::new(); 76 for line in stdout.lines() { 77 if EXCLUDED_PATHS.iter().any(|excluded| line.starts_with(excluded)) { 78 continue; 79 } 80 let (dir, _) = 81 line.rsplit_once('/').ok_or(TestMappingError::GrepParseError(line.to_string()))?; 82 if let Ok(bp) = BluePrint::from_file(repo_root.join(line)) { 83 for rustlib in bp.rust_deps() { 84 rdeps.entry(rustlib).or_insert(BTreeSet::new()).insert(dir.to_string()); 85 } 86 } 87 } 88 Ok(ReverseDeps { rdeps }) 89 } 90 } 91 92 // Originally taken from update_crate_tests.py, but of the values in there, only external/crosvm 93 // seems to exist. 94 static EXCLUDED_PATHS: LazyLock<Vec<&'static str>> = 95 LazyLock::new(|| vec!["external/crosvm/", "development/tools/cargo_embargo/testdata/"]); 96