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

Side by Side Diff: pylib/gyp/generator/ninja.py

Issue 1506733002: GYP: Make GYP build deterministic (Closed) Base URL: https://chromium.googlesource.com/external/gyp.git@master
Patch Set: Clearer naming, better understanding Created 5 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
« no previous file with comments | « no previous file | pylib/gyp/input.py » ('j') | pylib/gyp/input.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2013 Google Inc. All rights reserved. 1 # Copyright (c) 2013 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 import collections 5 import collections
6 import copy 6 import copy
7 import hashlib 7 import hashlib
8 import json 8 import json
9 import multiprocessing 9 import multiprocessing
10 import os.path 10 import os.path
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
62 generator_extra_sources_for_rules = [] 62 generator_extra_sources_for_rules = []
63 generator_filelist_paths = None 63 generator_filelist_paths = None
64 64
65 generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested() 65 generator_supports_multiple_toolsets = gyp.common.CrossCompileRequested()
66 66
67 def StripPrefix(arg, prefix): 67 def StripPrefix(arg, prefix):
68 if arg.startswith(prefix): 68 if arg.startswith(prefix):
69 return arg[len(prefix):] 69 return arg[len(prefix):]
70 return arg 70 return arg
71 71
72 def OrderDeterministically(l, **kwargs):
73 """ Sorts l so that it is ordered deterministically. """
74 return sorted(l, **kwargs)
mithro-old 2015/12/10 03:15:27 I think this should be in gyp.common with OrderedS
Zachary Forman 2015/12/30 02:18:43 Yeah, you caught me mid refactor. Good spot for it
72 75
73 def QuoteShellArgument(arg, flavor): 76 def QuoteShellArgument(arg, flavor):
74 """Quote a string such that it will be interpreted as a single argument 77 """Quote a string such that it will be interpreted as a single argument
75 by the shell.""" 78 by the shell."""
76 # Rather than attempting to enumerate the bad shell characters, just 79 # Rather than attempting to enumerate the bad shell characters, just
77 # whitelist common OK ones and quote anything else. 80 # whitelist common OK ones and quote anything else.
78 if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg): 81 if re.match(r'^[a-zA-Z0-9_=.\\/-]+$', arg):
79 return arg # No quoting necessary. 82 return arg # No quoting necessary.
80 if flavor == 'win': 83 if flavor == 'win':
81 return gyp.msvs_emulation.QuoteForRspFile(arg) 84 return gyp.msvs_emulation.QuoteForRspFile(arg)
(...skipping 332 matching lines...) Expand 10 before | Expand all | Expand 10 after
414 # TODO(evan): it is rather confusing which things are lists and which 417 # TODO(evan): it is rather confusing which things are lists and which
415 # are strings. Fix these. 418 # are strings. Fix these.
416 if 'dependencies' in spec: 419 if 'dependencies' in spec:
417 for dep in spec['dependencies']: 420 for dep in spec['dependencies']:
418 if dep in self.target_outputs: 421 if dep in self.target_outputs:
419 target = self.target_outputs[dep] 422 target = self.target_outputs[dep]
420 actions_depends.append(target.PreActionInput(self.flavor)) 423 actions_depends.append(target.PreActionInput(self.flavor))
421 compile_depends.append(target.PreCompileInput()) 424 compile_depends.append(target.PreCompileInput())
422 actions_depends = filter(None, actions_depends) 425 actions_depends = filter(None, actions_depends)
423 compile_depends = filter(None, compile_depends) 426 compile_depends = filter(None, compile_depends)
424 actions_depends = self.WriteCollapsedDependencies('actions_depends', 427 actions_depends = self.WriteCollapsedDependencies(
425 actions_depends) 428 'actions_depends', OrderDeterministically(actions_depends))
426 compile_depends = self.WriteCollapsedDependencies('compile_depends', 429 compile_depends = self.WriteCollapsedDependencies(
427 compile_depends) 430 'compile_depends', OrderDeterministically(compile_depends))
428 self.target.preaction_stamp = actions_depends 431 self.target.preaction_stamp = actions_depends
429 self.target.precompile_stamp = compile_depends 432 self.target.precompile_stamp = compile_depends
430 433
431 # Write out actions, rules, and copies. These must happen before we 434 # Write out actions, rules, and copies. These must happen before we
432 # compile any sources, so compute a list of predependencies for sources 435 # compile any sources, so compute a list of predependencies for sources
433 # while we do it. 436 # while we do it.
434 extra_sources = [] 437 extra_sources = []
435 mac_bundle_depends = [] 438 mac_bundle_depends = []
436 self.target.actions_stamp = self.WriteActionsRulesCopies( 439 self.target.actions_stamp = self.WriteActionsRulesCopies(
437 spec, extra_sources, actions_depends, mac_bundle_depends) 440 spec, extra_sources, actions_depends, mac_bundle_depends)
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
552 if 'rules' in spec: 555 if 'rules' in spec:
553 outputs += self.WriteRules(spec['rules'], extra_sources, prebuild, 556 outputs += self.WriteRules(spec['rules'], extra_sources, prebuild,
554 mac_bundle_resources, 557 mac_bundle_resources,
555 extra_mac_bundle_resources) 558 extra_mac_bundle_resources)
556 if 'copies' in spec: 559 if 'copies' in spec:
557 outputs += self.WriteCopies(spec['copies'], prebuild, mac_bundle_depends) 560 outputs += self.WriteCopies(spec['copies'], prebuild, mac_bundle_depends)
558 561
559 if 'sources' in spec and self.flavor == 'win': 562 if 'sources' in spec and self.flavor == 'win':
560 outputs += self.WriteWinIdlFiles(spec, prebuild) 563 outputs += self.WriteWinIdlFiles(spec, prebuild)
561 564
562 stamp = self.WriteCollapsedDependencies('actions_rules_copies', outputs) 565 stamp = self.WriteCollapsedDependencies('actions_rules_copies',
566 OrderDeterministically(outputs))
563 567
564 if self.is_mac_bundle: 568 if self.is_mac_bundle:
565 xcassets = self.WriteMacBundleResources( 569 xcassets = self.WriteMacBundleResources(
566 extra_mac_bundle_resources + mac_bundle_resources, mac_bundle_depends) 570 extra_mac_bundle_resources + mac_bundle_resources, mac_bundle_depends)
567 partial_info_plist = self.WriteMacXCassets(xcassets, mac_bundle_depends) 571 partial_info_plist = self.WriteMacXCassets(xcassets, mac_bundle_depends)
568 self.WriteMacInfoPlist(partial_info_plist, mac_bundle_depends) 572 self.WriteMacInfoPlist(partial_info_plist, mac_bundle_depends)
569 573
570 return stamp 574 return stamp
571 575
572 def GenerateDescription(self, verb, message, fallback): 576 def GenerateDescription(self, verb, message, fallback):
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
650 # Rules can potentially make use of some special variables which 654 # Rules can potentially make use of some special variables which
651 # must vary per source file. 655 # must vary per source file.
652 # Compute the list of variables we'll need to provide. 656 # Compute the list of variables we'll need to provide.
653 special_locals = ('source', 'root', 'dirname', 'ext', 'name') 657 special_locals = ('source', 'root', 'dirname', 'ext', 'name')
654 needed_variables = set(['source']) 658 needed_variables = set(['source'])
655 for argument in args: 659 for argument in args:
656 for var in special_locals: 660 for var in special_locals:
657 if '${%s}' % var in argument: 661 if '${%s}' % var in argument:
658 needed_variables.add(var) 662 needed_variables.add(var)
659 663
664 needed_variables = OrderDeterministically(needed_variables)
665
660 def cygwin_munge(path): 666 def cygwin_munge(path):
661 # pylint: disable=cell-var-from-loop 667 # pylint: disable=cell-var-from-loop
662 if is_cygwin: 668 if is_cygwin:
663 return path.replace('\\', '/') 669 return path.replace('\\', '/')
664 return path 670 return path
665 671
666 inputs = [self.GypPathToNinja(i, env) for i in rule.get('inputs', [])] 672 inputs = [self.GypPathToNinja(i, env) for i in rule.get('inputs', [])]
667 673
668 # If there are n source files matching the rule, and m additional rule 674 # If there are n source files matching the rule, and m additional rule
669 # inputs, then adding 'inputs' to each build edge written below will 675 # inputs, then adding 'inputs' to each build edge written below will
670 # write m * n inputs. Collapsing reduces this to m + n. 676 # write m * n inputs. Collapsing reduces this to m + n.
671 sources = rule.get('rule_sources', []) 677 sources = rule.get('rule_sources', [])
672 num_inputs = len(inputs) 678 num_inputs = len(inputs)
673 if prebuild: 679 if prebuild:
674 num_inputs += 1 680 num_inputs += 1
675 if num_inputs > 2 and len(sources) > 2: 681 if num_inputs > 2 and len(sources) > 2:
676 inputs = [self.WriteCollapsedDependencies( 682 inputs = [self.WriteCollapsedDependencies(
677 rule['rule_name'], inputs, order_only=prebuild)] 683 rule['rule_name'], OrderDeterministically(inputs), order_only=prebuild )]
678 prebuild = [] 684 prebuild = []
679 685
680 # For each source file, write an edge that generates all the outputs. 686 # For each source file, write an edge that generates all the outputs.
681 for source in sources: 687 for source in sources:
682 source = os.path.normpath(source) 688 source = os.path.normpath(source)
683 dirname, basename = os.path.split(source) 689 dirname, basename = os.path.split(source)
684 root, ext = os.path.splitext(basename) 690 root, ext = os.path.splitext(basename)
685 691
686 # Gather the list of inputs and outputs, expanding $vars if possible. 692 # Gather the list of inputs and outputs, expanding $vars if possible.
687 outputs = [self.ExpandRuleVariables(o, root, dirname, 693 outputs = [self.ExpandRuleVariables(o, root, dirname,
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
722 elif var == 'name': 728 elif var == 'name':
723 extra_bindings.append(('name', cygwin_munge(basename))) 729 extra_bindings.append(('name', cygwin_munge(basename)))
724 else: 730 else:
725 assert var == None, repr(var) 731 assert var == None, repr(var)
726 732
727 outputs = [self.GypPathToNinja(o, env) for o in outputs] 733 outputs = [self.GypPathToNinja(o, env) for o in outputs]
728 if self.flavor == 'win': 734 if self.flavor == 'win':
729 # WriteNewNinjaRule uses unique_name for creating an rsp file on win. 735 # WriteNewNinjaRule uses unique_name for creating an rsp file on win.
730 extra_bindings.append(('unique_name', 736 extra_bindings.append(('unique_name',
731 hashlib.md5(outputs[0]).hexdigest())) 737 hashlib.md5(outputs[0]).hexdigest()))
738
739 # Make sure we sort extra_bindings so that output is deterministic.
732 self.ninja.build(outputs, rule_name, self.GypPathToNinja(source), 740 self.ninja.build(outputs, rule_name, self.GypPathToNinja(source),
733 implicit=inputs, 741 implicit=inputs,
734 order_only=prebuild, 742 order_only=prebuild,
735 variables=extra_bindings) 743 variables=extra_bindings)
736 744
737 all_outputs.extend(outputs) 745 all_outputs.extend(outputs)
738 746
739 return all_outputs 747 return all_outputs
740 748
741 def WriteCopies(self, copies, prebuild, mac_bundle_depends): 749 def WriteCopies(self, copies, prebuild, mac_bundle_depends):
(...skipping 385 matching lines...) Expand 10 before | Expand all | Expand 10 after
1127 new_deps = [target.binary] 1135 new_deps = [target.binary]
1128 for new_dep in new_deps: 1136 for new_dep in new_deps:
1129 if new_dep not in extra_link_deps: 1137 if new_dep not in extra_link_deps:
1130 extra_link_deps.add(new_dep) 1138 extra_link_deps.add(new_dep)
1131 link_deps.append(new_dep) 1139 link_deps.append(new_dep)
1132 1140
1133 final_output = target.FinalOutput() 1141 final_output = target.FinalOutput()
1134 if not linkable or final_output != target.binary: 1142 if not linkable or final_output != target.binary:
1135 implicit_deps.add(final_output) 1143 implicit_deps.add(final_output)
1136 1144
1145 link_deps = OrderDeterministically(link_deps)
1146
1137 extra_bindings = [] 1147 extra_bindings = []
1138 if self.uses_cpp and self.flavor != 'win': 1148 if self.uses_cpp and self.flavor != 'win':
1139 extra_bindings.append(('ld', '$ldxx')) 1149 extra_bindings.append(('ld', '$ldxx'))
1140 1150
1141 output = self.ComputeOutput(spec, arch) 1151 output = self.ComputeOutput(spec, arch)
1142 if arch is None and not self.is_mac_bundle: 1152 if arch is None and not self.is_mac_bundle:
1143 self.AppendPostbuildVariable(extra_bindings, spec, output, output) 1153 self.AppendPostbuildVariable(extra_bindings, spec, output, output)
1144 1154
1145 is_executable = spec['type'] == 'executable' 1155 is_executable = spec['type'] == 'executable'
1146 # The ldflags config key is not used on mac or win. On those platforms 1156 # The ldflags config key is not used on mac or win. On those platforms
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
1243 output = [output, output + '.TOC'] 1253 output = [output, output + '.TOC']
1244 else: 1254 else:
1245 command = command + '_notoc' 1255 command = command + '_notoc'
1246 elif self.flavor == 'win': 1256 elif self.flavor == 'win':
1247 extra_bindings.append(('binary', output)) 1257 extra_bindings.append(('binary', output))
1248 pdbname = self.msvs_settings.GetPDBName( 1258 pdbname = self.msvs_settings.GetPDBName(
1249 config_name, self.ExpandSpecial, output + '.pdb') 1259 config_name, self.ExpandSpecial, output + '.pdb')
1250 if pdbname: 1260 if pdbname:
1251 output = [output, pdbname] 1261 output = [output, pdbname]
1252 1262
1253
1254 if len(solibs): 1263 if len(solibs):
1255 extra_bindings.append(('solibs', gyp.common.EncodePOSIXShellList(solibs))) 1264 extra_bindings.append(('solibs',
1265 gyp.common.EncodePOSIXShellList(OrderDeterministically(solibs))))
1256 1266
1257 ninja_file.build(output, command + command_suffix, link_deps, 1267 ninja_file.build(output, command + command_suffix, link_deps,
1258 implicit=list(implicit_deps), 1268 implicit=OrderDeterministically(implicit_deps),
1259 order_only=list(order_deps), 1269 order_only=order_deps,
1260 variables=extra_bindings) 1270 variables=extra_bindings)
1261 return linked_binary 1271 return linked_binary
1262 1272
1263 def WriteTarget(self, spec, config_name, config, link_deps, compile_deps): 1273 def WriteTarget(self, spec, config_name, config, link_deps, compile_deps):
1264 extra_link_deps = any(self.target_outputs.get(dep).Linkable() 1274 extra_link_deps = any(self.target_outputs.get(dep).Linkable()
1265 for dep in spec.get('dependencies', []) 1275 for dep in spec.get('dependencies', [])
1266 if dep in self.target_outputs) 1276 if dep in self.target_outputs)
1267 if spec['type'] == 'none' or (not link_deps and not extra_link_deps): 1277 if spec['type'] == 'none' or (not link_deps and not extra_link_deps):
1268 # TODO(evan): don't call this function for 'none' target types, as 1278 # TODO(evan): don't call this function for 'none' target types, as
1269 # it doesn't do anything, and we fake out a 'binary' with a stamp file. 1279 # it doesn't do anything, and we fake out a 'binary' with a stamp file.
(...skipping 1060 matching lines...) Expand 10 before | Expand all | Expand 10 after
2330 non_empty_target_names.add(name) 2340 non_empty_target_names.add(name)
2331 else: 2341 else:
2332 empty_target_names.add(name) 2342 empty_target_names.add(name)
2333 2343
2334 if target_short_names: 2344 if target_short_names:
2335 # Write a short name to build this target. This benefits both the 2345 # Write a short name to build this target. This benefits both the
2336 # "build chrome" case as well as the gyp tests, which expect to be 2346 # "build chrome" case as well as the gyp tests, which expect to be
2337 # able to run actions and build libraries by their short name. 2347 # able to run actions and build libraries by their short name.
2338 master_ninja.newline() 2348 master_ninja.newline()
2339 master_ninja.comment('Short names for targets.') 2349 master_ninja.comment('Short names for targets.')
2340 for short_name in target_short_names: 2350
2341 master_ninja.build(short_name, 'phony', [x.FinalOutput() for x in 2351 for short_name in OrderDeterministically(target_short_names):
2342 target_short_names[short_name]]) 2352 master_ninja.build(short_name, 'phony', ([x.FinalOutput() for x in
2353 target_short_names[short_name]]))
2343 2354
2344 # Write phony targets for any empty targets that weren't written yet. As 2355 # Write phony targets for any empty targets that weren't written yet. As
2345 # short names are not necessarily unique only do this for short names that 2356 # short names are not necessarily unique only do this for short names that
2346 # haven't already been output for another target. 2357 # haven't already been output for another target.
2347 empty_target_names = empty_target_names - non_empty_target_names 2358 empty_target_names = empty_target_names - non_empty_target_names
2348 if empty_target_names: 2359 if empty_target_names:
2349 master_ninja.newline() 2360 master_ninja.newline()
2350 master_ninja.comment('Empty targets (output for completeness).') 2361 master_ninja.comment('Empty targets (output for completeness).')
2351 for name in sorted(empty_target_names): 2362
2363 # Iterate over empty target names in a deterministic order by sorting.
2364 for name in OrderDeterministically(empty_target_names):
2352 master_ninja.build(name, 'phony') 2365 master_ninja.build(name, 'phony')
2353 2366
2354 if all_outputs: 2367 if all_outputs:
2355 master_ninja.newline() 2368 master_ninja.newline()
2356 master_ninja.build('all', 'phony', list(all_outputs)) 2369 master_ninja.build('all', 'phony', OrderDeterministically(all_outputs))
2357 master_ninja.default(generator_flags.get('default_target', 'all')) 2370 master_ninja.default(generator_flags.get('default_target', 'all'))
2358 2371
2359 master_ninja_file.close() 2372 master_ninja_file.close()
2360 2373
2361 2374
2362 def PerformBuild(data, configurations, params): 2375 def PerformBuild(data, configurations, params):
2363 options = params['options'] 2376 options = params['options']
2364 for config in configurations: 2377 for config in configurations:
2365 builddir = os.path.join(options.toplevel_dir, 'out', config) 2378 builddir = os.path.join(options.toplevel_dir, 'out', config)
2366 arguments = ['ninja', '-C', builddir] 2379 arguments = ['ninja', '-C', builddir]
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
2401 arglists.append( 2414 arglists.append(
2402 (target_list, target_dicts, data, params, config_name)) 2415 (target_list, target_dicts, data, params, config_name))
2403 pool.map(CallGenerateOutputForConfig, arglists) 2416 pool.map(CallGenerateOutputForConfig, arglists)
2404 except KeyboardInterrupt, e: 2417 except KeyboardInterrupt, e:
2405 pool.terminate() 2418 pool.terminate()
2406 raise e 2419 raise e
2407 else: 2420 else:
2408 for config_name in config_names: 2421 for config_name in config_names:
2409 GenerateOutputForConfig(target_list, target_dicts, data, params, 2422 GenerateOutputForConfig(target_list, target_dicts, data, params,
2410 config_name) 2423 config_name)
OLDNEW
« no previous file with comments | « no previous file | pylib/gyp/input.py » ('j') | pylib/gyp/input.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698