blob: acb7448cab7e65289396caba1379631450084c97 [file] [log] [blame]
Dan Willemsen99568622015-11-06 18:36:16 -08001#!/usr/bin/env python
2#
3# Copyright (C) 2009 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18#
19# Finds differences between two target files packages
20#
21
22from __future__ import print_function
23
24import argparse
25import contextlib
26import os
27import re
28import subprocess
Tao Bao5dd9a2c2015-11-24 15:17:17 -080029import sys
Dan Willemsen99568622015-11-06 18:36:16 -080030import tempfile
31
32def ignore(name):
33 """
34 Files to ignore when diffing
35
36 These are packages that we're already diffing elsewhere,
37 or files that we expect to be different for every build,
38 or known problems.
39 """
40
41 # We're looking at the files that make the images, so no need to search them
42 if name in ['IMAGES']:
43 return True
44 # These are packages of the recovery partition, which we're already diffing
45 if name in ['SYSTEM/etc/recovery-resource.dat',
46 'SYSTEM/recovery-from-boot.p']:
47 return True
48
49 # These files are just the BUILD_NUMBER, and will always be different
50 if name in ['BOOT/RAMDISK/selinux_version',
51 'RECOVERY/RAMDISK/selinux_version']:
52 return True
53
Dan Willemsen99568622015-11-06 18:36:16 -080054 # b/25348136 libpac.so changes with every build
55 if name in ['SYSTEM/lib/libpac.so',
56 'SYSTEM/lib64/libpac.so']:
57 return True
58
59 return False
60
61
62def rewrite_build_property(original, new):
63 """
64 Rewrite property files to remove values known to change for every build
65 """
66
67 skipped = ['ro.bootimage.build.date=',
68 'ro.bootimage.build.date.utc=',
69 'ro.bootimage.build.fingerprint=',
70 'ro.build.id=',
71 'ro.build.display.id=',
72 'ro.build.version.incremental=',
73 'ro.build.date=',
74 'ro.build.date.utc=',
75 'ro.build.host=',
Dan Willemsen734d78c2016-02-01 13:38:25 -080076 'ro.build.user=',
Dan Willemsen99568622015-11-06 18:36:16 -080077 'ro.build.description=',
78 'ro.build.fingerprint=',
79 'ro.expect.recovery_id=',
80 'ro.vendor.build.date=',
81 'ro.vendor.build.date.utc=',
82 'ro.vendor.build.fingerprint=']
83
84 for line in original:
85 skip = False
86 for s in skipped:
87 if line.startswith(s):
88 skip = True
89 break
90 if not skip:
91 new.write(line)
92
93
94def trim_install_recovery(original, new):
95 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -080096 Rewrite the install-recovery script to remove the hash of the recovery
97 partition.
Dan Willemsen99568622015-11-06 18:36:16 -080098 """
99 for line in original:
100 new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
101
102def sort_file(original, new):
103 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800104 Sort the file. Some OTA metadata files are not in a deterministic order
105 currently.
Dan Willemsen99568622015-11-06 18:36:16 -0800106 """
107 lines = original.readlines()
108 lines.sort()
109 for line in lines:
110 new.write(line)
111
112# Map files to the functions that will modify them for diffing
113REWRITE_RULES = {
114 'BOOT/RAMDISK/default.prop': rewrite_build_property,
115 'RECOVERY/RAMDISK/default.prop': rewrite_build_property,
116 'SYSTEM/build.prop': rewrite_build_property,
117 'VENDOR/build.prop': rewrite_build_property,
118
119 'SYSTEM/bin/install-recovery.sh': trim_install_recovery,
120
121 'META/boot_filesystem_config.txt': sort_file,
122 'META/filesystem_config.txt': sort_file,
123 'META/recovery_filesystem_config.txt': sort_file,
124 'META/vendor_filesystem_config.txt': sort_file,
125}
126
127@contextlib.contextmanager
128def preprocess(name, filename):
129 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800130 Optionally rewrite files before diffing them, to remove known-variable
131 information.
Dan Willemsen99568622015-11-06 18:36:16 -0800132 """
133 if name in REWRITE_RULES:
134 with tempfile.NamedTemporaryFile() as newfp:
135 with open(filename, 'r') as oldfp:
136 REWRITE_RULES[name](oldfp, newfp)
137 newfp.flush()
138 yield newfp.name
139 else:
140 yield filename
141
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800142def diff(name, file1, file2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800143 """
144 Diff a file pair with diff, running preprocess() on the arguments first.
145 """
146 with preprocess(name, file1) as f1:
147 with preprocess(name, file2) as f2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800148 proc = subprocess.Popen(['diff', f1, f2], stdout=subprocess.PIPE,
149 stderr=subprocess.STDOUT)
150 (stdout, _) = proc.communicate()
Dan Willemsen99568622015-11-06 18:36:16 -0800151 if proc.returncode == 0:
152 return
153 stdout = stdout.strip()
154 if stdout == 'Binary files %s and %s differ' % (f1, f2):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800155 print("%s: Binary files differ" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800156 else:
157 for line in stdout.strip().split('\n'):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800158 print("%s: %s" % (name, line), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800159
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800160def recursiveDiff(prefix, dir1, dir2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800161 """
162 Recursively diff two directories, checking metadata then calling diff()
163 """
164 list1 = sorted(os.listdir(dir1))
165 list2 = sorted(os.listdir(dir2))
166
167 for entry in list1:
168 name = os.path.join(prefix, entry)
169 name1 = os.path.join(dir1, entry)
170 name2 = os.path.join(dir2, entry)
171
172 if ignore(name):
173 continue
174
175 if entry in list2:
176 if os.path.islink(name1):
177 if os.path.islink(name2):
178 link1 = os.readlink(name1)
179 link2 = os.readlink(name2)
180 if link1 != link2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800181 print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
182 file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800183 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800184 print("%s: File types differ, skipping compare" % name,
185 file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800186 continue
187
188 stat1 = os.stat(name1)
189 stat2 = os.stat(name2)
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800190 type1 = stat1.st_mode & ~0o777
191 type2 = stat2.st_mode & ~0o777
Dan Willemsen99568622015-11-06 18:36:16 -0800192
193 if type1 != type2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800194 print("%s: File types differ, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800195 continue
196
197 if stat1.st_mode != stat2.st_mode:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800198 print("%s: Modes differ: %o vs %o" %
199 (name, stat1.st_mode, stat2.st_mode), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800200
201 if os.path.isdir(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800202 recursiveDiff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800203 elif os.path.isfile(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800204 diff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800205 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800206 print("%s: Unknown file type, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800207 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800208 print("%s: Only in base package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800209
210 for entry in list2:
211 name = os.path.join(prefix, entry)
212 name1 = os.path.join(dir1, entry)
213 name2 = os.path.join(dir2, entry)
214
215 if ignore(name):
216 continue
217
218 if entry not in list1:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800219 print("%s: Only in new package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800220
221def main():
222 parser = argparse.ArgumentParser()
223 parser.add_argument('dir1', help='The base target files package (extracted)')
224 parser.add_argument('dir2', help='The new target files package (extracted)')
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800225 parser.add_argument('--output',
226 help='The output file, otherwise it prints to stdout')
Dan Willemsen99568622015-11-06 18:36:16 -0800227 args = parser.parse_args()
228
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800229 if args.output:
230 out_file = open(args.output, 'w')
231 else:
232 out_file = sys.stdout
233
234 recursiveDiff('', args.dir1, args.dir2, out_file)
235
236 if args.output:
237 out_file.close()
Dan Willemsen99568622015-11-06 18:36:16 -0800238
239if __name__ == '__main__':
240 main()