blob: 0f717e0b9866ca1479e3ede1d5206b86a1bb6d84 [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 Willemsenc4438d32016-02-03 10:46:39 -080054 # b/26956807 .odex files are not deterministic
55 if name.endswith('.odex'):
56 return True
57
Dan Willemsen99568622015-11-06 18:36:16 -080058 return False
59
60
61def rewrite_build_property(original, new):
62 """
63 Rewrite property files to remove values known to change for every build
64 """
65
66 skipped = ['ro.bootimage.build.date=',
67 'ro.bootimage.build.date.utc=',
68 'ro.bootimage.build.fingerprint=',
69 'ro.build.id=',
70 'ro.build.display.id=',
71 'ro.build.version.incremental=',
72 'ro.build.date=',
73 'ro.build.date.utc=',
74 'ro.build.host=',
Dan Willemsen734d78c2016-02-01 13:38:25 -080075 'ro.build.user=',
Dan Willemsen99568622015-11-06 18:36:16 -080076 'ro.build.description=',
77 'ro.build.fingerprint=',
78 'ro.expect.recovery_id=',
79 'ro.vendor.build.date=',
80 'ro.vendor.build.date.utc=',
81 'ro.vendor.build.fingerprint=']
82
83 for line in original:
84 skip = False
85 for s in skipped:
86 if line.startswith(s):
87 skip = True
88 break
89 if not skip:
90 new.write(line)
91
92
93def trim_install_recovery(original, new):
94 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -080095 Rewrite the install-recovery script to remove the hash of the recovery
96 partition.
Dan Willemsen99568622015-11-06 18:36:16 -080097 """
98 for line in original:
99 new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
100
101def sort_file(original, new):
102 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800103 Sort the file. Some OTA metadata files are not in a deterministic order
104 currently.
Dan Willemsen99568622015-11-06 18:36:16 -0800105 """
106 lines = original.readlines()
107 lines.sort()
108 for line in lines:
109 new.write(line)
110
111# Map files to the functions that will modify them for diffing
112REWRITE_RULES = {
113 'BOOT/RAMDISK/default.prop': rewrite_build_property,
114 'RECOVERY/RAMDISK/default.prop': rewrite_build_property,
115 'SYSTEM/build.prop': rewrite_build_property,
116 'VENDOR/build.prop': rewrite_build_property,
117
118 'SYSTEM/bin/install-recovery.sh': trim_install_recovery,
119
120 'META/boot_filesystem_config.txt': sort_file,
121 'META/filesystem_config.txt': sort_file,
122 'META/recovery_filesystem_config.txt': sort_file,
123 'META/vendor_filesystem_config.txt': sort_file,
124}
125
126@contextlib.contextmanager
127def preprocess(name, filename):
128 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800129 Optionally rewrite files before diffing them, to remove known-variable
130 information.
Dan Willemsen99568622015-11-06 18:36:16 -0800131 """
132 if name in REWRITE_RULES:
133 with tempfile.NamedTemporaryFile() as newfp:
134 with open(filename, 'r') as oldfp:
135 REWRITE_RULES[name](oldfp, newfp)
136 newfp.flush()
137 yield newfp.name
138 else:
139 yield filename
140
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800141def diff(name, file1, file2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800142 """
143 Diff a file pair with diff, running preprocess() on the arguments first.
144 """
145 with preprocess(name, file1) as f1:
146 with preprocess(name, file2) as f2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800147 proc = subprocess.Popen(['diff', f1, f2], stdout=subprocess.PIPE,
148 stderr=subprocess.STDOUT)
149 (stdout, _) = proc.communicate()
Dan Willemsen99568622015-11-06 18:36:16 -0800150 if proc.returncode == 0:
151 return
152 stdout = stdout.strip()
153 if stdout == 'Binary files %s and %s differ' % (f1, f2):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800154 print("%s: Binary files differ" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800155 else:
156 for line in stdout.strip().split('\n'):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800157 print("%s: %s" % (name, line), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800158
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800159def recursiveDiff(prefix, dir1, dir2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800160 """
161 Recursively diff two directories, checking metadata then calling diff()
162 """
163 list1 = sorted(os.listdir(dir1))
164 list2 = sorted(os.listdir(dir2))
165
166 for entry in list1:
167 name = os.path.join(prefix, entry)
168 name1 = os.path.join(dir1, entry)
169 name2 = os.path.join(dir2, entry)
170
171 if ignore(name):
172 continue
173
174 if entry in list2:
Tao Baof31a6de2016-04-25 10:03:38 -0700175 if os.path.islink(name1) and os.path.islink(name2):
176 link1 = os.readlink(name1)
177 link2 = os.readlink(name2)
178 if link1 != link2:
179 print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800180 file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800181 continue
Tao Baof31a6de2016-04-25 10:03:38 -0700182 elif os.path.islink(name1) or os.path.islink(name2):
183 print("%s: File types differ, skipping compare" % name, file=out_file)
184 continue
Dan Willemsen99568622015-11-06 18:36:16 -0800185
186 stat1 = os.stat(name1)
187 stat2 = os.stat(name2)
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800188 type1 = stat1.st_mode & ~0o777
189 type2 = stat2.st_mode & ~0o777
Dan Willemsen99568622015-11-06 18:36:16 -0800190
191 if type1 != type2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800192 print("%s: File types differ, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800193 continue
194
195 if stat1.st_mode != stat2.st_mode:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800196 print("%s: Modes differ: %o vs %o" %
197 (name, stat1.st_mode, stat2.st_mode), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800198
199 if os.path.isdir(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800200 recursiveDiff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800201 elif os.path.isfile(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800202 diff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800203 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800204 print("%s: Unknown file type, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800205 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800206 print("%s: Only in base package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800207
208 for entry in list2:
209 name = os.path.join(prefix, entry)
210 name1 = os.path.join(dir1, entry)
211 name2 = os.path.join(dir2, entry)
212
213 if ignore(name):
214 continue
215
216 if entry not in list1:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800217 print("%s: Only in new package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800218
219def main():
220 parser = argparse.ArgumentParser()
221 parser.add_argument('dir1', help='The base target files package (extracted)')
222 parser.add_argument('dir2', help='The new target files package (extracted)')
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800223 parser.add_argument('--output',
224 help='The output file, otherwise it prints to stdout')
Dan Willemsen99568622015-11-06 18:36:16 -0800225 args = parser.parse_args()
226
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800227 if args.output:
228 out_file = open(args.output, 'w')
229 else:
230 out_file = sys.stdout
231
232 recursiveDiff('', args.dir1, args.dir2, out_file)
233
234 if args.output:
235 out_file.close()
Dan Willemsen99568622015-11-06 18:36:16 -0800236
237if __name__ == '__main__':
238 main()