blob: 4402c8d20ee2b25f0d4844115cdd0237cdcec00b [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 return False
55
56
57def rewrite_build_property(original, new):
58 """
59 Rewrite property files to remove values known to change for every build
60 """
61
62 skipped = ['ro.bootimage.build.date=',
63 'ro.bootimage.build.date.utc=',
64 'ro.bootimage.build.fingerprint=',
65 'ro.build.id=',
66 'ro.build.display.id=',
67 'ro.build.version.incremental=',
68 'ro.build.date=',
69 'ro.build.date.utc=',
70 'ro.build.host=',
Dan Willemsen734d78c2016-02-01 13:38:25 -080071 'ro.build.user=',
Dan Willemsen99568622015-11-06 18:36:16 -080072 'ro.build.description=',
73 'ro.build.fingerprint=',
Dan Willemsen99568622015-11-06 18:36:16 -080074 'ro.vendor.build.date=',
75 'ro.vendor.build.date.utc=',
76 'ro.vendor.build.fingerprint=']
77
78 for line in original:
79 skip = False
80 for s in skipped:
81 if line.startswith(s):
82 skip = True
83 break
84 if not skip:
85 new.write(line)
86
87
88def trim_install_recovery(original, new):
89 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -080090 Rewrite the install-recovery script to remove the hash of the recovery
91 partition.
Dan Willemsen99568622015-11-06 18:36:16 -080092 """
93 for line in original:
94 new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
95
96def sort_file(original, new):
97 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -080098 Sort the file. Some OTA metadata files are not in a deterministic order
99 currently.
Dan Willemsen99568622015-11-06 18:36:16 -0800100 """
101 lines = original.readlines()
102 lines.sort()
103 for line in lines:
104 new.write(line)
105
106# Map files to the functions that will modify them for diffing
107REWRITE_RULES = {
108 'BOOT/RAMDISK/default.prop': rewrite_build_property,
109 'RECOVERY/RAMDISK/default.prop': rewrite_build_property,
110 'SYSTEM/build.prop': rewrite_build_property,
111 'VENDOR/build.prop': rewrite_build_property,
112
113 'SYSTEM/bin/install-recovery.sh': trim_install_recovery,
114
115 'META/boot_filesystem_config.txt': sort_file,
116 'META/filesystem_config.txt': sort_file,
117 'META/recovery_filesystem_config.txt': sort_file,
118 'META/vendor_filesystem_config.txt': sort_file,
119}
120
121@contextlib.contextmanager
122def preprocess(name, filename):
123 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800124 Optionally rewrite files before diffing them, to remove known-variable
125 information.
Dan Willemsen99568622015-11-06 18:36:16 -0800126 """
127 if name in REWRITE_RULES:
128 with tempfile.NamedTemporaryFile() as newfp:
129 with open(filename, 'r') as oldfp:
130 REWRITE_RULES[name](oldfp, newfp)
131 newfp.flush()
132 yield newfp.name
133 else:
134 yield filename
135
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800136def diff(name, file1, file2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800137 """
138 Diff a file pair with diff, running preprocess() on the arguments first.
139 """
140 with preprocess(name, file1) as f1:
141 with preprocess(name, file2) as f2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800142 proc = subprocess.Popen(['diff', f1, f2], stdout=subprocess.PIPE,
143 stderr=subprocess.STDOUT)
144 (stdout, _) = proc.communicate()
Dan Willemsen99568622015-11-06 18:36:16 -0800145 if proc.returncode == 0:
146 return
147 stdout = stdout.strip()
148 if stdout == 'Binary files %s and %s differ' % (f1, f2):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800149 print("%s: Binary files differ" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800150 else:
151 for line in stdout.strip().split('\n'):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800152 print("%s: %s" % (name, line), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800153
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800154def recursiveDiff(prefix, dir1, dir2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800155 """
156 Recursively diff two directories, checking metadata then calling diff()
157 """
158 list1 = sorted(os.listdir(dir1))
159 list2 = sorted(os.listdir(dir2))
160
161 for entry in list1:
162 name = os.path.join(prefix, entry)
163 name1 = os.path.join(dir1, entry)
164 name2 = os.path.join(dir2, entry)
165
166 if ignore(name):
167 continue
168
169 if entry in list2:
Tao Baof31a6de2016-04-25 10:03:38 -0700170 if os.path.islink(name1) and os.path.islink(name2):
171 link1 = os.readlink(name1)
172 link2 = os.readlink(name2)
173 if link1 != link2:
174 print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800175 file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800176 continue
Tao Baof31a6de2016-04-25 10:03:38 -0700177 elif os.path.islink(name1) or os.path.islink(name2):
178 print("%s: File types differ, skipping compare" % name, file=out_file)
179 continue
Dan Willemsen99568622015-11-06 18:36:16 -0800180
181 stat1 = os.stat(name1)
182 stat2 = os.stat(name2)
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800183 type1 = stat1.st_mode & ~0o777
184 type2 = stat2.st_mode & ~0o777
Dan Willemsen99568622015-11-06 18:36:16 -0800185
186 if type1 != type2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800187 print("%s: File types differ, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800188 continue
189
190 if stat1.st_mode != stat2.st_mode:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800191 print("%s: Modes differ: %o vs %o" %
192 (name, stat1.st_mode, stat2.st_mode), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800193
194 if os.path.isdir(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800195 recursiveDiff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800196 elif os.path.isfile(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800197 diff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800198 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800199 print("%s: Unknown file type, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800200 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800201 print("%s: Only in base package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800202
203 for entry in list2:
204 name = os.path.join(prefix, entry)
205 name1 = os.path.join(dir1, entry)
206 name2 = os.path.join(dir2, entry)
207
208 if ignore(name):
209 continue
210
211 if entry not in list1:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800212 print("%s: Only in new package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800213
214def main():
215 parser = argparse.ArgumentParser()
216 parser.add_argument('dir1', help='The base target files package (extracted)')
217 parser.add_argument('dir2', help='The new target files package (extracted)')
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800218 parser.add_argument('--output',
219 help='The output file, otherwise it prints to stdout')
Dan Willemsen99568622015-11-06 18:36:16 -0800220 args = parser.parse_args()
221
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800222 if args.output:
223 out_file = open(args.output, 'w')
224 else:
225 out_file = sys.stdout
226
227 recursiveDiff('', args.dir1, args.dir2, out_file)
228
229 if args.output:
230 out_file.close()
Dan Willemsen99568622015-11-06 18:36:16 -0800231
232if __name__ == '__main__':
233 main()