Chromium Code Reviews| 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 """Adaptor script called through build/isolate.gypi. | 6 """Adaptor script called through build/isolate.gypi. |
| 7 | 7 |
| 8 Creates a wrapping .isolate which 'includes' the original one, that can be | 8 Creates a wrapping .isolate which 'includes' the original one, that can be |
| 9 consumed by tools/swarming_client/isolate.py. Path variables are determined | 9 consumed by tools/swarming_client/isolate.py. Path variables are determined |
| 10 based on the current working directory. The relative_cwd in the .isolated file | 10 based on the current working directory. The relative_cwd in the .isolated file |
| 11 is determined based on *the .isolate file that declare the 'command' variable to | 11 is determined based on the .isolate file that declare the 'command' variable to |
| 12 be used* so the wrapping .isolate doesn't affect this value. | 12 be used so the wrapping .isolate doesn't affect this value. |
| 13 | 13 |
| 14 It packages all the dynamic libraries found in this wrapping .isolate. This is | 14 This script loads build.ninja and processes it to determine all the executables |
| 15 inefficient and non-deterministic. In the very near future, it will parse | 15 referenced by the isolated target. It adds them in the wrapping .isolate file. |
| 16 build.ninja, find back the root target and find all the dynamic libraries that | |
| 17 are marked as a dependency to this target. | |
| 18 """ | 16 """ |
| 19 | 17 |
| 18 import StringIO | |
| 20 import glob | 19 import glob |
| 20 import logging | |
| 21 import os | 21 import os |
| 22 import posixpath | 22 import posixpath |
| 23 import subprocess | 23 import subprocess |
| 24 import sys | 24 import sys |
| 25 import time | 25 import time |
| 26 | 26 |
| 27 TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) | 27 TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 28 SWARMING_CLIENT_DIR = os.path.join(TOOLS_DIR, 'swarming_client') | 28 SWARMING_CLIENT_DIR = os.path.join(TOOLS_DIR, 'swarming_client') |
| 29 SRC_DIR = os.path.dirname(TOOLS_DIR) | 29 SRC_DIR = os.path.dirname(TOOLS_DIR) |
| 30 | 30 |
| 31 sys.path.insert(0, SWARMING_CLIENT_DIR) | 31 sys.path.insert(0, SWARMING_CLIENT_DIR) |
| 32 | 32 |
| 33 import isolate_format | 33 import isolate_format |
| 34 | 34 |
| 35 | 35 |
| 36 def load_ninja_recursively(build_dir, ninja_path, build_steps): | |
| 37 """Crudely extracts all the subninja and build referenced in ninja_path. | |
| 36 | 38 |
| 37 # Location to grab binaries based on the OS. | 39 In particular, it ignores rule and variable declarations. The goal is to be |
| 38 DYNAMIC_LIBRARIES = { | 40 performant (well, as much as python can be performant) which is currently in |
| 39 'darwin': '*.dylib', | 41 the <200ms range for a complete chromium tree. As such the code is layed out |
|
csharp
2014/04/08 18:05:37
layed->laid
M-A Ruel
2014/04/08 18:51:25
Done.
| |
| 40 'linux2': 'lib/*.so', | 42 for performance instead of readability. |
| 41 'win32': '*.dll', | 43 """ |
| 42 } | 44 try: |
| 45 with open(os.path.join(build_dir, ninja_path), 'rb') as f: | |
| 46 line = None | |
| 47 merge_line = '' | |
| 48 subninja = [] | |
| 49 for line in f: | |
| 50 line = line.rstrip() | |
| 51 if not line: | |
| 52 continue | |
| 53 | |
| 54 if line[-1:] == '$': | |
|
csharp
2014/04/08 18:05:37
drop :? (since line[-1:] == line[-1], right?)
M-A Ruel
2014/04/08 18:51:25
Done. While editing I hadn't done line 51-52 so th
| |
| 55 # The next line needs to be merged in. | |
| 56 merge_line += line[:-1] | |
| 57 continue | |
| 58 | |
| 59 if merge_line: | |
| 60 line = merge_line + line | |
| 61 merge_line = '' | |
| 62 | |
| 63 statement = line[:line.find(' ')] | |
| 64 if statement == 'build': | |
| 65 # Save the dependency list as a raw string. Only the lines needed will | |
|
csharp
2014/04/08 18:05:37
What do you mean" only the lines needed will be pr
M-A Ruel
2014/04/08 18:51:25
Changed to:
Save the dependency list as a raw stri
| |
| 66 # be processed. This saves a good 70ms of processing time. | |
| 67 build_target, line = line[6:].split(': ', 1) | |
|
csharp
2014/04/08 18:05:37
Can you change this to "build_target, dependencies
M-A Ruel
2014/04/08 18:51:25
Done.
| |
| 68 # Interestingly, trying to be smart and only saving the build steps | |
| 69 # with the intended extensions ('', '.stamp', '.so') slows down | |
| 70 # parsing even if 90% of the build rules can be skipped. | |
| 71 # On Windows, a single step may generate two target, so split items | |
| 72 # accordingly. It has only been seen for .exe/.exe.pdb combos. | |
| 73 for i in build_target.strip().split(): | |
| 74 build_steps[i] = line | |
| 75 elif statement == 'subninja': | |
| 76 subninja.append(line[9:]) | |
| 77 except IOError: | |
| 78 print >> sys.stderr, 'Failed to open %s' % ninja_path | |
| 79 raise | |
| 80 | |
| 81 for rel_path in subninja: | |
| 82 try: | |
| 83 # Load each of the files referenced. | |
| 84 # TODO(maruel): Skip the files known to not be needed. It's save an aweful | |
|
csharp
2014/04/08 18:05:37
"it's save" -> "It saves"
M-A Ruel
2014/04/08 18:51:25
Done.
| |
| 85 # lot of processing time. | |
| 86 load_ninja_recursively(build_dir, rel_path, build_steps) | |
| 87 except IOError: | |
| 88 print >> sys.stderr, '... as referenced by %s' % ninja_path | |
| 89 raise | |
| 90 return build_steps | |
| 43 | 91 |
| 44 | 92 |
| 45 def get_dynamic_libs(build_dir): | 93 def load_ninja(build_dir): |
| 46 """Finds all the dynamic libs to map. | 94 """Loads the tree of .ninja files in build_dir.""" |
| 95 build_steps = {} | |
| 96 load_ninja_recursively(build_dir, 'build.ninja', build_steps) | |
| 97 return build_steps | |
| 47 | 98 |
| 48 Returns: | 99 |
| 49 list of relative path, e.g. [../out/Debug/lib/libuser_prefs.so]. | 100 def using_blacklist(item): |
| 101 """Returns True if an item should be analyzed. | |
| 102 | |
| 103 Ignores many rules that are assumed to not depend on a dynamic library. If | |
| 104 the assumption doesn't hold true anymore for a file format, remove it from | |
| 105 this list. This is simply an optimization. | |
| 50 """ | 106 """ |
| 51 items = set() | 107 IGNORED = ( |
| 52 root = os.path.join(build_dir, DYNAMIC_LIBRARIES[sys.platform]) | 108 '.a', '.cc', '.css', '.h', '.html', '.js', '.json', '.manifest', '.o', |
| 53 for i in glob.iglob(root): | 109 '.obj', '.pak', '.png', '.pdb', '.strings', '.txt', |
| 54 try: | 110 ) |
| 55 # Will throw on Windows if another process is writing to this file. | 111 # ninja files use native path format. |
| 56 open(i).close() | 112 ext = os.path.splitext(item)[1] |
| 57 items.add((i, os.stat(i).st_size)) | 113 if ext in IGNORED: |
| 58 except IOError: | 114 return False |
| 59 continue | 115 # Special case Windows, keep .dll.lib but discard .lib. |
| 116 if item.endswith('.dll.lib'): | |
| 117 return True | |
| 118 if ext == '.lib': | |
| 119 return False | |
| 120 return item not in ('', '|', '||') | |
| 60 | 121 |
| 61 # The following sleep value was carefully selected via random guessing. The | |
| 62 # goal is to detect files that are being linked by checking their file size | |
| 63 # after a small delay. | |
| 64 # | |
| 65 # This happens as other build targets can be built simultaneously. For | |
| 66 # example, base_unittests.isolated is being processed but dynamic libraries | |
| 67 # for chrome are currently being linked. | |
| 68 # | |
| 69 # TODO(maruel): Obviously, this must go away and be replaced with a proper | |
| 70 # ninja parser but we need something now. http://crbug.com/333473 | |
| 71 time.sleep(10) | |
| 72 | 122 |
| 73 for item in sorted(items): | 123 def raw_build_to_deps(item): |
| 74 file_name, file_size = item | 124 """Converts a raw ninja build statement into the list of interesting |
| 75 try: | 125 dependencies. |
| 76 open(file_name).close() | 126 """ |
| 77 if os.stat(file_name).st_size != file_size: | 127 # TODO(maruel): Use a whitelist instead? .stamp, .so.TOC, .dylib.TOC, |
| 78 items.remove(item) | 128 # .dll.lib, .exe and empty. |
| 79 except IOError: | 129 # The first item is the build rule, e.g. 'link', 'cxx', 'phony', etc. |
| 80 items.remove(item) | 130 return filter(using_blacklist, item.split(' ')[1:]) |
| 81 continue | |
| 82 | 131 |
| 83 return [i[0].replace(os.path.sep, '/') for i in items] | 132 |
| 133 def recurse(target, build_steps): | |
| 134 """Recursively returns all the interesting dependencies for root_item.""" | |
| 135 out = [] | |
| 136 try: | |
| 137 dependencies = raw_build_to_deps(build_steps[target]) | |
| 138 except KeyError: | |
| 139 logging.warning('MIA: %s', target) | |
|
csharp
2014/04/08 18:05:37
Why use MIA here, I'm not sure I understand how th
M-A Ruel
2014/04/08 18:51:25
It used to happen in the code as I was making prog
| |
| 140 return out | |
| 141 logging.debug('recurse(%s) -> %s', target, dependencies) | |
| 142 for dependency in dependencies: | |
| 143 out.append(dependency) | |
| 144 dependency_raw_dependencies = build_steps.get(dependency) | |
| 145 if dependency_raw_dependencies: | |
| 146 for i in raw_build_to_deps(dependency_raw_dependencies): | |
| 147 out.extend(recurse(i, build_steps)) | |
| 148 else: | |
| 149 logging.info('MIA: %s', dependency) | |
| 150 return out | |
| 151 | |
| 152 | |
| 153 def post_process_deps(dependencies): | |
| 154 """Processes the dependency list with OS specific rules.""" | |
| 155 def filter_item(i): | |
| 156 if i.endswith('.so.TOC'): | |
| 157 # Remove only the suffix .TOC, not the .so! | |
| 158 return i[:-4] | |
| 159 if i.endswith('.dylib.TOC'): | |
| 160 # Remove only the suffix .TOC, not the .dylib! | |
| 161 return i[:-4] | |
| 162 if i.endswith('.dll.lib'): | |
| 163 # Remove only the suffix .lib, not the .dll! | |
| 164 return i[:-4] | |
| 165 return i | |
| 166 | |
| 167 return map(filter_item, dependencies) | |
| 84 | 168 |
| 85 | 169 |
| 86 def create_wrapper(args, isolate_index, isolated_index): | 170 def create_wrapper(args, isolate_index, isolated_index): |
| 87 """Creates a wrapper .isolate that add dynamic libs. | 171 """Creates a wrapper .isolate that add dynamic libs. |
| 88 | 172 |
| 89 The original .isolate is not modified. | 173 The original .isolate is not modified. |
| 90 """ | 174 """ |
| 91 cwd = os.getcwd() | 175 cwd = os.getcwd() |
| 92 isolate = args[isolate_index] | 176 isolate = args[isolate_index] |
| 93 # The code assumes the .isolate file is always specified path-less in cwd. Fix | 177 # The code assumes the .isolate file is always specified path-less in cwd. Fix |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 104 | 188 |
| 105 # The wrapping .isolate. This will look like | 189 # The wrapping .isolate. This will look like |
| 106 # ../out/Debug/gen/chrome/unit_tests.isolate. | 190 # ../out/Debug/gen/chrome/unit_tests.isolate. |
| 107 temp_isolate = os.path.join(build_dir, 'gen', src_isolate) | 191 temp_isolate = os.path.join(build_dir, 'gen', src_isolate) |
| 108 temp_isolate_dir = os.path.dirname(temp_isolate) | 192 temp_isolate_dir = os.path.dirname(temp_isolate) |
| 109 | 193 |
| 110 # Relative path between the new and old .isolate file. | 194 # Relative path between the new and old .isolate file. |
| 111 isolate_relpath = os.path.relpath( | 195 isolate_relpath = os.path.relpath( |
| 112 '.', temp_isolate_dir).replace(os.path.sep, '/') | 196 '.', temp_isolate_dir).replace(os.path.sep, '/') |
| 113 | 197 |
| 114 # Will look like ['<(PRODUCT_DIR)/lib/flibuser_prefs.so']. | 198 # It's a big assumption here that the name of the isolate file matches the |
|
csharp
2014/04/08 18:05:37
Can we assert this somewhere in the code?
M-A Ruel
2014/04/08 18:51:25
It's really an assumption, there's no way to asser
| |
| 115 rebased_libs = [ | 199 # primary target. Fix accordingly if this doesn't hold true. |
| 116 '<(PRODUCT_DIR)/%s' % i[len(build_dir)+1:] | 200 target = isolate[:-len('.isolate')] |
| 117 for i in get_dynamic_libs(build_dir) | 201 build_steps = load_ninja(build_dir) |
| 118 ] | 202 binary_deps = post_process_deps(recurse(target, build_steps)) |
| 203 logging.debug( | |
| 204 'Binary dependencies:%s', ''.join('\n ' + i for i in binary_deps)) | |
| 119 | 205 |
| 120 # Now do actual wrapping .isolate. | 206 # Now do actual wrapping .isolate. |
| 121 out = { | 207 isolate_dict = { |
| 122 'includes': [ | 208 'includes': [ |
| 123 posixpath.join(isolate_relpath, isolate), | 209 posixpath.join(isolate_relpath, isolate), |
| 124 ], | 210 ], |
| 125 'variables': { | 211 'variables': { |
| 126 isolate_format.KEY_TRACKED: rebased_libs, | 212 # Will look like ['<(PRODUCT_DIR)/lib/flibuser_prefs.so']. |
| 213 isolate_format.KEY_TRACKED: sorted( | |
| 214 '<(PRODUCT_DIR)/%s' % i for i in binary_deps), | |
| 127 }, | 215 }, |
| 128 } | 216 } |
| 129 if not os.path.isdir(temp_isolate_dir): | 217 if not os.path.isdir(temp_isolate_dir): |
| 130 os.makedirs(temp_isolate_dir) | 218 os.makedirs(temp_isolate_dir) |
| 131 comment = ( | 219 comment = ( |
| 132 '# Warning: this file was AUTOGENERATED.\n' | 220 '# Warning: this file was AUTOGENERATED.\n' |
| 133 '# DO NO EDIT.\n') | 221 '# DO NO EDIT.\n') |
| 222 out = StringIO.StringIO() | |
| 223 isolate_format.print_all(comment, isolate_dict, out) | |
|
csharp
2014/04/08 18:05:37
why don't we write directly to the file anymore?
M-A Ruel
2014/04/08 18:51:25
So it's content can be logged at line 228, which i
| |
| 224 isolate_content = out.getvalue() | |
| 134 with open(temp_isolate, 'wb') as f: | 225 with open(temp_isolate, 'wb') as f: |
| 135 isolate_format.print_all(comment, out, f) | 226 f.write(isolate_content) |
| 136 if '--verbose' in args: | 227 logging.info('Added %d dynamic libs', len(binary_deps)) |
| 137 print('Added %d dynamic libs' % len(dynamic_libs)) | 228 logging.debug('%s', isolate_content) |
| 138 args[isolate_index] = temp_isolate | 229 args[isolate_index] = temp_isolate |
| 139 | 230 |
| 140 | 231 |
| 141 def main(): | 232 def main(): |
| 233 logging.basicConfig(level=logging.ERROR) | |
| 142 args = sys.argv[1:] | 234 args = sys.argv[1:] |
| 143 isolate = None | 235 isolate = None |
| 144 isolated = None | 236 isolated = None |
| 145 is_component = False | 237 is_component = False |
| 146 for i, arg in enumerate(args): | 238 for i, arg in enumerate(args): |
| 147 if arg == '--isolate': | 239 if arg == '--isolate': |
| 148 isolate = i + 1 | 240 isolate = i + 1 |
| 149 if arg == '--isolated': | 241 if arg == '--isolated': |
| 150 isolated = i + 1 | 242 isolated = i + 1 |
| 151 if arg == 'component=shared_library': | 243 if arg == 'component=shared_library': |
| 152 is_component = True | 244 is_component = True |
| 153 if isolate is None or isolated is None: | 245 if isolate is None or isolated is None: |
| 154 print >> sys.stderr, 'Internal failure' | 246 print >> sys.stderr, 'Internal failure' |
| 155 return 1 | 247 return 1 |
| 156 | 248 |
| 157 # Implement a ninja parser. | 249 if is_component: |
| 158 # http://crbug.com/360223 | |
| 159 if is_component and False: | |
| 160 create_wrapper(args, isolate, isolated) | 250 create_wrapper(args, isolate, isolated) |
| 161 | 251 |
| 162 swarming_client = os.path.join(SRC_DIR, 'tools', 'swarming_client') | 252 swarming_client = os.path.join(SRC_DIR, 'tools', 'swarming_client') |
| 163 sys.stdout.flush() | 253 sys.stdout.flush() |
| 164 result = subprocess.call( | 254 result = subprocess.call( |
| 165 [sys.executable, os.path.join(swarming_client, 'isolate.py')] + args) | 255 [sys.executable, os.path.join(swarming_client, 'isolate.py')] + args) |
| 166 return result | 256 return result |
| 167 | 257 |
| 168 | 258 |
| 169 if __name__ == '__main__': | 259 if __name__ == '__main__': |
| 170 sys.exit(main()) | 260 sys.exit(main()) |
| OLD | NEW |