Add support for scons ninja backend

This commit is contained in:
seelabs
2015-05-29 06:54:39 -07:00
committed by Vinnie Falco
parent ab8ffc1a00
commit 7b5bf7f129
2 changed files with 129 additions and 0 deletions

View File

@@ -60,6 +60,11 @@ The following environment variables modify the build environment:
OPENSSL_ROOT OPENSSL_ROOT
Path to the openssl directory. Path to the openssl directory.
The following extra options may be used:
--ninja Generate a `build.ninja` build file for the specified target
(see: https://martine.github.io/ninja/). Only gcc and clang targets
are supported.
''' '''
# #
''' '''
@@ -83,11 +88,16 @@ import time
import SCons.Action import SCons.Action
sys.path.append(os.path.join('src', 'beast', 'site_scons')) sys.path.append(os.path.join('src', 'beast', 'site_scons'))
sys.path.append(os.path.join('src', 'ripple', 'site_scons'))
import Beast import Beast
import scons_to_ninja
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
AddOption('--ninja', dest='ninja', action='store_true',
help='generate ninja build file build.ninja')
def parse_time(t): def parse_time(t):
return time.strptime(t, '%a %b %d %H:%M:%S %Z %Y') return time.strptime(t, '%a %b %d %H:%M:%S %Z %Y')
@@ -591,6 +601,7 @@ class ObjectBuilder(object):
self.env = env self.env = env
self.variant_dirs = variant_dirs self.variant_dirs = variant_dirs
self.objects = [] self.objects = []
self.child_envs = []
def add_source_files(self, *filenames, **kwds): def add_source_files(self, *filenames, **kwds):
for filename in filenames: for filename in filenames:
@@ -598,6 +609,7 @@ class ObjectBuilder(object):
if kwds: if kwds:
env = env.Clone() env = env.Clone()
env.Prepend(**kwds) env.Prepend(**kwds)
self.child_envs.append(env)
o = env.Object(Beast.variantFile(filename, self.variant_dirs)) o = env.Object(Beast.variantFile(filename, self.variant_dirs))
self.objects.append(o) self.objects.append(o)
@@ -733,6 +745,31 @@ def should_prepare_targets(style, toolchain, variant):
if should_prepare_target(t, style, toolchain, variant): if should_prepare_target(t, style, toolchain, variant):
return True return True
def should_build_ninja(style, toolchain, variant):
"""
Return True if a ninja build file should be generated.
Typically, scons will be called as follows to generate a ninja build file:
`scons ninja=1 gcc.debug` where `gcc.debug` may be replaced with any of our
non-visual studio targets. Raise an exception if we cannot generate the
requested ninja build file (for example, if multiple targets are requested).
"""
if not GetOption('ninja'):
return False
if len(COMMAND_LINE_TARGETS) != 1:
raise Exception('Can only generate a ninja file for a single target')
cl_target = COMMAND_LINE_TARGETS[0]
if 'vcxproj' in cl_target:
raise Exception('Cannot generate a ninja file for a vcxproj')
s = cl_target.split('.')
if ( style == 'unity' and 'nounity' in s or
style == 'classic' and 'nounity' not in s or
len(s) == 1 ):
return False
if len(s) == 2 or len(s) == 3:
return s[0] == toolchain and s[1] == variant
return False
for tu_style in ['classic', 'unity']: for tu_style in ['classic', 'unity']:
if tu_style == 'classic': if tu_style == 'classic':
sources = get_classic_sources() sources = get_classic_sources()
@@ -861,6 +898,13 @@ for tu_style in ['classic', 'unity']:
aliases[variant].extend(target) aliases[variant].extend(target)
env.Alias(variant_name, target) env.Alias(variant_name, target)
# ninja support
if should_build_ninja(tu_style, toolchain, variant):
print('Generating ninja: {}:{}:{}'.format(tu_style, toolchain, variant))
scons_to_ninja.GenerateNinjaFile(
[object_builder.env] + object_builder.child_envs,
dest_file='build.ninja')
for key, value in aliases.iteritems(): for key, value in aliases.iteritems():
env.Alias(key, value) env.Alias(key, value)

View File

@@ -0,0 +1,85 @@
# Copyright (c) 2014 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import atexit
import os
import sys
import SCons.Action
# This implements a Ninja backend for SCons. This allows SCons to be used
# as a Ninja file generator, similar to how Gyp will generate Ninja files.
#
# This is a way to bypass SCons's slow startup time. After running SCons
# to generate a Ninja file (which is fairly slow), you can rebuild targets
# quickly using Ninja, as long as the .scons files haven't changed.
#
# The implementation is fairly hacky: It hooks PRINT_CMD_LINE_FUNC to
# discover the build commands that SCons would normally run.
#
# A cleaner implementation would traverse the node graph instead.
# Traversing the node graph is itself straightforward, but finding the
# command associated with a node is not -- I couldn't figure out how to do
# that.
# This is necessary to handle SCons's "variant dir" feature. The filename
# associated with a Scons node can be ambiguous: it might come from the
# build dir or the source dir.
def GetRealNode(node):
src = node.srcnode()
if src.stat() is not None:
return src
return node
def GenerateNinjaFile(envs, dest_file):
# Tell SCons not to run any commands, just report what would be run.
for e in envs:
e.SetOption('no_exec', True)
# Tell SCons that everything needs rebuilding.
e.Decider(lambda dependency, target, prev_ni: True)
# Use a list to ensure that the output is ordered deterministically.
node_list = []
node_map = {}
def CustomCommandPrinter(cmd, targets, source, env):
assert len(targets) == 1, len(targets)
node = targets[0]
# There can sometimes be multiple commands per target (e.g. ar+ranlib).
# We must collect these together to output a single Ninja rule.
if node not in node_map:
node_list.append(node)
node_map.setdefault(node, []).append(cmd)
for e in envs:
e.Append(PRINT_CMD_LINE_FUNC=CustomCommandPrinter)
def WriteFile():
dest_temp = '%s.tmp' % dest_file
ninja_fh = open(dest_temp, 'w')
ninja_fh.write("""\
# Generated by scons_to_ninja.py
# Generic rule for handling any command.
rule cmd
command = $cmd
# NaCl overrides SCons's Install() step to create hard links, for speed.
# To coexist with that, we must remove the file before copying, otherwise
# cp complains the source and dest "are the same file". We also create
# hard links here (with -l) for speed.
rule install
command = rm -f $out && cp -l $in $out
""")
for node in node_list:
dest_path = node.get_path()
cmds = node_map[node]
deps = [GetRealNode(dep).get_path() for dep in node.all_children()]
action = node.builder.action
if type(action) == SCons.Action.FunctionAction:
funcname = action.function_name()
if funcname == 'installFunc':
assert len(deps) == 1, len(deps)
ninja_fh.write('\nbuild %s: install %s\n'
% (dest_path, ' '.join(deps)))
continue
else:
sys.stderr.write('Unknown FunctionAction, %r: skipping target %r\n'
% (funcname, dest_path))
continue
ninja_fh.write('\nbuild %s: cmd %s\n'
% (dest_path, ' '.join(deps)))
ninja_fh.write(' cmd = %s\n' % ' && '.join(cmds))
# Make the result file visible atomically.
os.rename(dest_temp, dest_file)
atexit.register(WriteFile)