1#!/usr/bin/env python3 2 3import argparse 4import csv 5import glob 6import json 7import os 8import sys 9 10HELP_MSG = ''' 11This script computes the differences between two system images (system1 - 12system2), and lists the files grouped by package. The difference is just based 13on the existence of the file, not on its contents. 14''' 15 16VENDOR_PATH_MAP = { 17 'vendor/google' : 'Google', 18 'vendor/unbundled_google': 'Google', 19 'vendor/verizon' : 'Verizon', 20 'vendor/qcom' : 'Qualcomm', 21 'vendor/tmobile' : 'TMobile', 22 'vendor/mediatek' : 'Mediatek', 23 'vendor/htc' : 'HTC', 24 'vendor/realtek' : 'Realtek' 25} 26 27def _get_relative_out_path_from_root(out_path): 28 """Given a path to a target out directory, get the relative path from the 29 Android root. 30 31 The module-info.json file paths are relative to the root source folder 32 ie. one directory before out.""" 33 system_path = os.path.normpath(os.path.join(out_path, 'system')) 34 system_path_dirs = system_path.split(os.sep) 35 out_index = system_path_dirs.index("out") 36 return os.path.join(*system_path_dirs[out_index:]) 37 38def system_files(path): 39 """Returns an array of the files under /system, recursively, and ignoring 40 symbolic-links""" 41 system_files = [] 42 system_prefix = os.path.join(path, 'system') 43 # Skip trailing '/' 44 system_prefix_len = len(system_prefix) + 1 45 46 for root, dirs, files in os.walk(system_prefix, topdown=True): 47 for file in files: 48 # Ignore symbolic links. 49 if not os.path.islink(os.path.join(root, file)): 50 system_files.append(os.path.join(root[system_prefix_len:], file)) 51 52 return system_files 53 54def system_files_to_package_map(path): 55 """Returns a dictionary mapping from each file in the /system partition to its 56 package, according to modules-info.json.""" 57 system_files_to_package_map = {} 58 system_prefix = _get_relative_out_path_from_root(path) 59 # Skip trailing '/' 60 system_prefix_len = len(system_prefix) + 1 61 62 with open(os.path.join(path, 'module-info.json')) as module_info_json: 63 module_info = json.load(module_info_json) 64 for module in module_info: 65 installs = module_info[module]['installed'] 66 for install in installs: 67 if install.startswith(system_prefix): 68 system_file = install[system_prefix_len:] 69 # Not clear if collisions can ever happen in modules-info.json (e.g. 70 # the same file installed by multiple packages), but it doesn't hurt 71 # to check. 72 if system_file in system_files_to_package_map: 73 system_files_to_package_map[system_file] = "--multiple--" 74 else: 75 system_files_to_package_map[system_file] = module 76 77 return system_files_to_package_map 78 79def package_to_vendor_map(path): 80 """Returns a dictionary mapping from each package in modules-info.json to its 81 vendor. If a vendor cannot be found, it maps to "--unknown--". Those cases 82 are: 83 84 1. The package maps to multiple modules (e.g., one in APPS and one in 85 SHARED_LIBRARIES. 86 2. The path to the module is not one of the recognized vendor paths in 87 VENDOR_PATH_MAP.""" 88 package_vendor_map = {} 89 system_prefix = os.path.join(path, 'system') 90 # Skip trailing '/' 91 system_prefix_len = len(system_prefix) + 1 92 vendor_prefixes = VENDOR_PATH_MAP.keys() 93 94 with open(os.path.join(path, 'module-info.json')) as module_info_json: 95 module_info = json.load(module_info_json) 96 for module in module_info: 97 paths = module_info[module]['path'] 98 vendor = "" 99 if len(paths) == 1: 100 path = paths[0] 101 for prefix in vendor_prefixes: 102 if path.startswith(prefix): 103 vendor = VENDOR_PATH_MAP[prefix] 104 break 105 if vendor == "": 106 vendor = "--unknown--" 107 else: 108 vendor = "--multiple--" 109 package_vendor_map[module] = vendor 110 111 return package_vendor_map 112 113def main(): 114 parser = argparse.ArgumentParser(description=HELP_MSG) 115 parser.add_argument("out1", help="First $OUT directory") 116 parser.add_argument("out2", help="Second $OUT directory") 117 args = parser.parse_args() 118 119 system_files1 = system_files(args.out1) 120 system_files2 = system_files(args.out2) 121 system_files_diff = set(system_files1) - set(system_files2) 122 123 system_files_map = system_files_to_package_map(args.out1) 124 package_vendor_map = package_to_vendor_map(args.out1) 125 packages = {} 126 127 for file in system_files_diff: 128 if file in system_files_map: 129 package = system_files_map[file] 130 else: 131 package = "--unknown--" 132 133 if package in packages: 134 packages[package].append(file) 135 else: 136 packages[package] = [file] 137 138 with open(os.path.join(args.out1, 'module-info.json')) as module_info_json: 139 module_info = json.load(module_info_json) 140 141 writer = csv.writer(sys.stdout, quoting = csv.QUOTE_NONNUMERIC, 142 delimiter = ',', lineterminator = '\n') 143 for package, files in packages.iteritems(): 144 for file in files: 145 # Group sources of the deltas. 146 if package in package_vendor_map: 147 vendor = package_vendor_map[package] 148 else: 149 vendor = "--unknown--" 150 # Get file size. 151 full_path = os.path.join(args.out1, 'system', file) 152 size = os.stat(full_path).st_size 153 if package in module_info.keys(): 154 module_path = module_info[package]['path'] 155 else: 156 module_path = '' 157 writer.writerow([ 158 # File that exists in out1 but not out2. 159 file, 160 # Module name that the file came from. 161 package, 162 # Path to the module. 163 module_path, 164 # File size. 165 size, 166 # Vendor owner. 167 vendor]) 168 169if __name__ == '__main__': 170 main() 171