diff options
Diffstat (limited to 'scripts/redundancy_check/srcs/main.rs')
-rw-r--r-- | scripts/redundancy_check/srcs/main.rs | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/scripts/redundancy_check/srcs/main.rs b/scripts/redundancy_check/srcs/main.rs new file mode 100644 index 00000000..401eeb74 --- /dev/null +++ b/scripts/redundancy_check/srcs/main.rs @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2022, 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. + */ + +//! Reports redundant AIDL libraries included in a partition. + +use anyhow::{Context, Result}; +use clap::Parser; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::BufReader; +use std::path::{Path, PathBuf}; + +#[derive(Parser, Debug)] +#[structopt()] +struct Opt { + /// JSON file with list of files installed in a partition, e.g. "$OUT/installed-files.json". + #[clap(long)] + installed_files_json: PathBuf, + + /// JSON file with metadata for AIDL interfaces. Optional, but fewer checks are performed when + /// unset. + #[clap(long)] + aidl_metadata_json: Option<PathBuf>, +} + +/// "aidl_metadata.json" entry. +#[derive(Debug, serde::Deserialize)] +struct AidlInterfaceMetadata { + /// Name of module defining package. + name: String, +} + +/// "installed-files.json" entry. +#[derive(Debug, serde::Deserialize)] +struct InstalledFile { + /// Full file path. + #[serde(rename = "Name")] + name: String, + /// File size. + #[serde(rename = "Size")] + size: u64, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +enum LibDir { + Lib, + Lib64, +} + +/// An instance of an AIDL interface lib. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +struct AidlInstance { + installed_path: String, + size: u64, + name: String, + variant: String, // e.g. "ndk" or "cpp" + version: usize, + lib_dir: LibDir, +} + +/// Deserializes a JSON file at `path` into an object of type `T`. +fn read_json_file<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> { + let file = File::open(path).with_context(|| format!("failed to open: {}", path.display()))?; + serde_json::from_reader(BufReader::new(file)) + .with_context(|| format!("failed to read: {}", path.display())) +} + +/// Extracts AIDL lib info an `InstalledFile`, mainly by parsing the file path. Returns `None` if +/// it doesn't look like an AIDL lib. +fn extract_aidl_instance(installed_file: &InstalledFile) -> Option<AidlInstance> { + // example: android.hardware.security.keymint-V2-ndk.so + let lib_regex = regex::Regex::new(r#".*/(lib|lib64)/([^-]*)-V(\d+)-([^.]+)\."#) + .expect("failed to parse regex"); + let captures = lib_regex.captures(&installed_file.name)?; + let (dir, name, version, variant) = (&captures[1], &captures[2], &captures[3], &captures[4]); + Some(AidlInstance { + installed_path: installed_file.name.clone(), + size: installed_file.size, + name: name.to_string(), + variant: variant.to_string(), + version: version.parse().unwrap(), + lib_dir: if dir == "lib64" { LibDir::Lib64 } else { LibDir::Lib }, + }) +} + +fn main() -> Result<()> { + let args = Opt::parse(); + + // Read the metadata file if available. + let metadata_list: Option<Vec<AidlInterfaceMetadata>> = match &args.aidl_metadata_json { + Some(aidl_metadata_json) => read_json_file(aidl_metadata_json)?, + None => None, + }; + let is_valid_aidl_lib = |name: &str| match &metadata_list { + Some(x) => x.iter().any(|metadata| metadata.name == name), + None => true, + }; + + // Read the "installed-files.json" and create a list of AidlInstance. + let installed_files: Vec<InstalledFile> = read_json_file(&args.installed_files_json)?; + let instances: Vec<AidlInstance> = installed_files + .iter() + .filter_map(extract_aidl_instance) + .filter(|instance| { + if !is_valid_aidl_lib(&instance.name) { + eprintln!( + "WARNING: {} looks like an AIDL lib, but has no metadata", + &instance.installed_path + ); + return false; + } + true + }) + .collect(); + + // Group redundant AIDL lib instances together. + let groups: BTreeMap<(String, LibDir), Vec<&AidlInstance>> = + instances.iter().fold(BTreeMap::new(), |mut acc, x| { + let key = (x.name.clone(), x.lib_dir); + acc.entry(key).or_default().push(x); + acc + }); + let mut total_wasted_bytes = 0; + for (group_key, mut instances) in groups { + if instances.len() > 1 { + instances.sort(); + // Prefer the highest version, break ties favoring ndk. + let preferred_instance = instances + .iter() + .max_by_key(|x| (x.version, i32::from(x.variant == "ndk"))) + .unwrap(); + let wasted_bytes: u64 = + instances.iter().filter(|x| *x != preferred_instance).map(|x| x.size).sum(); + println!("Found redundant AIDL instances for {:?}", group_key); + for instance in instances.iter() { + println!( + "\t{}\t({:.2} KiB){}", + instance.installed_path, + instance.size as f64 / 1024.0, + if instance == preferred_instance { " <- preferred" } else { "" } + ); + } + total_wasted_bytes += wasted_bytes; + println!("\t(potential savings: {:.2} KiB)", wasted_bytes as f64 / 1024.0); + println!(); + } + } + println!("total potential savings: {:.2} KiB", total_wasted_bytes as f64 / 1024.0); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_opt() { + Opt::command().debug_assert(); + } +} |