Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(784)

Side by Side Diff: tools/isolate_driver.py

Issue 228463003: Make isolate_driver.py process build.ninja and extract dependencies. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Lower verbosity Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698