Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python3
      2 
      3 import argparse
      4 import csv
      5 import glob
      6 import json
      7 import os
      8 import sys
      9 
     10 HELP_MSG = '''
     11 This script computes the differences between two system images (system1 -
     12 system2), and lists the files grouped by package. The difference is just based
     13 on the existence of the file, not on its contents.
     14 '''
     15 
     16 VENDOR_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 
     27 def system_files(path):
     28   """Returns an array of the files under /system, recursively, and ignoring
     29   symbolic-links"""
     30   system_files = []
     31   system_prefix = os.path.join(path, 'system')
     32   # Skip trailing '/'
     33   system_prefix_len = len(system_prefix) + 1
     34 
     35   for root, dirs, files in os.walk(system_prefix, topdown=True):
     36     for file in files:
     37       # Ignore symbolic links.
     38       if not os.path.islink(os.path.join(root, file)):
     39         system_files.append(os.path.join(root[system_prefix_len:], file))
     40 
     41   return system_files
     42 
     43 def system_files_to_package_map(path):
     44   """Returns a dictionary mapping from each file in the /system partition to its
     45   package, according to modules-info.json."""
     46   system_files_to_package_map = {}
     47   system_prefix = os.path.join(path, 'system')
     48   # Skip trailing '/'
     49   system_prefix_len = len(system_prefix) + 1
     50 
     51   with open(os.path.join(path, 'module-info.json')) as module_info_json:
     52     module_info = json.load(module_info_json)
     53     for module in module_info:
     54       installs = module_info[module]['installed']
     55       for install in installs:
     56         if install.startswith(system_prefix):
     57           system_file = install[system_prefix_len:]
     58           # Not clear if collisions can ever happen in modules-info.json (e.g.
     59           # the same file installed by multiple packages), but it doesn't hurt
     60           # to check.
     61           if system_file in system_files_to_package_map:
     62             system_files_to_package_map[system_file] = "--multiple--"
     63           else:
     64             system_files_to_package_map[system_file] = module
     65 
     66   return system_files_to_package_map
     67 
     68 def package_to_vendor_map(path):
     69   """Returns a dictionary mapping from each package in modules-info.json to its
     70   vendor. If a vendor cannot be found, it maps to "--unknown--". Those cases
     71   are:
     72 
     73     1. The package maps to multiple modules (e.g., one in APPS and one in
     74        SHARED_LIBRARIES.
     75     2. The path to the module is not one of the recognized vendor paths in
     76        VENDOR_PATH_MAP."""
     77   package_vendor_map = {}
     78   system_prefix = os.path.join(path, 'system')
     79   # Skip trailing '/'
     80   system_prefix_len = len(system_prefix) + 1
     81   vendor_prefixes = VENDOR_PATH_MAP.keys()
     82 
     83   with open(os.path.join(path, 'module-info.json')) as module_info_json:
     84     module_info = json.load(module_info_json)
     85     for module in module_info:
     86       paths = module_info[module]['path']
     87       vendor = ""
     88       if len(paths) == 1:
     89         path = paths[0]
     90         for prefix in vendor_prefixes:
     91           if path.startswith(prefix):
     92             vendor = VENDOR_PATH_MAP[prefix]
     93             break
     94         if vendor == "":
     95           vendor = "--unknown--"
     96       else:
     97         vendor = "--multiple--"
     98       package_vendor_map[module] = vendor
     99 
    100   return package_vendor_map
    101 
    102 def main():
    103   parser = argparse.ArgumentParser(description=HELP_MSG)
    104   parser.add_argument("out1", help="First $OUT directory")
    105   parser.add_argument("out2", help="Second $OUT directory")
    106   args = parser.parse_args()
    107 
    108   system_files1 = system_files(args.out1)
    109   system_files2 = system_files(args.out2)
    110   system_files_diff = set(system_files1) - set(system_files2)
    111 
    112   system_files_map = system_files_to_package_map(args.out1)
    113   package_vendor_map = package_to_vendor_map(args.out1)
    114   packages = {}
    115 
    116   for file in system_files_diff:
    117     if file in system_files_map:
    118       package = system_files_map[file]
    119     else:
    120       package = "--unknown--"
    121 
    122     if package in packages:
    123       packages[package].append(file)
    124     else:
    125       packages[package] = [file]
    126 
    127   writer = csv.writer(sys.stdout, quoting = csv.QUOTE_NONNUMERIC,
    128                       delimiter = ',', lineterminator = '\n')
    129   for package, files in packages.items():
    130     for file in files:
    131       if package in package_vendor_map:
    132         vendor = package_vendor_map[package]
    133       else:
    134         vendor = "--unknown--"
    135       # Get file size
    136       full_path = os.path.join(args.out1, 'system', file)
    137       size = os.stat(full_path).st_size
    138       writer.writerow([vendor, package, file, size])
    139 
    140 if __name__ == '__main__':
    141   main()
    142