| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Compare the artifacts from two builds.""" | 6 """Compare the artifacts from two builds.""" |
| 7 | 7 |
| 8 import difflib | 8 import difflib |
| 9 import json | 9 import json |
| 10 import optparse | 10 import optparse |
| 11 import os | 11 import os |
| 12 import struct | 12 import struct |
| 13 import sys | 13 import sys |
| 14 import time | 14 import time |
| 15 | 15 |
| 16 | 16 |
| 17 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 17 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 18 | 18 |
| 19 | 19 |
| 20 def get_files_to_compare(build_dir): | 20 def get_files_to_compare(build_dir, recursive=False): |
| 21 """Get the list of files to compare.""" | 21 """Get the list of files to compare.""" |
| 22 allowed = frozenset( | 22 allowed = frozenset( |
| 23 ('', '.apk', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so')) | 23 ('', '.apk', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so')) |
| 24 | |
| 25 def check(f): | 24 def check(f): |
| 26 if not os.path.isfile(f): | 25 if not os.path.isfile(f): |
| 27 return False | 26 return False |
| 28 if os.path.basename(f).startswith('.'): | 27 if os.path.basename(f).startswith('.'): |
| 29 return False | 28 return False |
| 30 ext = os.path.splitext(f)[1] | 29 ext = os.path.splitext(f)[1] |
| 31 if ext == '.isolated': | 30 if ext == '.isolated': |
| 32 return True | 31 return True |
| 33 return ext in allowed and os.access(f, os.X_OK) | 32 return ext in allowed and os.access(f, os.X_OK) |
| 34 | 33 |
| 35 return set(f for f in os.listdir(build_dir) if | 34 ret_files = set() |
| 36 check(os.path.join(build_dir, f))) | 35 for root, dirs, files in os.walk(build_dir): |
| 36 if not recursive: |
| 37 dirs[:] = [d for d in dirs if d.endswith('_apk')] |
| 38 for f in (f for f in files if check(os.path.join(root, f))): |
| 39 ret_files.add(os.path.relpath(os.path.join(root, f), build_dir)) |
| 40 return ret_files |
| 37 | 41 |
| 38 | 42 |
| 39 def diff_dict(a, b): | 43 def diff_dict(a, b): |
| 40 """Returns a yaml-like textural diff of two dict. | 44 """Returns a yaml-like textural diff of two dict. |
| 41 | 45 |
| 42 It is currently optimized for the .isolated format. | 46 It is currently optimized for the .isolated format. |
| 43 """ | 47 """ |
| 44 out = '' | 48 out = '' |
| 45 for key in set(a) | set(b): | 49 for key in set(a) | set(b): |
| 46 va = a.get(key) | 50 va = a.get(key) |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 112 # else, falls through binary comparison, it must be binary equal too. | 116 # else, falls through binary comparison, it must be binary equal too. |
| 113 | 117 |
| 114 file_len = os.stat(first_filepath).st_size | 118 file_len = os.stat(first_filepath).st_size |
| 115 if file_len != os.stat(second_filepath).st_size: | 119 if file_len != os.stat(second_filepath).st_size: |
| 116 return 'different size: %d != %d' % ( | 120 return 'different size: %d != %d' % ( |
| 117 file_len, os.stat(second_filepath).st_size) | 121 file_len, os.stat(second_filepath).st_size) |
| 118 | 122 |
| 119 return diff_binary(first_filepath, second_filepath, file_len) | 123 return diff_binary(first_filepath, second_filepath, file_len) |
| 120 | 124 |
| 121 | 125 |
| 122 def compare_build_artifacts(first_dir, second_dir): | 126 def compare_build_artifacts(first_dir, second_dir, recursive=False): |
| 123 """Compare the artifacts from two distinct builds.""" | 127 """Compare the artifacts from two distinct builds.""" |
| 124 if not os.path.isdir(first_dir): | 128 if not os.path.isdir(first_dir): |
| 125 print >> sys.stderr, '%s isn\'t a valid directory.' % first_dir | 129 print >> sys.stderr, '%s isn\'t a valid directory.' % first_dir |
| 126 return 1 | 130 return 1 |
| 127 if not os.path.isdir(second_dir): | 131 if not os.path.isdir(second_dir): |
| 128 print >> sys.stderr, '%s isn\'t a valid directory.' % second_dir | 132 print >> sys.stderr, '%s isn\'t a valid directory.' % second_dir |
| 129 return 1 | 133 return 1 |
| 130 | 134 |
| 131 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: | 135 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: |
| 132 blacklist = frozenset(json.load(f)) | 136 blacklist = frozenset(json.load(f)) |
| 133 | 137 |
| 134 res = 0 | 138 res = 0 |
| 135 first_list = get_files_to_compare(first_dir) - blacklist | 139 first_list = get_files_to_compare(first_dir, recursive) - blacklist |
| 136 second_list = get_files_to_compare(second_dir) - blacklist | 140 second_list = get_files_to_compare(second_dir, recursive) - blacklist |
| 137 | 141 |
| 138 diff = first_list.symmetric_difference(second_list) | 142 diff = first_list.symmetric_difference(second_list) |
| 139 if diff: | 143 if diff: |
| 140 print >> sys.stderr, 'Different list of files in both directories' | 144 print >> sys.stderr, 'Different list of files in both directories' |
| 141 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(diff)) | 145 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(diff)) |
| 142 res += len(diff) | 146 res += len(diff) |
| 143 | 147 |
| 144 epoch_hex = struct.pack('<I', int(time.time())).encode('hex') | 148 epoch_hex = struct.pack('<I', int(time.time())).encode('hex') |
| 145 print('Epoch: %s' % | 149 print('Epoch: %s' % |
| 146 ' '.join(epoch_hex[i:i+2] for i in xrange(0, len(epoch_hex), 2))) | 150 ' '.join(epoch_hex[i:i+2] for i in xrange(0, len(epoch_hex), 2))) |
| (...skipping 14 matching lines...) Expand all Loading... |
| 161 | 165 |
| 162 return 0 if res == 0 else 1 | 166 return 0 if res == 0 else 1 |
| 163 | 167 |
| 164 | 168 |
| 165 def main(): | 169 def main(): |
| 166 parser = optparse.OptionParser(usage='%prog [options]') | 170 parser = optparse.OptionParser(usage='%prog [options]') |
| 167 parser.add_option( | 171 parser.add_option( |
| 168 '-f', '--first-build-dir', help='The first build directory.') | 172 '-f', '--first-build-dir', help='The first build directory.') |
| 169 parser.add_option( | 173 parser.add_option( |
| 170 '-s', '--second-build-dir', help='The second build directory.') | 174 '-s', '--second-build-dir', help='The second build directory.') |
| 175 parser.add_option('-r', '--recursive', action='store_true', default=False, |
| 176 help='Indicates if the comparison should be recursive.') |
| 171 options, _ = parser.parse_args() | 177 options, _ = parser.parse_args() |
| 172 | 178 |
| 173 if not options.first_build_dir: | 179 if not options.first_build_dir: |
| 174 parser.error('--first-build-dir is required') | 180 parser.error('--first-build-dir is required') |
| 175 if not options.second_build_dir: | 181 if not options.second_build_dir: |
| 176 parser.error('--second-build-dir is required') | 182 parser.error('--second-build-dir is required') |
| 177 | 183 |
| 178 return compare_build_artifacts(options.first_build_dir, | 184 return compare_build_artifacts(os.path.abspath(options.first_build_dir), |
| 179 options.second_build_dir) | 185 os.path.abspath(options.second_build_dir), |
| 186 options.recursive) |
| 180 | 187 |
| 181 | 188 |
| 182 if __name__ == '__main__': | 189 if __name__ == '__main__': |
| 183 sys.exit(main()) | 190 sys.exit(main()) |
| OLD | NEW |