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 28 use crate::{blueprint::RustDeps, Error}; 29 30 #[derive(Clone)] 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) -> ReverseDeps40 pub fn for_repo(repo_root: &Path) -> ReverseDeps { 41 static RDEPS: LazyLock<Mutex<HashMap<PathBuf, ReverseDeps>>> = 42 LazyLock::new(|| Mutex::new(HashMap::new())); 43 44 RDEPS 45 .lock() 46 .unwrap() 47 .entry(repo_root.to_path_buf()) 48 .or_insert_with(|| ReverseDeps::grep_and_parse(repo_root).unwrap()) 49 .clone() 50 } 51 /// Get the paths that depend on a rust library. get(&self, name: &str) -> Option<&BTreeSet<String>>52 pub fn get(&self, name: &str) -> Option<&BTreeSet<String>> { 53 self.rdeps.get(name) 54 } grep_and_parse<P: Into<PathBuf>>(repo_root: P) -> Result<ReverseDeps, Error>55 fn grep_and_parse<P: Into<PathBuf>>(repo_root: P) -> Result<ReverseDeps, Error> { 56 let repo_root = repo_root.into(); 57 // Empirically, TEST_MAPPING files for 3rd party crates only 58 // have imports from external, packages, system, and tools. 59 let output = Command::new("grep") 60 .args([ 61 "-r", 62 "-l", 63 "--include=*.bp", 64 "rustlibs", 65 "external", 66 "packages", 67 "system", 68 "tools", 69 ]) 70 .current_dir(&repo_root) 71 .output()?; 72 let stdout = from_utf8(&output.stdout)?; 73 let mut rdeps = HashMap::new(); 74 for line in stdout.lines() { 75 if EXCLUDED_PATHS.iter().any(|excluded| line.starts_with(excluded)) { 76 continue; 77 } 78 let (dir, _) = line.rsplit_once('/').ok_or(Error::GrepParseError(line.to_string()))?; 79 if let Ok(bp) = BluePrint::from_file(repo_root.join(line)) { 80 for rustlib in bp.rust_deps() { 81 rdeps.entry(rustlib).or_insert(BTreeSet::new()).insert(dir.to_string()); 82 } 83 } 84 } 85 Ok(ReverseDeps { rdeps }) 86 } 87 } 88 89 // Originally taken from update_crate_tests.py, but of the values in there, only external/crosvm 90 // seems to exist. 91 static EXCLUDED_PATHS: LazyLock<Vec<&'static str>> = 92 LazyLock::new(|| vec!["external/crosvm/", "development/tools/cargo_embargo/testdata/"]); 93