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 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
43def 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
68def 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
102def 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
140if __name__ == '__main__':
141  main()
142