aboutsummaryrefslogtreecommitdiff
path: root/scripts/redundancy_check/srcs/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/redundancy_check/srcs/main.rs')
-rw-r--r--scripts/redundancy_check/srcs/main.rs175
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();
+ }
+}