176 lines
5.8 KiB
Rust
176 lines
5.8 KiB
Rust
/*
|
|
* 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();
|
|
}
|
|
}
|