| #!/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() |