blob: 7415f276f43b46dea6ff7ff673eee2daa141c7d3 [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:
Tao Baof31a6de2016-04-25 10:03:38 -0700171 if os.path.islink(name1) and os.path.islink(name2):
172 link1 = os.readlink(name1)
173 link2 = os.readlink(name2)
174 if link1 != link2:
175 print("%s: Symlinks differ: %s vs %s" % (name, link1, link2),
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800176 file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800177 continue
Tao Baof31a6de2016-04-25 10:03:38 -0700178 elif os.path.islink(name1) or os.path.islink(name2):
179 print("%s: File types differ, skipping compare" % name, file=out_file)
180 continue
Dan Willemsen99568622015-11-06 18:36:16 -0800181
182 stat1 = os.stat(name1)
183 stat2 = os.stat(name2)
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800184 type1 = stat1.st_mode & ~0o777
185 type2 = stat2.st_mode & ~0o777
Dan Willemsen99568622015-11-06 18:36:16 -0800186
187 if type1 != type2:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800188 print("%s: File types differ, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800189 continue
190
191 if stat1.st_mode != stat2.st_mode:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800192 print("%s: Modes differ: %o vs %o" %
193 (name, stat1.st_mode, stat2.st_mode), file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800194
195 if os.path.isdir(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800196 recursiveDiff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800197 elif os.path.isfile(name1):
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800198 diff(name, name1, name2, out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800199 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800200 print("%s: Unknown file type, skipping compare" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800201 else:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800202 print("%s: Only in base package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800203
204 for entry in list2:
205 name = os.path.join(prefix, entry)
206 name1 = os.path.join(dir1, entry)
207 name2 = os.path.join(dir2, entry)
208
209 if ignore(name):
210 continue
211
212 if entry not in list1:
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800213 print("%s: Only in new package" % name, file=out_file)
Dan Willemsen99568622015-11-06 18:36:16 -0800214
215def main():
216 parser = argparse.ArgumentParser()
217 parser.add_argument('dir1', help='The base target files package (extracted)')
218 parser.add_argument('dir2', help='The new target files package (extracted)')
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800219 parser.add_argument('--output',
220 help='The output file, otherwise it prints to stdout')
Dan Willemsen99568622015-11-06 18:36:16 -0800221 args = parser.parse_args()
222
Tao Bao5dd9a2c2015-11-24 15:17:17 -0800223 if args.output:
224 out_file = open(args.output, 'w')
225 else:
226 out_file = sys.stdout
227
228 recursiveDiff('', args.dir1, args.dir2, out_file)
229
230 if args.output:
231 out_file.close()
Dan Willemsen99568622015-11-06 18:36:16 -0800232
233if __name__ == '__main__':
234 main()