diff options
-rw-r--r-- | SConstruct | 1 | ||||
-rw-r--r-- | site_scons/site_tools/ninja_next.py | 78 |
2 files changed, 63 insertions, 16 deletions
diff --git a/SConstruct b/SConstruct index 91f59ff3d6e..da262bd2575 100644 --- a/SConstruct +++ b/SConstruct @@ -3907,6 +3907,7 @@ if get_option('ninja') != 'disabled': ninja_builder.generate(env) else: ninja_builder = Tool("ninja_next") + env["NINJA_BUILDDIR"] = env.Dir("$BUILD_DIR/ninja") ninja_builder.generate(env) ninjaConf = Configure(env, help=False, custom_tests = { diff --git a/site_scons/site_tools/ninja_next.py b/site_scons/site_tools/ninja_next.py index 6b1954397e0..157eb1911bf 100644 --- a/site_scons/site_tools/ninja_next.py +++ b/site_scons/site_tools/ninja_next.py @@ -147,10 +147,10 @@ def get_dependencies(node, skip_sources=False): def get_inputs(node, skip_unknown_types=False): """ Collect the Ninja inputs for node. - + If the given node has inputs which can not be converted into something Ninja can process, this will throw an exception. Optionally, those nodes - that are not processable can be skipped as inputs with the + that are not processable can be skipped as inputs with the skip_unknown_types keyword arg. """ executor = node.get_executor() @@ -171,8 +171,8 @@ def get_inputs(node, skip_unknown_types=False): if skip_unknown_types: continue raise Exception("Can't process {} node '{}' as an input for '{}'".format( - type(input_node), - str(input_node), + type(input_node), + str(input_node), str(node))) # convert node items into raw paths/aliases for ninja @@ -194,6 +194,25 @@ def get_outputs(node): return outputs +def generate_depfile(env, node, dependencies): + """ + Ninja tool function for writing a depfile. The depfile should include + the node path followed by all the dependent files in a makefile format. + """ + depfile = os.path.join(get_path(env['NINJA_BUILDDIR']), str(node) + '.depfile') + depfile_contents = str(node) + ": " + ' '.join(sorted(dependencies)) + + need_rewrite = False + try: + with open(depfile, 'r') as f: + need_rewrite = (f.read() != depfile_contents) + except FileNotFoundError: + need_rewrite = True + + if need_rewrite: + os.makedirs(os.path.dirname(depfile) or '.', exist_ok=True) + with open(depfile, 'w') as f: + f.write(depfile_contents) class SConsToNinjaTranslator: """Translates SCons Actions into Ninja build objects.""" @@ -265,8 +284,8 @@ class SConsToNinjaTranslator: # dependencies don't really matter when we're going to shove these to # the bottom of ninja's DAG anyway and Textfile builders can have text # content as their source which doesn't work as an implicit dep in - # ninja. We suppress errors on input Nodes types that we cannot handle - # since we expect that the re-invocation of SCons will handle dependency + # ninja. We suppress errors on input Nodes types that we cannot handle + # since we expect that the re-invocation of SCons will handle dependency # tracking for those Nodes and their dependents. if name == "_action": return { @@ -504,6 +523,7 @@ class NinjaState: "command": "$SCONS_INVOCATION_W_TARGETS", "description": "Regenerating $out", "generator": 1, + "depfile": os.path.join(get_path(env['NINJA_BUILDDIR']), '$out.depfile'), # Console pool restricts to 1 job running at a time, # it additionally has some special handling about # passing stdin, stdout, etc to process in this pool @@ -582,6 +602,8 @@ class NinjaState: ninja.comment("Generated by scons. DO NOT EDIT.") + ninja.variable("builddir", get_path(self.env['NINJA_BUILDDIR'])) + for pool_name, size in self.pools.items(): ninja.pool(pool_name, size) @@ -691,6 +713,16 @@ class NinjaState: build["outputs"] = first_output + # Optionally a rule can specify a depfile, and SCons can generate implicit + # dependencies into the depfile. This allows for dependencies to come and go + # without invalidating the ninja file. The depfile was created in ninja specifically + # for dealing with header files appearing and disappearing across rebuilds, but it can + # be repurposed for anything, as long as you have a way to regenerate the depfile. + # More specific info can be found here: https://ninja-build.org/manual.html#_depfile + if rule is not None and rule.get('depfile') and build.get('deps_files'): + path = build['outputs'] if SCons.Util.is_List(build['outputs']) else [build['outputs']] + generate_depfile(self.env, path[0], build.pop('deps_files', [])) + if "inputs" in build: build["inputs"].sort() @@ -725,19 +757,30 @@ class NinjaState: # generate this rule even though SCons should know we're # dependent on SCons files. # + # The REGENERATE rule uses depfile, so we need to generate the depfile + # in case any of the SConscripts have changed. The depfile needs to be + # path with in the build and the passed ninja file is an abspath, so + # we will use SCons to give us the path within the build. Normally + # generate_depfile should not be called like this, but instead be called + # through the use of custom rules, and filtered out in the normal + # list of build generation about. However, because the generate rule + # is hardcoded here, we need to do this generate_depfile call manually. + ninja_file_path = self.env.File(ninja_file).path + generate_depfile( + self.env, + ninja_file_path, + [self.env.File("#SConstruct").path] + glob("src/**/SConscript", recursive=True) + ) + # TODO: We're working on getting an API into SCons that will # allow us to query the actual SConscripts used. Right now # this glob method has deficiencies like skipping # jstests/SConscript and being specific to the MongoDB # repository layout. ninja.build( - self.env.File(ninja_file).path, + ninja_file_path, rule="REGENERATE", - implicit=[ - self.env.File("#SConstruct").path, - __file__, - ] - + sorted(glob("src/**/SConscript", recursive=True)), + implicit=[__file__], ) # If we ever change the name/s of the rules that include @@ -1071,13 +1114,16 @@ def register_custom_rule_mapping(env, pre_subst_string, rule): __NINJA_RULE_MAPPING[pre_subst_string] = rule -def register_custom_rule(env, rule, command, description="", deps=None, pool=None): +def register_custom_rule(env, rule, command, description="", deps=None, pool=None, use_depfile=False): """Allows specification of Ninja rules from inside SCons files.""" rule_obj = { "command": command, "description": description if description else "{} $out".format(rule), } + if use_depfile: + rule_obj["depfile"] = os.path.join(get_path(env['NINJA_BUILDDIR']),'$out.depfile') + if deps is not None: rule_obj["deps"] = deps @@ -1120,7 +1166,7 @@ def CheckNinjaCompdbExpand(env, context): context.Message('Checking if ninja compdb can expand response files... ') ret, output = context.TryAction( - action='ninja -f $SOURCE -t compdb -x CMD_RSP > $TARGET', + action='ninja -f $SOURCE -t compdb -x CMD_RSP > $TARGET', extension='.ninja', text=textwrap.dedent(""" rule CMD_RSP @@ -1129,7 +1175,7 @@ def CheckNinjaCompdbExpand(env, context): rspfile = $out.rsp rspfile_content = $rspc build fake_output.txt: CMD_RSP fake_input.txt - cmd = echo + cmd = echo pool = console rspc = "test" """)) @@ -1246,7 +1292,7 @@ def generate(env): env["NINJA_PREFIX"] = env.get("NINJA_PREFIX", "build") env["NINJA_SUFFIX"] = env.get("NINJA_SUFFIX", "ninja") env["NINJA_ALIAS_NAME"] = env.get("NINJA_ALIAS_NAME", "generate-ninja") - + env['NINJA_BUILDDIR'] = env.get("NINJA_BUILDDIR", env.Dir(".ninja").path) ninja_file_name = env.subst("${NINJA_PREFIX}.${NINJA_SUFFIX}") ninja_file = env.Ninja(target=ninja_file_name, source=[]) env.AlwaysBuild(ninja_file) |