#!/usr/bin/env python3 import argparse import csv import glob import json import os import sys HELP_MSG = ''' This script computes the differences between two system images (system1 - system2), and lists the files grouped by package. The difference is just based on the existence of the file, not on its contents. ''' VENDOR_PATH_MAP = { 'vendor/google' : 'Google', 'vendor/unbundled_google': 'Google', 'vendor/verizon' : 'Verizon', 'vendor/qcom' : 'Qualcomm', 'vendor/tmobile' : 'TMobile', 'vendor/mediatek' : 'Mediatek', 'vendor/htc' : 'HTC', 'vendor/realtek' : 'Realtek' } def _get_relative_out_path_from_root(out_path): """Given a path to a target out directory, get the relative path from the Android root. The module-info.json file paths are relative to the root source folder ie. one directory before out.""" system_path = os.path.normpath(os.path.join(out_path, 'system')) system_path_dirs = system_path.split(os.sep) out_index = system_path_dirs.index("out") return os.path.join(*system_path_dirs[out_index:]) def system_files(path): """Returns an array of the files under /system, recursively, and ignoring symbolic-links""" system_files = [] system_prefix = os.path.join(path, 'system') # Skip trailing '/' system_prefix_len = len(system_prefix) + 1 for root, dirs, files in os.walk(system_prefix, topdown=True): for file in files: # Ignore symbolic links. if not os.path.islink(os.path.join(root, file)): system_files.append(os.path.join(root[system_prefix_len:], file)) return system_files def system_files_to_package_map(path): """Returns a dictionary mapping from each file in the /system partition to its package, according to modules-info.json.""" system_files_to_package_map = {} system_prefix = _get_relative_out_path_from_root(path) # Skip trailing '/' system_prefix_len = len(system_prefix) + 1 with open(os.path.join(path, 'module-info.json')) as module_info_json: module_info = json.load(module_info_json) for module in module_info: installs = module_info[module]['installed'] for install in installs: if install.startswith(system_prefix): system_file = install[system_prefix_len:] # Not clear if collisions can ever happen in modules-info.json (e.g. # the same file installed by multiple packages), but it doesn't hurt # to check. if system_file in system_files_to_package_map: system_files_to_package_map[system_file] = "--multiple--" else: system_files_to_package_map[system_file] = module return system_files_to_package_map def package_to_vendor_map(path): """Returns a dictionary mapping from each package in modules-info.json to its vendor. If a vendor cannot be found, it maps to "--unknown--". Those cases are: 1. The package maps to multiple modules (e.g., one in APPS and one in SHARED_LIBRARIES. 2. The path to the module is not one of the recognized vendor paths in VENDOR_PATH_MAP.""" package_vendor_map = {} system_prefix = os.path.join(path, 'system') # Skip trailing '/' system_prefix_len = len(system_prefix) + 1 vendor_prefixes = VENDOR_PATH_MAP.keys() with open(os.path.join(path, 'module-info.json')) as module_info_json: module_info = json.load(module_info_json) for module in module_info: paths = module_info[module]['path'] vendor = "" if len(paths) == 1: path = paths[0] for prefix in vendor_prefixes: if path.startswith(prefix): vendor = VENDOR_PATH_MAP[prefix] break if vendor == "": vendor = "--unknown--" else: vendor = "--multiple--" package_vendor_map[module] = vendor return package_vendor_map def main(): parser = argparse.ArgumentParser(description=HELP_MSG) parser.add_argument("out1", help="First $OUT directory") parser.add_argument("out2", help="Second $OUT directory") args = parser.parse_args() system_files1 = system_files(args.out1) system_files2 = system_files(args.out2) system_files_diff = set(system_files1) - set(system_files2) system_files_map = system_files_to_package_map(args.out1) package_vendor_map = package_to_vendor_map(args.out1) packages = {} for file in system_files_diff: if file in system_files_map: package = system_files_map[file] else: package = "--unknown--" if package in packages: packages[package].append(file) else: packages[package] = [file] with open(os.path.join(args.out1, 'module-info.json')) as module_info_json: module_info = json.load(module_info_json) writer = csv.writer(sys.stdout, quoting = csv.QUOTE_NONNUMERIC, delimiter = ',', lineterminator = '\n') for package, files in packages.iteritems(): for file in files: # Group sources of the deltas. if package in package_vendor_map: vendor = package_vendor_map[package] else: vendor = "--unknown--" # Get file size. full_path = os.path.join(args.out1, 'system', file) size = os.stat(full_path).st_size if package in module_info.keys(): module_path = module_info[package]['path'] else: module_path = '' writer.writerow([ # File that exists in out1 but not out2. file, # Module name that the file came from. package, # Path to the module. module_path, # File size. size, # Vendor owner. vendor]) if __name__ == '__main__': main()