blob: 2cf90513efd4497d86aafac81f0037528a492c74 [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=',
74 'ro.expect.recovery_id=',
75 'ro.vendor.build.date=',
76 'ro.vendor.build.date.utc=',
77 'ro.vendor.build.fingerprint=']
78
79 for line in original:
80 skip = False
81 for s in skipped:
82 if line.startswith(s):
83 skip = True
84 break
85 if not skip:
86 new.write(line)
87
88
89def trim_install_recovery(original, new):
90 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -080091 Rewrite the install-recovery script to remove the hash of the recovery
92 partition.
Dan Willemsen99568622015-11-06 18:36:16 -080093 """
94 for line in original:
95 new.write(re.sub(r'[0-9a-f]{40}', '0'*40, line))
96
97def sort_file(original, new):
98 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -080099 Sort the file. Some OTA metadata files are not in a deterministic order
100 currently.
Dan Willemsen99568622015-11-06 18:36:16 -0800101 """
102 lines = original.readlines()
103 lines.sort()
104 for line in lines:
105 new.write(line)
106
107# Map files to the functions that will modify them for diffing
108REWRITE_RULES = {
109 'BOOT/RAMDISK/default.prop': rewrite_build_property,
110 'RECOVERY/RAMDISK/default.prop': rewrite_build_property,
111 'SYSTEM/build.prop': rewrite_build_property,
112 'VENDOR/build.prop': rewrite_build_property,
113
114 'SYSTEM/bin/install-recovery.sh': trim_install_recovery,
115
116 'META/boot_filesystem_config.txt': sort_file,
117 'META/filesystem_config.txt': sort_file,
118 'META/recovery_filesystem_config.txt': sort_file,
119 'META/vendor_filesystem_config.txt': sort_file,
120}
121
122@contextlib.contextmanager
123def preprocess(name, filename):
124 """
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800125 Optionally rewrite files before diffing them, to remove known-variable
126 information.
Dan Willemsen99568622015-11-06 18:36:16 -0800127 """
128 if name in REWRITE_RULES:
129 with tempfile.NamedTemporaryFile() as newfp:
130 with open(filename, 'r') as oldfp:
131 REWRITE_RULES[name](oldfp, newfp)
132 newfp.flush()
133 yield newfp.name
134 else:
135 yield filename
136
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800137def diff(name, file1, file2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800138 """
139 Diff a file pair with diff, running preprocess() on the arguments first.
140 """
141 with preprocess(name, file1) as f1:
142 with preprocess(name, file2) as f2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800143 proc = subprocess.Popen(['diff', f1, f2], stdout=subprocess.PIPE,
144 stderr=subprocess.STDOUT)
145 (stdout, _) = proc.communicate()
Dan Willemsen99568622015-11-06 18:36:16 -0800146 if proc.returncode == 0:
147 return
148 stdout = stdout.strip()
149 if stdout == 'Binary files %s and %s differ' % (f1, f2):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800150 print("%s: Binary files differ" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800151 else:
152 for line in stdout.strip().split('\n'):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800153 print("%s: %s" % (name, line), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800154
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800155def recursiveDiff(prefix, dir1, dir2, out_file):
Dan Willemsen99568622015-11-06 18:36:16 -0800156 """
157 Recursively diff two directories, checking metadata then calling diff()
158 """
159 list1 = sorted(os.listdir(dir1))
160 list2 = sorted(os.listdir(dir2))
161
162 for entry in list1:
163 name = os.path.join(prefix, entry)
164 name1 = os.path.join(dir1, entry)
165 name2 = os.path.join(dir2, entry)
166
167 if ignore(name):
168 continue
169
170 if entry in list2:
171 if os.path.islink(name1):
172 if os.path.islink(name2):
173 link1 = os.readlink(name1)
174 link2 = os.readlink(name2)
175 if link1 != link2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800176 print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
177 file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800178 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800179 print("%s: File types differ, skipping compare" % name,
180 file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800181 continue
182
183 stat1 = os.stat(name1)
184 stat2 = os.stat(name2)
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800185 type1 = stat1.st_mode & ~0o777
186 type2 = stat2.st_mode & ~0o777
Dan Willemsen99568622015-11-06 18:36:16 -0800187
188 if type1 != type2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800189 print("%s: File types differ, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800190 continue
191
192 if stat1.st_mode != stat2.st_mode:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800193 print("%s: Modes differ: %o vs %o" %
194 (name, stat1.st_mode, stat2.st_mode), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800195
196 if os.path.isdir(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800197 recursiveDiff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800198 elif os.path.isfile(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800199 diff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800200 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800201 print("%s: Unknown file type, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800202 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800203 print("%s: Only in base package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800204
205 for entry in list2:
206 name = os.path.join(prefix, entry)
207 name1 = os.path.join(dir1, entry)
208 name2 = os.path.join(dir2, entry)
209
210 if ignore(name):
211 continue
212
213 if entry not in list1:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800214 print("%s: Only in new package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800215
216def main():
217 parser = argparse.ArgumentParser()
218 parser.add_argument('dir1', help='The base target files package (extracted)')
219 parser.add_argument('dir2', help='The new target files package (extracted)')
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800220 parser.add_argument('--output',
221 help='The output file, otherwise it prints to stdout')
Dan Willemsen99568622015-11-06 18:36:16 -0800222 args = parser.parse_args()
223
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800224 if args.output:
225 out_file = open(args.output, 'w')
226 else:
227 out_file = sys.stdout
228
229 recursiveDiff('', args.dir1, args.dir2, out_file)
230
231 if args.output:
232 out_file.close()
Dan Willemsen99568622015-11-06 18:36:16 -0800233
234if __name__ == '__main__':
235 main()