summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct1
-rw-r--r--site_scons/site_tools/ninja_next.py78
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)