NinjaState.py (scons-4.2.0) | : | NinjaState.py (SCons-4.3.0) | ||
---|---|---|---|---|
skipping to change at line 27 | skipping to change at line 27 | |||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY | |||
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | |||
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
import io | import io | |||
import os | import os | |||
import shutil | ||||
import sys | import sys | |||
from os.path import splitext | from os.path import splitext | |||
from tempfile import NamedTemporaryFile | ||||
import ninja | import ninja | |||
import SCons | import SCons | |||
from SCons.Script import COMMAND_LINE_TARGETS | from SCons.Script import COMMAND_LINE_TARGETS | |||
from SCons.Util import is_List | from SCons.Util import is_List | |||
from SCons.Errors import InternalError | from SCons.Errors import InternalError | |||
from .Globals import COMMAND_TYPES, NINJA_RULES, NINJA_POOLS, \ | from .Globals import COMMAND_TYPES, NINJA_RULES, NINJA_POOLS, \ | |||
NINJA_CUSTOM_HANDLERS | NINJA_CUSTOM_HANDLERS | |||
from .Rules import _install_action_function, _mkdir_action_function, _lib_symlin k_action_function, _copy_action_function | from .Rules import _install_action_function, _mkdir_action_function, _lib_symlin k_action_function, _copy_action_function | |||
from .Utils import get_path, alias_to_ninja_build, generate_depfile, ninja_noop, get_order_only, \ | from .Utils import get_path, alias_to_ninja_build, generate_depfile, ninja_noop, get_order_only, \ | |||
get_outputs, get_inputs, get_dependencies, get_rule, get_command_env | get_outputs, get_inputs, get_dependencies, get_rule, get_command_env, to_esc aped_list | |||
from .Methods import get_command | from .Methods import get_command | |||
# pylint: disable=too-many-instance-attributes | # pylint: disable=too-many-instance-attributes | |||
class NinjaState: | class NinjaState: | |||
"""Maintains state of Ninja build system as it's translated from SCons.""" | """Maintains state of Ninja build system as it's translated from SCons.""" | |||
def __init__(self, env, ninja_file, writer_class): | def __init__(self, env, ninja_file, ninja_syntax): | |||
self.env = env | self.env = env | |||
self.ninja_file = ninja_file | self.ninja_file = ninja_file | |||
self.ninja_bin_path = env.get('NINJA') | self.ninja_bin_path = env.get('NINJA') | |||
if not self.ninja_bin_path: | if not self.ninja_bin_path: | |||
# default to using ninja installed with python module | # default to using ninja installed with python module | |||
ninja_bin = 'ninja.exe' if env["PLATFORM"] == "win32" else 'ninja' | ninja_bin = 'ninja.exe' if env["PLATFORM"] == "win32" else 'ninja' | |||
self.ninja_bin_path = os.path.abspath(os.path.join( | self.ninja_bin_path = os.path.abspath(os.path.join( | |||
ninja.__file__, | ninja.__file__, | |||
os.pardir, | os.pardir, | |||
'data', | 'data', | |||
'bin', | 'bin', | |||
ninja_bin)) | ninja_bin)) | |||
if not os.path.exists(self.ninja_bin_path): | if not os.path.exists(self.ninja_bin_path): | |||
# couldn't find it, just give the bin name and hope | # couldn't find it, just give the bin name and hope | |||
# its in the path later | # its in the path later | |||
self.ninja_bin_path = ninja_bin | self.ninja_bin_path = ninja_bin | |||
self.writer_class = writer_class | self.writer_class = ninja_syntax.Writer | |||
self.__generated = False | self.__generated = False | |||
self.translator = SConsToNinjaTranslator(env) | self.translator = SConsToNinjaTranslator(env) | |||
self.generated_suffixes = env.get("NINJA_GENERATED_SOURCE_SUFFIXES", []) | self.generated_suffixes = env.get("NINJA_GENERATED_SOURCE_SUFFIXES", []) | |||
# List of generated builds that will be written at a later stage | # List of generated builds that will be written at a later stage | |||
self.builds = dict() | self.builds = dict() | |||
# List of targets for which we have generated a build. This | # List of targets for which we have generated a build. This | |||
# allows us to take multiple Alias nodes as sources and to not | # allows us to take multiple Alias nodes as sources and to not | |||
# fail to build if they have overlapping targets. | # fail to build if they have overlapping targets. | |||
self.built = set() | self.built = set() | |||
# SCons sets this variable to a function which knows how to do | # SCons sets this variable to a function which knows how to do | |||
# shell quoting on whatever platform it's run on. Here we use it | # shell quoting on whatever platform it's run on. Here we use it | |||
# to make the SCONS_INVOCATION variable properly quoted for things | # to make the SCONS_INVOCATION variable properly quoted for things | |||
# like CCFLAGS | # like CCFLAGS | |||
escape = env.get("ESCAPE", lambda x: x) | scons_escape = env.get("ESCAPE", lambda x: x) | |||
# if SCons was invoked from python, we expect the first arg to be the sc ons.py | # if SCons was invoked from python, we expect the first arg to be the sc ons.py | |||
# script, otherwise scons was invoked from the scons script | # script, otherwise scons was invoked from the scons script | |||
python_bin = '' | python_bin = '' | |||
if os.path.basename(sys.argv[0]) == 'scons.py': | if os.path.basename(sys.argv[0]) == 'scons.py': | |||
python_bin = escape(sys.executable) | python_bin = ninja_syntax.escape(scons_escape(sys.executable)) | |||
self.variables = { | self.variables = { | |||
"COPY": "cmd.exe /c 1>NUL copy" if sys.platform == "win32" else "cp" , | "COPY": "cmd.exe /c 1>NUL copy" if sys.platform == "win32" else "cp" , | |||
"SCONS_INVOCATION": '{} {} --disable-ninja __NINJA_NO=1 $out'.format ( | "SCONS_INVOCATION": '{} {} --disable-ninja __NINJA_NO=1 $out'.format ( | |||
python_bin, | python_bin, | |||
" ".join( | " ".join( | |||
[escape(arg) for arg in sys.argv if arg not in COMMAND_LINE_ TARGETS] | [ninja_syntax.escape(scons_escape(arg)) for arg in sys.argv if arg not in COMMAND_LINE_TARGETS] | |||
), | ), | |||
), | ), | |||
"SCONS_INVOCATION_W_TARGETS": "{} {}".format( | "SCONS_INVOCATION_W_TARGETS": "{} {} NINJA_DISABLE_AUTO_RUN=1".forma | |||
python_bin, " ".join([escape(arg) for arg in sys.argv]) | t( | |||
python_bin, " ".join([ | ||||
ninja_syntax.escape(scons_escape(arg)) | ||||
for arg in sys.argv | ||||
if arg != 'NINJA_DISABLE_AUTO_RUN=1']) | ||||
), | ), | |||
# This must be set to a global default per: | # This must be set to a global default per: | |||
# https://ninja-build.org/manual.html#_deps | # https://ninja-build.org/manual.html#_deps | |||
# English Visual Studio will have the default below, | # English Visual Studio will have the default below, | |||
# otherwise the user can define the variable in the first environmen t | # otherwise the user can define the variable in the first environmen t | |||
# that initialized ninja tool | # that initialized ninja tool | |||
"msvc_deps_prefix": env.get("NINJA_MSVC_DEPS_PREFIX", "Note: includi ng file:") | "msvc_deps_prefix": env.get("NINJA_MSVC_DEPS_PREFIX", "Note: includi ng file:") | |||
} | } | |||
self.rules = { | self.rules = { | |||
skipping to change at line 210 | skipping to change at line 215 | |||
# targets. But since SCons is doing it's own up to | # targets. But since SCons is doing it's own up to | |||
# date-ness checks it may only update say one of | # date-ness checks it may only update say one of | |||
# them. Restat will find out which of the multiple | # them. Restat will find out which of the multiple | |||
# build targets did actually change then only rebuild | # build targets did actually change then only rebuild | |||
# those targets which depend specifically on that | # those targets which depend specifically on that | |||
# output. | # output. | |||
"restat": 1, | "restat": 1, | |||
}, | }, | |||
"REGENERATE": { | "REGENERATE": { | |||
"command": "$SCONS_INVOCATION_W_TARGETS", | "command": "$SCONS_INVOCATION_W_TARGETS", | |||
"description": "Regenerating $out", | "description": "Regenerating $self", | |||
"generator": 1, | "generator": 1, | |||
"depfile": os.path.join(get_path(env['NINJA_DIR']), '$out.depfil | ||||
e'), | ||||
# 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 | ||||
# that we need for SCons to behave correctly when | ||||
# regenerating Ninja | ||||
"pool": "console", | "pool": "console", | |||
# Again we restat in case Ninja thought the | ||||
# build.ninja should be regenerated but SCons knew | ||||
# better. | ||||
"restat": 1, | "restat": 1, | |||
}, | }, | |||
} | } | |||
if env['PLATFORM'] == 'darwin' and env['AR'] == 'ar': | if env['PLATFORM'] == 'darwin' and env.get('AR', "") == 'ar': | |||
self.rules["AR"] = { | self.rules["AR"] = { | |||
"command": "rm -f $out && $env$AR $rspc", | "command": "rm -f $out && $env$AR $rspc", | |||
"description": "Archiving $out", | "description": "Archiving $out", | |||
"pool": "local_pool", | "pool": "local_pool", | |||
} | } | |||
num_jobs = self.env.get('NINJA_MAX_JOBS', self.env.GetOption("num_jobs") ) | num_jobs = self.env.get('NINJA_MAX_JOBS', self.env.GetOption("num_jobs") ) | |||
self.pools = { | self.pools = { | |||
"local_pool": num_jobs, | "local_pool": num_jobs, | |||
"install_pool": num_jobs / 2, | "install_pool": num_jobs / 2, | |||
skipping to change at line 301 | skipping to change at line 297 | |||
return | return | |||
self.rules.update(self.env.get(NINJA_RULES, {})) | self.rules.update(self.env.get(NINJA_RULES, {})) | |||
self.pools.update(self.env.get(NINJA_POOLS, {})) | self.pools.update(self.env.get(NINJA_POOLS, {})) | |||
content = io.StringIO() | content = io.StringIO() | |||
ninja = self.writer_class(content, width=100) | ninja = self.writer_class(content, width=100) | |||
ninja.comment("Generated by scons. DO NOT EDIT.") | ninja.comment("Generated by scons. DO NOT EDIT.") | |||
ninja.variable("builddir", get_path(self.env['NINJA_DIR'])) | ninja.variable("builddir", get_path(self.env.Dir(self.env['NINJA_DIR']). path)) | |||
for pool_name, size in self.pools.items(): | for pool_name, size in self.pools.items(): | |||
ninja.pool(pool_name, min(self.env.get('NINJA_MAX_JOBS', size), size )) | ninja.pool(pool_name, min(self.env.get('NINJA_MAX_JOBS', size), size )) | |||
for var, val in self.variables.items(): | for var, val in self.variables.items(): | |||
ninja.variable(var, val) | ninja.variable(var, val) | |||
for rule, kwargs in self.rules.items(): | for rule, kwargs in self.rules.items(): | |||
if self.env.get('NINJA_MAX_JOBS') is not None and 'pool' not in kwar gs: | if self.env.get('NINJA_MAX_JOBS') is not None and 'pool' not in kwar gs: | |||
kwargs['pool'] = 'local_pool' | kwargs['pool'] = 'local_pool' | |||
skipping to change at line 449 | skipping to change at line 445 | |||
if is_List(cur_val): | if is_List(cur_val): | |||
new_val += cur_val | new_val += cur_val | |||
else: | else: | |||
new_val.append(cur_val) | new_val.append(cur_val) | |||
template_builds[agg_key] = new_val | template_builds[agg_key] = new_val | |||
# Collect all other keys | # Collect all other keys | |||
template_builds.update(template_builder) | template_builds.update(template_builder) | |||
if template_builds.get("outputs", []): | if template_builds.get("outputs", []): | |||
# Try to clean up any dependency cycles. If we are passing an | ||||
# ouptut node to SCons, it will build any dependencys if ninja | ||||
# has not already. | ||||
for output in template_builds.get("outputs", []): | ||||
inputs = template_builds.get('inputs') | ||||
if inputs and output in inputs: | ||||
inputs.remove(output) | ||||
implicits = template_builds.get('implicit') | ||||
if implicits and output in implicits: | ||||
implicits.remove(output) | ||||
ninja.build(**template_builds) | ninja.build(**template_builds) | |||
# We have to glob the SCons files here to teach the ninja file | # We have to glob the SCons files here to teach the ninja file | |||
# how to regenerate itself. We'll never see ourselves in the | # how to regenerate itself. We'll never see ourselves in the | |||
# DAG walk so we can't rely on action_to_ninja_build to | # DAG walk so we can't rely on action_to_ninja_build to | |||
# generate this rule even though SCons should know we're | # generate this rule even though SCons should know we're | |||
# dependent on SCons files. | # 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(self.ninja_file).path | ninja_file_path = self.env.File(self.ninja_file).path | |||
generate_depfile( | regenerate_deps = to_escaped_list(self.env, self.env['NINJA_REGENERATE_D | |||
self.env, | EPS']) | |||
ninja_file_path, | ||||
self.env['NINJA_REGENERATE_DEPS'] | ||||
) | ||||
ninja.build( | ninja.build( | |||
ninja_file_path, | ninja_file_path, | |||
rule="REGENERATE", | rule="REGENERATE", | |||
implicit=[__file__], | implicit=regenerate_deps, | |||
variables={ | ||||
"self": ninja_file_path, | ||||
} | ||||
) | ||||
ninja.build( | ||||
regenerate_deps, | ||||
rule="phony", | ||||
variables={ | ||||
"self": ninja_file_path, | ||||
} | ||||
) | ) | |||
# If we ever change the name/s of the rules that include | # If we ever change the name/s of the rules that include | |||
# compile commands (i.e. something like CC) we will need to | # compile commands (i.e. something like CC) we will need to | |||
# update this build to reflect that complete list. | # update this build to reflect that complete list. | |||
ninja.build( | ninja.build( | |||
"compile_commands.json", | "compile_commands.json", | |||
rule="CMD", | rule="CMD", | |||
pool="console", | pool="console", | |||
implicit=[str(self.ninja_file)], | implicit=[str(self.ninja_file)], | |||
skipping to change at line 516 | skipping to change at line 523 | |||
for tgt in SCons.Script.DEFAULT_TARGETS | for tgt in SCons.Script.DEFAULT_TARGETS | |||
if get_path(tgt) in self.built | if get_path(tgt) in self.built | |||
] | ] | |||
# If we found an overlap between SCons's list of default | # If we found an overlap between SCons's list of default | |||
# targets and the targets we created ninja builds for then use | # targets and the targets we created ninja builds for then use | |||
# those as ninja's default as well. | # those as ninja's default as well. | |||
if scons_default_targets: | if scons_default_targets: | |||
ninja.default(" ".join(scons_default_targets)) | ninja.default(" ".join(scons_default_targets)) | |||
with open(str(self.ninja_file), "w") as build_ninja: | with NamedTemporaryFile(delete=False, mode='w') as temp_ninja_file: | |||
build_ninja.write(content.getvalue()) | temp_ninja_file.write(content.getvalue()) | |||
shutil.move(temp_ninja_file.name, ninja_file_path) | ||||
self.__generated = True | self.__generated = True | |||
class SConsToNinjaTranslator: | class SConsToNinjaTranslator: | |||
"""Translates SCons Actions into Ninja build objects.""" | """Translates SCons Actions into Ninja build objects.""" | |||
def __init__(self, env): | def __init__(self, env): | |||
self.env = env | self.env = env | |||
self.func_handlers = { | self.func_handlers = { | |||
# Skip conftest builders | # Skip conftest builders | |||
"_createSource": ninja_noop, | "_createSource": ninja_noop, | |||
# SCons has a custom FunctionAction that just makes sure the | # SCons has a custom FunctionAction that just makes sure the | |||
# target isn't static. We let the commands that ninja runs do | # target isn't static. We let the commands that ninja runs do | |||
# this check for us. | # this check for us. | |||
"SharedFlagChecker": ninja_noop, | "SharedFlagChecker": ninja_noop, | |||
# The install builder is implemented as a function action. | # The install builder is implemented as a function action. | |||
# TODO: use command action #3573 | # TODO: use command action #3573 | |||
"installFunc": _install_action_function, | "installFunc": _install_action_function, | |||
"MkdirFunc": _mkdir_action_function, | "MkdirFunc": _mkdir_action_function, | |||
"Mkdir": _mkdir_action_function, | ||||
"LibSymlinksActionFunction": _lib_symlink_action_function, | "LibSymlinksActionFunction": _lib_symlink_action_function, | |||
"Copy": _copy_action_function | "Copy": _copy_action_function | |||
} | } | |||
self.loaded_custom = False | self.loaded_custom = False | |||
# pylint: disable=too-many-return-statements | # pylint: disable=too-many-return-statements | |||
def action_to_ninja_build(self, node, action=None): | def action_to_ninja_build(self, node, action=None): | |||
"""Generate build arguments dictionary for node.""" | """Generate build arguments dictionary for node.""" | |||
End of changes. 20 change blocks. | ||||
38 lines changed or deleted | 48 lines changed or added |