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

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

Issue 773883002: Cache data for included files in the multiprocess load codepath (Closed) Base URL: http://gyp.googlecode.com/svn/trunk
Patch Set: Remove global statements Created 6 years 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 # 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
(...skipping 30 matching lines...) Expand all
41 'destination', 41 'destination',
42 'files', 42 'files',
43 'include_dirs', 43 'include_dirs',
44 'inputs', 44 'inputs',
45 'libraries', 45 'libraries',
46 'outputs', 46 'outputs',
47 'sources', 47 'sources',
48 ] 48 ]
49 path_sections = set() 49 path_sections = set()
50 50
51 # These per-process dictionaries are used to cache build file data when loading
52 # in parallel mode.
53 per_process_data = {}
54 per_process_aux_data = {}
55
51 def IsPathSection(section): 56 def IsPathSection(section):
52 # If section ends in one of the '=+?!' characters, it's applied to a section 57 # If section ends in one of the '=+?!' characters, it's applied to a section
53 # without the trailing characters. '/' is notably absent from this list, 58 # without the trailing characters. '/' is notably absent from this list,
54 # because there's no way for a regular expression to be treated as a path. 59 # because there's no way for a regular expression to be treated as a path.
55 while section[-1:] in '=+?!': 60 while section[-1:] in '=+?!':
56 section = section[:-1] 61 section = section[:-1]
57 62
58 if section in path_sections: 63 if section in path_sections:
59 return True 64 return True
60 65
(...skipping 294 matching lines...) Expand 10 before | Expand all | Expand 10 after
355 if depth: 360 if depth:
356 # TODO(dglazkov) The backslash/forward-slash replacement at the end is a 361 # TODO(dglazkov) The backslash/forward-slash replacement at the end is a
357 # temporary measure. This should really be addressed by keeping all paths 362 # temporary measure. This should really be addressed by keeping all paths
358 # in POSIX until actual project generation. 363 # in POSIX until actual project generation.
359 d = gyp.common.RelativePath(depth, os.path.dirname(build_file_path)) 364 d = gyp.common.RelativePath(depth, os.path.dirname(build_file_path))
360 if d == '': 365 if d == '':
361 variables['DEPTH'] = '.' 366 variables['DEPTH'] = '.'
362 else: 367 else:
363 variables['DEPTH'] = d.replace('\\', '/') 368 variables['DEPTH'] = d.replace('\\', '/')
364 369
365 if build_file_path in data['target_build_files']: 370 # The 'target_build_files' key is only set when loading target build files in
366 # Already loaded. 371 # the non-parallel code path, where LoadTargetBuildFile is called
367 return False 372 # recursively. In the parallel code path, we don't need to check whether the
368 data['target_build_files'].add(build_file_path) 373 # |build_file_path| has already been loaded, because the 'scheduled' set in
374 # ParallelState guarantees that we never load the same |build_file_path|
375 # twice.
376 if 'target_build_files' in data:
377 if build_file_path in data['target_build_files']:
378 # Already loaded.
379 return False
380 data['target_build_files'].add(build_file_path)
369 381
370 gyp.DebugOutput(gyp.DEBUG_INCLUDES, 382 gyp.DebugOutput(gyp.DEBUG_INCLUDES,
371 "Loading Target Build File '%s'", build_file_path) 383 "Loading Target Build File '%s'", build_file_path)
372 384
373 build_file_data = LoadOneBuildFile(build_file_path, data, aux_data, 385 build_file_data = LoadOneBuildFile(build_file_path, data, aux_data,
374 includes, True, check) 386 includes, True, check)
375 387
376 # Store DEPTH for later use in generators. 388 # Store DEPTH for later use in generators.
377 build_file_data['_DEPTH'] = depth 389 build_file_data['_DEPTH'] = depth
378 390
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
449 try: 461 try:
450 LoadTargetBuildFile(dependency, data, aux_data, variables, 462 LoadTargetBuildFile(dependency, data, aux_data, variables,
451 includes, depth, check, load_dependencies) 463 includes, depth, check, load_dependencies)
452 except Exception, e: 464 except Exception, e:
453 gyp.common.ExceptionAppend( 465 gyp.common.ExceptionAppend(
454 e, 'while loading dependencies of %s' % build_file_path) 466 e, 'while loading dependencies of %s' % build_file_path)
455 raise 467 raise
456 else: 468 else:
457 return (build_file_path, dependencies) 469 return (build_file_path, dependencies)
458 470
459
460 def CallLoadTargetBuildFile(global_flags, 471 def CallLoadTargetBuildFile(global_flags,
461 build_file_path, data, 472 build_file_path, variables,
462 aux_data, variables,
463 includes, depth, check, 473 includes, depth, check,
464 generator_input_info): 474 generator_input_info):
465 """Wrapper around LoadTargetBuildFile for parallel processing. 475 """Wrapper around LoadTargetBuildFile for parallel processing.
466 476
467 This wrapper is used when LoadTargetBuildFile is executed in 477 This wrapper is used when LoadTargetBuildFile is executed in
468 a worker process. 478 a worker process.
469 """ 479 """
470 480
471 try: 481 try:
472 signal.signal(signal.SIGINT, signal.SIG_IGN) 482 signal.signal(signal.SIGINT, signal.SIG_IGN)
473 483
474 # Apply globals so that the worker process behaves the same. 484 # Apply globals so that the worker process behaves the same.
475 for key, value in global_flags.iteritems(): 485 for key, value in global_flags.iteritems():
476 globals()[key] = value 486 globals()[key] = value
477 487
478 # Save the keys so we can return data that changed.
479 data_keys = set(data)
480 aux_data_keys = set(aux_data)
481
482 SetGeneratorGlobals(generator_input_info) 488 SetGeneratorGlobals(generator_input_info)
483 result = LoadTargetBuildFile(build_file_path, data, 489 result = LoadTargetBuildFile(build_file_path, per_process_data,
484 aux_data, variables, 490 per_process_aux_data, variables,
485 includes, depth, check, False) 491 includes, depth, check, False)
486 if not result: 492 if not result:
487 return result 493 return result
488 494
489 (build_file_path, dependencies) = result 495 (build_file_path, dependencies) = result
490 496
491 data_out = {} 497 # We can safely pop the build_file_data from per_process_data because it
492 for key in data: 498 # will never be referenced by this process again, so we don't need to keep
493 if key == 'target_build_files': 499 # it in the cache.
494 continue 500 build_file_data = per_process_data.pop(build_file_path)
495 if key not in data_keys:
496 data_out[key] = data[key]
497 aux_data_out = {}
498 for key in aux_data:
499 if key not in aux_data_keys:
500 aux_data_out[key] = aux_data[key]
501 501
502 # This gets serialized and sent back to the main process via a pipe. 502 # This gets serialized and sent back to the main process via a pipe.
503 # It's handled in LoadTargetBuildFileCallback. 503 # It's handled in LoadTargetBuildFileCallback.
504 return (build_file_path, 504 return (build_file_path,
505 data_out, 505 build_file_data,
506 aux_data_out,
507 dependencies) 506 dependencies)
508 except GypError, e: 507 except GypError, e:
509 sys.stderr.write("gyp: %s\n" % e) 508 sys.stderr.write("gyp: %s\n" % e)
510 return None 509 return None
511 except Exception, e: 510 except Exception, e:
512 print >>sys.stderr, 'Exception:', e 511 print >>sys.stderr, 'Exception:', e
513 print >>sys.stderr, traceback.format_exc() 512 print >>sys.stderr, traceback.format_exc()
514 return None 513 return None
515 514
516 515
(...skipping 10 matching lines...) Expand all
527 """ 526 """
528 527
529 def __init__(self): 528 def __init__(self):
530 # The multiprocessing pool. 529 # The multiprocessing pool.
531 self.pool = None 530 self.pool = None
532 # The condition variable used to protect this object and notify 531 # The condition variable used to protect this object and notify
533 # the main loop when there might be more data to process. 532 # the main loop when there might be more data to process.
534 self.condition = None 533 self.condition = None
535 # The "data" dict that was passed to LoadTargetBuildFileParallel 534 # The "data" dict that was passed to LoadTargetBuildFileParallel
536 self.data = None 535 self.data = None
537 # The "aux_data" dict that was passed to LoadTargetBuildFileParallel
538 self.aux_data = None
539 # The number of parallel calls outstanding; decremented when a response 536 # The number of parallel calls outstanding; decremented when a response
540 # was received. 537 # was received.
541 self.pending = 0 538 self.pending = 0
542 # The set of all build files that have been scheduled, so we don't 539 # The set of all build files that have been scheduled, so we don't
543 # schedule the same one twice. 540 # schedule the same one twice.
544 self.scheduled = set() 541 self.scheduled = set()
545 # A list of dependency build file paths that haven't been scheduled yet. 542 # A list of dependency build file paths that haven't been scheduled yet.
546 self.dependencies = [] 543 self.dependencies = []
547 # Flag to indicate if there was an error in a child process. 544 # Flag to indicate if there was an error in a child process.
548 self.error = False 545 self.error = False
549 546
550 def LoadTargetBuildFileCallback(self, result): 547 def LoadTargetBuildFileCallback(self, result):
551 """Handle the results of running LoadTargetBuildFile in another process. 548 """Handle the results of running LoadTargetBuildFile in another process.
552 """ 549 """
553 self.condition.acquire() 550 self.condition.acquire()
554 if not result: 551 if not result:
555 self.error = True 552 self.error = True
556 self.condition.notify() 553 self.condition.notify()
557 self.condition.release() 554 self.condition.release()
558 return 555 return
559 (build_file_path0, data0, aux_data0, dependencies0) = result 556 (build_file_path0, build_file_data0, dependencies0) = result
557 self.data[build_file_path0] = build_file_data0
560 self.data['target_build_files'].add(build_file_path0) 558 self.data['target_build_files'].add(build_file_path0)
561 for key in data0:
562 self.data[key] = data0[key]
563 for key in aux_data0:
564 self.aux_data[key] = aux_data0[key]
565 for new_dependency in dependencies0: 559 for new_dependency in dependencies0:
566 if new_dependency not in self.scheduled: 560 if new_dependency not in self.scheduled:
567 self.scheduled.add(new_dependency) 561 self.scheduled.add(new_dependency)
568 self.dependencies.append(new_dependency) 562 self.dependencies.append(new_dependency)
569 self.pending -= 1 563 self.pending -= 1
570 self.condition.notify() 564 self.condition.notify()
571 self.condition.release() 565 self.condition.release()
572 566
573 567
574 def LoadTargetBuildFilesParallel(build_files, data, aux_data, 568 def LoadTargetBuildFilesParallel(build_files, data, variables, includes, depth,
575 variables, includes, depth, check, 569 check, generator_input_info):
576 generator_input_info):
577 parallel_state = ParallelState() 570 parallel_state = ParallelState()
578 parallel_state.condition = threading.Condition() 571 parallel_state.condition = threading.Condition()
579 # Make copies of the build_files argument that we can modify while working. 572 # Make copies of the build_files argument that we can modify while working.
580 parallel_state.dependencies = list(build_files) 573 parallel_state.dependencies = list(build_files)
581 parallel_state.scheduled = set(build_files) 574 parallel_state.scheduled = set(build_files)
582 parallel_state.pending = 0 575 parallel_state.pending = 0
583 parallel_state.data = data 576 parallel_state.data = data
584 parallel_state.aux_data = aux_data
585 577
586 try: 578 try:
587 parallel_state.condition.acquire() 579 parallel_state.condition.acquire()
588 while parallel_state.dependencies or parallel_state.pending: 580 while parallel_state.dependencies or parallel_state.pending:
589 if parallel_state.error: 581 if parallel_state.error:
590 break 582 break
591 if not parallel_state.dependencies: 583 if not parallel_state.dependencies:
592 parallel_state.condition.wait() 584 parallel_state.condition.wait()
593 continue 585 continue
594 586
595 dependency = parallel_state.dependencies.pop() 587 dependency = parallel_state.dependencies.pop()
596 588
597 parallel_state.pending += 1 589 parallel_state.pending += 1
598 data_in = {}
599 data_in['target_build_files'] = data['target_build_files']
600 aux_data_in = {}
601 global_flags = { 590 global_flags = {
602 'path_sections': globals()['path_sections'], 591 'path_sections': globals()['path_sections'],
603 'non_configuration_keys': globals()['non_configuration_keys'], 592 'non_configuration_keys': globals()['non_configuration_keys'],
604 'multiple_toolsets': globals()['multiple_toolsets']} 593 'multiple_toolsets': globals()['multiple_toolsets']}
605 594
606 if not parallel_state.pool: 595 if not parallel_state.pool:
607 parallel_state.pool = multiprocessing.Pool(multiprocessing.cpu_count()) 596 parallel_state.pool = multiprocessing.Pool(multiprocessing.cpu_count())
608 parallel_state.pool.apply_async( 597 parallel_state.pool.apply_async(
609 CallLoadTargetBuildFile, 598 CallLoadTargetBuildFile,
610 args = (global_flags, dependency, 599 args = (global_flags, dependency,
611 data_in, aux_data_in,
612 variables, includes, depth, check, generator_input_info), 600 variables, includes, depth, check, generator_input_info),
613 callback = parallel_state.LoadTargetBuildFileCallback) 601 callback = parallel_state.LoadTargetBuildFileCallback)
614 except KeyboardInterrupt, e: 602 except KeyboardInterrupt, e:
615 parallel_state.pool.terminate() 603 parallel_state.pool.terminate()
616 raise e 604 raise e
617 605
618 parallel_state.condition.release() 606 parallel_state.condition.release()
619 607
620 parallel_state.pool.close() 608 parallel_state.pool.close()
621 parallel_state.pool.join() 609 parallel_state.pool.join()
(...skipping 2105 matching lines...) Expand 10 before | Expand all | Expand 10 after
2727 extra_sources_for_rules = generator_input_info['extra_sources_for_rules'] 2715 extra_sources_for_rules = generator_input_info['extra_sources_for_rules']
2728 2716
2729 # Load build files. This loads every target-containing build file into 2717 # Load build files. This loads every target-containing build file into
2730 # the |data| dictionary such that the keys to |data| are build file names, 2718 # the |data| dictionary such that the keys to |data| are build file names,
2731 # and the values are the entire build file contents after "early" or "pre" 2719 # and the values are the entire build file contents after "early" or "pre"
2732 # processing has been done and includes have been resolved. 2720 # processing has been done and includes have been resolved.
2733 # NOTE: data contains both "target" files (.gyp) and "includes" (.gypi), as 2721 # NOTE: data contains both "target" files (.gyp) and "includes" (.gypi), as
2734 # well as meta-data (e.g. 'included_files' key). 'target_build_files' keeps 2722 # well as meta-data (e.g. 'included_files' key). 'target_build_files' keeps
2735 # track of the keys corresponding to "target" files. 2723 # track of the keys corresponding to "target" files.
2736 data = {'target_build_files': set()} 2724 data = {'target_build_files': set()}
2737 aux_data = {}
2738 # Normalize paths everywhere. This is important because paths will be 2725 # Normalize paths everywhere. This is important because paths will be
2739 # used as keys to the data dict and for references between input files. 2726 # used as keys to the data dict and for references between input files.
2740 build_files = set(map(os.path.normpath, build_files)) 2727 build_files = set(map(os.path.normpath, build_files))
2741 if parallel: 2728 if parallel:
2742 LoadTargetBuildFilesParallel(build_files, data, aux_data, 2729 LoadTargetBuildFilesParallel(build_files, data, variables, includes, depth,
2743 variables, includes, depth, check, 2730 check, generator_input_info)
2744 generator_input_info)
2745 else: 2731 else:
2732 aux_data = {}
2746 for build_file in build_files: 2733 for build_file in build_files:
2747 try: 2734 try:
2748 LoadTargetBuildFile(build_file, data, aux_data, 2735 LoadTargetBuildFile(build_file, data, aux_data,
2749 variables, includes, depth, check, True) 2736 variables, includes, depth, check, True)
2750 except Exception, e: 2737 except Exception, e:
2751 gyp.common.ExceptionAppend(e, 'while trying to load %s' % build_file) 2738 gyp.common.ExceptionAppend(e, 'while trying to load %s' % build_file)
2752 raise 2739 raise
2753 2740
2754 # Build a dict to access each target's subdict by qualified name. 2741 # Build a dict to access each target's subdict by qualified name.
2755 targets = BuildTargetsDict(data) 2742 targets = BuildTargetsDict(data)
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
2857 ValidateRunAsInTarget(target, target_dict, build_file) 2844 ValidateRunAsInTarget(target, target_dict, build_file)
2858 ValidateActionsInTarget(target, target_dict, build_file) 2845 ValidateActionsInTarget(target, target_dict, build_file)
2859 2846
2860 # Generators might not expect ints. Turn them into strs. 2847 # Generators might not expect ints. Turn them into strs.
2861 TurnIntIntoStrInDict(data) 2848 TurnIntIntoStrInDict(data)
2862 2849
2863 # TODO(mark): Return |data| for now because the generator needs a list of 2850 # TODO(mark): Return |data| for now because the generator needs a list of
2864 # build files that came in. In the future, maybe it should just accept 2851 # build files that came in. In the future, maybe it should just accept
2865 # a list, and not the whole data dict. 2852 # a list, and not the whole data dict.
2866 return [flat_list, targets, data] 2853 return [flat_list, targets, data]
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