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

Side by Side Diff: pylib/gyp/input.py

Issue 10911082: Load target build files in parallel using Python multiprocessing. (Closed) Base URL: http://git.chromium.org/external/gyp.git@master
Patch Set: Put behind flag with env var Created 8 years, 3 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
« pylib/gyp/__init__.py ('K') | « pylib/gyp/__init__.py ('k') | 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 # Copyright (c) 2012 Google Inc. All rights reserved. 1 # Copyright (c) 2012 Google Inc. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 from compiler.ast import Const 5 from compiler.ast import Const
6 from compiler.ast import Dict 6 from compiler.ast import Dict
7 from compiler.ast import Discard 7 from compiler.ast import Discard
8 from compiler.ast import List 8 from compiler.ast import List
9 from compiler.ast import Module 9 from compiler.ast import Module
10 from compiler.ast import Node 10 from compiler.ast import Node
11 from compiler.ast import Stmt 11 from compiler.ast import Stmt
12 import compiler 12 import compiler
13 import copy 13 import copy
14 import gyp.common 14 import gyp.common
15 import multiprocessing
15 import optparse 16 import optparse
16 import os.path 17 import os.path
17 import re 18 import re
18 import shlex 19 import shlex
19 import subprocess 20 import subprocess
20 import sys 21 import sys
22 import time
21 23
22 24
23 # A list of types that are treated as linkable. 25 # A list of types that are treated as linkable.
24 linkable_types = ['executable', 'shared_library', 'loadable_module'] 26 linkable_types = ['executable', 'shared_library', 'loadable_module']
25 27
26 # A list of sections that contain links to other targets. 28 # A list of sections that contain links to other targets.
27 dependency_sections = ['dependencies', 'export_dependent_settings'] 29 dependency_sections = ['dependencies', 'export_dependent_settings']
28 30
29 # base_path_sections is a list of sections defined by GYP that contain 31 # base_path_sections is a list of sections defined by GYP that contain
30 # pathnames. The generators can provide more keys, the two lists are merged 32 # pathnames. The generators can provide more keys, the two lists are merged
(...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after
322 for condition in data['conditions']: 324 for condition in data['conditions']:
323 if isinstance(condition, list): 325 if isinstance(condition, list):
324 for condition_dict in condition[1:]: 326 for condition_dict in condition[1:]:
325 ProcessToolsetsInDict(condition_dict) 327 ProcessToolsetsInDict(condition_dict)
326 328
327 329
328 # TODO(mark): I don't love this name. It just means that it's going to load 330 # TODO(mark): I don't love this name. It just means that it's going to load
329 # a build file that contains targets and is expected to provide a targets dict 331 # a build file that contains targets and is expected to provide a targets dict
330 # that contains the targets... 332 # that contains the targets...
331 def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes, 333 def LoadTargetBuildFile(build_file_path, data, aux_data, variables, includes,
332 depth, check): 334 depth, check, load_dependencies):
333 # If depth is set, predefine the DEPTH variable to be a relative path from 335 # If depth is set, predefine the DEPTH variable to be a relative path from
334 # this build file's directory to the directory identified by depth. 336 # this build file's directory to the directory identified by depth.
335 if depth: 337 if depth:
336 # TODO(dglazkov) The backslash/forward-slash replacement at the end is a 338 # TODO(dglazkov) The backslash/forward-slash replacement at the end is a
337 # temporary measure. This should really be addressed by keeping all paths 339 # temporary measure. This should really be addressed by keeping all paths
338 # in POSIX until actual project generation. 340 # in POSIX until actual project generation.
339 d = gyp.common.RelativePath(depth, os.path.dirname(build_file_path)) 341 d = gyp.common.RelativePath(depth, os.path.dirname(build_file_path))
340 if d == '': 342 if d == '':
341 variables['DEPTH'] = '.' 343 variables['DEPTH'] = '.'
342 else: 344 else:
343 variables['DEPTH'] = d.replace('\\', '/') 345 variables['DEPTH'] = d.replace('\\', '/')
344 346
345 # If the generator needs absolue paths, then do so. 347 # If the generator needs absolue paths, then do so.
346 if absolute_build_file_paths: 348 if absolute_build_file_paths:
347 build_file_path = os.path.abspath(build_file_path) 349 build_file_path = os.path.abspath(build_file_path)
348 350
349 if build_file_path in data['target_build_files']: 351 if build_file_path in data['target_build_files']:
350 # Already loaded. 352 # Already loaded.
351 return 353 return False
352 data['target_build_files'].add(build_file_path) 354 data['target_build_files'].add(build_file_path)
353 355
354 gyp.DebugOutput(gyp.DEBUG_INCLUDES, 356 gyp.DebugOutput(gyp.DEBUG_INCLUDES,
355 "Loading Target Build File '%s'" % build_file_path) 357 "Loading Target Build File '%s'" % build_file_path)
356 358
357 build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, variables, 359 build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, variables,
358 includes, True, check) 360 includes, True, check)
359 361
360 # Store DEPTH for later use in generators. 362 # Store DEPTH for later use in generators.
361 build_file_data['_DEPTH'] = depth 363 build_file_data['_DEPTH'] = depth
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 "Unable to find targets in build file %s" % build_file_path 413 "Unable to find targets in build file %s" % build_file_path
412 414
413 # No longer needed. 415 # No longer needed.
414 del build_file_data['target_defaults'] 416 del build_file_data['target_defaults']
415 417
416 # Look for dependencies. This means that dependency resolution occurs 418 # Look for dependencies. This means that dependency resolution occurs
417 # after "pre" conditionals and variable expansion, but before "post" - 419 # after "pre" conditionals and variable expansion, but before "post" -
418 # in other words, you can't put a "dependencies" section inside a "post" 420 # in other words, you can't put a "dependencies" section inside a "post"
419 # conditional within a target. 421 # conditional within a target.
420 422
423 dependencies = []
421 if 'targets' in build_file_data: 424 if 'targets' in build_file_data:
422 for target_dict in build_file_data['targets']: 425 for target_dict in build_file_data['targets']:
423 if 'dependencies' not in target_dict: 426 if 'dependencies' not in target_dict:
424 continue 427 continue
425 for dependency in target_dict['dependencies']: 428 for dependency in target_dict['dependencies']:
426 other_build_file = \ 429 dependencies.append(
427 gyp.common.ResolveTarget(build_file_path, dependency, None)[0] 430 gyp.common.ResolveTarget(build_file_path, dependency, None)[0])
428 try:
429 LoadTargetBuildFile(other_build_file, data, aux_data, variables,
430 includes, depth, check)
431 except Exception, e:
432 gyp.common.ExceptionAppend(
433 e, 'while loading dependencies of %s' % build_file_path)
434 raise
435 431
436 return data 432 if load_dependencies:
433 for dependency in dependencies:
434 try:
435 LoadTargetBuildFile(dependency, data, aux_data, variables,
436 includes, depth, check, load_dependencies)
437 except Exception, e:
438 gyp.common.ExceptionAppend(
439 e, 'while loading dependencies of %s' % build_file_path)
440 raise
441 else:
442 return (build_file_path, dependencies)
443
444
445 # Wrapper around LoadTargetBuildFile used when LoadTargetBuildFile
446 # is executed in a worker process.
447 def CallLoadTargetBuildFile(global_flags,
448 build_file_path, data,
449 aux_data, variables,
450 includes, depth, check):
451 # Apply globals so that the worker process behaves the same.
452 for key, value in global_flags.iteritems():
453 globals()[key] = value
454
455 # Save the keys so we can return data that changed.
456 data_keys = set(data)
457 aux_data_keys = set(aux_data)
458
459 result = LoadTargetBuildFile(build_file_path, data,
460 aux_data, variables,
461 includes, depth, check, False)
462 if not result:
463 return result
464
465 (build_file_path, dependencies) = result
466
467 data_out = {}
468 for key in data:
469 if key == 'target_build_files':
470 continue
471 if key not in data_keys:
472 data_out[key] = data[key]
473 aux_data_out = {}
474 for key in aux_data:
475 if key not in aux_data_keys:
476 aux_data_out[key] = aux_data[key]
477
478 # This gets serialized and sent back to the main process via a pipe.
479 # It's handled in LoadTargetBuildFileCallback.
480 return (build_file_path,
481 data_out,
482 aux_data_out,
483 dependencies)
484
485
486 # Handle the results of LoadTargetBuildFile that executed in a separate
487 # process.
488 def LoadTargetBuildFileCallback(result):
489 (build_file_path0, data0, aux_data0, dependencies0) = result
490 gyp.data['target_build_files'].add(build_file_path0)
M-A Ruel 2012/09/18 19:42:18 Can you tell me where the "gyp" variable is define
dmazzoni 2012/09/19 21:14:52 gyp is the module object. I cleaned this up by cr
491 for key in data0:
492 gyp.data[key] = data0[key]
493 for key in aux_data0:
494 gyp.aux_data[key] = aux_data0[key]
495 for new_dependency in dependencies0:
496 if new_dependency not in gyp.scheduled:
497 gyp.scheduled.add(new_dependency)
498 gyp.dependencies.append(new_dependency)
499 gyp.pending -= 1
500
501
502 def LoadTargetBuildFileParallel(build_file_path, data, aux_data,
503 variables, includes, depth, check):
504 gyp.dependencies = [build_file_path]
M-A Ruel 2012/09/18 19:42:18 I find the code a bit hard to read, the "gyp" vari
505 gyp.scheduled = set([build_file_path])
506 gyp.pending = 0
507 gyp.data = data
508 gyp.aux_data = aux_data
509 while gyp.dependencies or gyp.pending:
510 if not gyp.dependencies:
511 time.sleep(0.003)
512 continue
513
514 dependency = gyp.dependencies.pop()
515
516 gyp.pending += 1
517 data_in = {}
518 data_in['target_build_files'] = data['target_build_files']
519 aux_data_in = {}
520 global_flags = {
521 'path_sections': globals()['path_sections'],
522 'non_configuration_keys': globals()['non_configuration_keys'],
523 'absolute_build_file_paths': globals()['absolute_build_file_paths'],
524 'multiple_toolsets': globals()['multiple_toolsets']}
525
526 if 'pool' not in dir(gyp):
527 gyp.pool = multiprocessing.Pool(8)
528 gyp.pool.apply_async(
529 CallLoadTargetBuildFile,
530 args = (global_flags, dependency,
531 data_in, aux_data_in,
532 variables, includes, depth, check),
533 callback = LoadTargetBuildFileCallback)
534 time.sleep(0.003)
437 535
438 536
439 # Look for the bracket that matches the first bracket seen in a 537 # Look for the bracket that matches the first bracket seen in a
440 # string, and return the start and end as a tuple. For example, if 538 # string, and return the start and end as a tuple. For example, if
441 # the input is something like "<(foo <(bar)) blah", then it would 539 # the input is something like "<(foo <(bar)) blah", then it would
442 # return (1, 13), indicating the entire string except for the leading 540 # return (1, 13), indicating the entire string except for the leading
443 # "<" and trailing " blah". 541 # "<" and trailing " blah".
444 def FindEnclosingBracketGroup(input): 542 def FindEnclosingBracketGroup(input):
445 brackets = { '}': '{', 543 brackets = { '}': '{',
446 ']': '[', 544 ']': '[',
(...skipping 1879 matching lines...) Expand 10 before | Expand all | Expand 10 after
2326 # Prepare a key like 'path/to:target_name'. 2424 # Prepare a key like 'path/to:target_name'.
2327 key = subdir + ':' + name 2425 key = subdir + ':' + name
2328 if key in used: 2426 if key in used:
2329 # Complain if this target is already used. 2427 # Complain if this target is already used.
2330 raise Exception('Duplicate target name "%s" in directory "%s" used both ' 2428 raise Exception('Duplicate target name "%s" in directory "%s" used both '
2331 'in "%s" and "%s".' % (name, subdir, gyp, used[key])) 2429 'in "%s" and "%s".' % (name, subdir, gyp, used[key]))
2332 used[key] = gyp 2430 used[key] = gyp
2333 2431
2334 2432
2335 def Load(build_files, variables, includes, depth, generator_input_info, check, 2433 def Load(build_files, variables, includes, depth, generator_input_info, check,
2336 circular_check): 2434 circular_check, parallel):
2337 # Set up path_sections and non_configuration_keys with the default data plus 2435 # Set up path_sections and non_configuration_keys with the default data plus
2338 # the generator-specifc data. 2436 # the generator-specifc data.
2339 global path_sections 2437 global path_sections
2340 path_sections = base_path_sections[:] 2438 path_sections = base_path_sections[:]
2341 path_sections.extend(generator_input_info['path_sections']) 2439 path_sections.extend(generator_input_info['path_sections'])
2342 2440
2343 global non_configuration_keys 2441 global non_configuration_keys
2344 non_configuration_keys = base_non_configuration_keys[:] 2442 non_configuration_keys = base_non_configuration_keys[:]
2345 non_configuration_keys.extend(generator_input_info['non_configuration_keys']) 2443 non_configuration_keys.extend(generator_input_info['non_configuration_keys'])
2346 2444
(...skipping 20 matching lines...) Expand all
2367 # NOTE: data contains both "target" files (.gyp) and "includes" (.gypi), as 2465 # NOTE: data contains both "target" files (.gyp) and "includes" (.gypi), as
2368 # well as meta-data (e.g. 'included_files' key). 'target_build_files' keeps 2466 # well as meta-data (e.g. 'included_files' key). 'target_build_files' keeps
2369 # track of the keys corresponding to "target" files. 2467 # track of the keys corresponding to "target" files.
2370 data = {'target_build_files': set()} 2468 data = {'target_build_files': set()}
2371 aux_data = {} 2469 aux_data = {}
2372 for build_file in build_files: 2470 for build_file in build_files:
2373 # Normalize paths everywhere. This is important because paths will be 2471 # Normalize paths everywhere. This is important because paths will be
2374 # used as keys to the data dict and for references between input files. 2472 # used as keys to the data dict and for references between input files.
2375 build_file = os.path.normpath(build_file) 2473 build_file = os.path.normpath(build_file)
2376 try: 2474 try:
2377 LoadTargetBuildFile(build_file, data, aux_data, variables, includes, 2475 if parallel:
2378 depth, check) 2476 print >>sys.stderr, 'Using parallel processing (experimental).'
2477 LoadTargetBuildFileParallel(build_file, data, aux_data,
2478 variables, includes, depth, check)
2479 else:
2480 LoadTargetBuildFile(build_file, data, aux_data,
2481 variables, includes, depth, check, True)
2379 except Exception, e: 2482 except Exception, e:
2380 gyp.common.ExceptionAppend(e, 'while trying to load %s' % build_file) 2483 gyp.common.ExceptionAppend(e, 'while trying to load %s' % build_file)
2381 raise 2484 raise
2382 2485
2383 # Build a dict to access each target's subdict by qualified name. 2486 # Build a dict to access each target's subdict by qualified name.
2384 targets = BuildTargetsDict(data) 2487 targets = BuildTargetsDict(data)
2385 2488
2386 # Fully qualify all dependency links. 2489 # Fully qualify all dependency links.
2387 QualifyDependencies(targets) 2490 QualifyDependencies(targets)
2388 2491
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
2476 ValidateRunAsInTarget(target, target_dict, build_file) 2579 ValidateRunAsInTarget(target, target_dict, build_file)
2477 ValidateActionsInTarget(target, target_dict, build_file) 2580 ValidateActionsInTarget(target, target_dict, build_file)
2478 2581
2479 # Generators might not expect ints. Turn them into strs. 2582 # Generators might not expect ints. Turn them into strs.
2480 TurnIntIntoStrInDict(data) 2583 TurnIntIntoStrInDict(data)
2481 2584
2482 # TODO(mark): Return |data| for now because the generator needs a list of 2585 # TODO(mark): Return |data| for now because the generator needs a list of
2483 # build files that came in. In the future, maybe it should just accept 2586 # build files that came in. In the future, maybe it should just accept
2484 # a list, and not the whole data dict. 2587 # a list, and not the whole data dict.
2485 return [flat_list, targets, data] 2588 return [flat_list, targets, data]
OLDNEW
« pylib/gyp/__init__.py ('K') | « pylib/gyp/__init__.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698