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 """Remove the build metadata embedded in the artifacts of a build.""" | 5 """Remove the build metadata embedded in the artifacts of a build.""" |
6 | 6 |
7 import json | 7 import json |
8 import optparse | 8 import optparse |
9 import os | 9 import os |
| 10 import shutil |
10 import subprocess | 11 import subprocess |
11 import sys | 12 import sys |
| 13 import tempfile |
| 14 import zipfile |
12 | 15 |
13 | 16 |
14 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 17 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
15 | 18 |
16 | 19 |
17 def RunZapTimestamp(src_dir, filepath): | 20 def get_files_to_clean(build_dir, recursive=False): |
| 21 """Get the list of files to clean.""" |
| 22 allowed = frozenset( |
| 23 ('', '.apk', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so')) |
| 24 non_x_ok_exts = frozenset(('.apk', '.isolated')) |
| 25 def check(f): |
| 26 if not os.path.isfile(f) or os.path.basename(f).startswith('.'): |
| 27 return False |
| 28 ext = os.path.splitext(f)[1] |
| 29 return (ext in non_x_ok_exts) or (ext in allowed and os.access(f, os.X_OK)) |
| 30 |
| 31 ret_files = set() |
| 32 for root, dirs, files in os.walk(build_dir): |
| 33 if not recursive: |
| 34 dirs[:] = [d for d in dirs if d.endswith('_apk')] |
| 35 for f in (f for f in files if check(os.path.join(root, f))): |
| 36 ret_files.add(os.path.relpath(os.path.join(root, f), build_dir)) |
| 37 return ret_files |
| 38 |
| 39 |
| 40 def run_zap_timestamp(src_dir, filepath): |
| 41 """Run zap_timestamp.exe on a PE binary.""" |
| 42 assert sys.platform == 'win32' |
18 syzygy_dir = os.path.join( | 43 syzygy_dir = os.path.join( |
19 src_dir, 'third_party', 'syzygy', 'binaries', 'exe') | 44 src_dir, 'third_party', 'syzygy', 'binaries', 'exe') |
20 zap_timestamp_exe = os.path.join(syzygy_dir, 'zap_timestamp.exe') | 45 zap_timestamp_exe = os.path.join(syzygy_dir, 'zap_timestamp.exe') |
21 print('Processing: %s' % os.path.basename(filepath)) | 46 print('Processing: %s' % os.path.basename(filepath)) |
22 proc = subprocess.Popen( | 47 proc = subprocess.Popen( |
23 [zap_timestamp_exe, '--input-image=%s' % filepath, '--overwrite'], | 48 [zap_timestamp_exe, '--input-image=%s' % filepath, '--overwrite'], |
24 stdout=subprocess.PIPE, stderr=subprocess.STDOUT) | 49 stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
25 log, _ = proc.communicate() | 50 log, _ = proc.communicate() |
26 if proc.returncode != 0: | 51 if proc.returncode != 0: |
27 print >> sys.stderr, log | 52 print >> sys.stderr, log |
28 return proc.returncode | 53 return proc.returncode |
29 | 54 |
30 | 55 |
31 def RemovePEMetadata(build_dir, src_dir): | 56 def remove_pe_metadata(filename, src_dir): |
32 """Remove the build metadata from a PE file.""" | 57 """Remove the build metadata from a PE file.""" |
33 files = (i for i in os.listdir(build_dir) if i.endswith(('.dll', '.exe'))) | 58 # Only run zap_timestamp on the PE files for which we have a PDB. |
| 59 ret = 0 |
| 60 if os.path.exists(filename + '.pdb'): |
| 61 ret = run_zap_timestamp(src_dir, filename) |
| 62 return ret |
34 | 63 |
| 64 |
| 65 def remove_apk_timestamps(filename): |
| 66 """Remove the timestamps embedded in an apk archive.""" |
| 67 print('Processing: %s' % os.path.basename(filename)) |
| 68 with zipfile.ZipFile(filename, 'r') as zf: |
| 69 # Creates a temporary file. |
| 70 out_file, out_filename = tempfile.mkstemp(prefix='remote_apk_timestamp') |
| 71 os.close(out_file) |
| 72 try: |
| 73 with zipfile.ZipFile(out_filename, 'w') as zf_o: |
| 74 # Copy the data from the original file to the new one. |
| 75 for info in zf.infolist(): |
| 76 # Overwrite the timestamp with a constant value. |
| 77 info.date_time = (1980, 1, 1, 0, 0, 0) |
| 78 zf_o.writestr(info, zf.read(info.filename)) |
| 79 # Remove the original file and replace it by the modified one. |
| 80 os.remove(filename) |
| 81 shutil.move(out_filename, filename) |
| 82 finally: |
| 83 if os.path.isfile(out_filename): |
| 84 os.remove(out_filename) |
| 85 |
| 86 |
| 87 def remove_metadata(build_dir, src_dir, recursive): |
| 88 """Remove the build metadata from the artifacts of a build.""" |
35 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: | 89 with open(os.path.join(BASE_DIR, 'deterministic_build_blacklist.json')) as f: |
36 blacklist = frozenset(json.load(f)) | 90 blacklist = frozenset(json.load(f)) |
| 91 files = get_files_to_clean(build_dir, recursive) - blacklist |
| 92 failed_files = [] |
| 93 ret = 0 |
| 94 for f in files: |
| 95 if f.endswith(('.dll', '.exe')): |
| 96 if remove_pe_metadata(os.path.join(build_dir, f), src_dir): |
| 97 ret = 1 |
| 98 failed_files.append(f) |
| 99 elif f.endswith('.apk'): |
| 100 remove_apk_timestamps(os.path.join(build_dir, f)) |
37 | 101 |
38 failed = [] | 102 if failed_files: |
39 for filename in files: | 103 print >> sys.stderr, 'Failed for the following files:' |
40 # Ignore the blacklisted files. | 104 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(failed_files)) |
41 if filename in blacklist: | |
42 print('Ignored: %s' % filename) | |
43 continue | |
44 # Only run zap_timestamp on the PE files for which we have a PDB. | |
45 if os.path.exists(os.path.join(build_dir, filename + '.pdb')): | |
46 ret = RunZapTimestamp(src_dir, os.path.join(build_dir, filename)) | |
47 if ret != 0: | |
48 failed.append(filename) | |
49 | |
50 if failed: | |
51 print >> sys.stderr, 'zap_timestamp.exe failed for the following files:' | |
52 print >> sys.stderr, '\n'.join(' ' + i for i in sorted(failed)) | |
53 return 1 | 105 return 1 |
54 | 106 |
55 return 0 | 107 return ret |
56 | 108 |
57 | 109 |
58 def main(): | 110 def main(): |
59 parser = optparse.OptionParser(usage='%prog [options]') | 111 parser = optparse.OptionParser(usage='%prog [options]') |
60 # TODO(sebmarchand): Add support for reading the list of artifact from a | 112 # TODO(sebmarchand): Add support for reading the list of artifact from a |
61 # .isolated file. | 113 # .isolated file. |
62 parser.add_option('--build-dir', help='The build directory.') | 114 parser.add_option('--build-dir', help='The build directory.') |
63 parser.add_option('--src-dir', help='The source directory.') | 115 parser.add_option('--src-dir', help='The source directory.') |
| 116 parser.add_option('-r', '--recursive', action='store_true', default=False, |
| 117 help='Indicates if the script should be recursive.') |
64 options, _ = parser.parse_args() | 118 options, _ = parser.parse_args() |
65 | 119 |
66 if not options.build_dir: | 120 if not options.build_dir: |
67 parser.error('--build-dir is required') | 121 parser.error('--build-dir is required') |
68 if not options.src_dir: | 122 |
| 123 if sys.platform == 'win32' and not options.src_dir: |
69 parser.error('--src-dir is required') | 124 parser.error('--src-dir is required') |
70 | 125 |
71 # There's nothing to do for the non-Windows platform yet. | 126 return remove_metadata(options.build_dir, options.src_dir, options.recursive) |
72 if sys.platform == 'win32': | |
73 return RemovePEMetadata(options.build_dir, options.src_dir) | |
74 | 127 |
75 | 128 |
76 if __name__ == '__main__': | 129 if __name__ == '__main__': |
77 sys.exit(main()) | 130 sys.exit(main()) |
OLD | NEW |