mapnik/scons/scons-local-4.7.0/SCons/Tool/ninja/Methods.py

288 lines
11 KiB
Python
Vendored

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import os
import shlex
import textwrap
from typing import Optional
import SCons
from SCons.Subst import SUBST_CMD
from SCons.Tool.ninja import NINJA_CUSTOM_HANDLERS, NINJA_RULES, NINJA_POOLS
from SCons.Tool.ninja.Globals import __NINJA_RULE_MAPPING
from SCons.Tool.ninja.Utils import get_targets_sources, get_dependencies, get_order_only, get_outputs, get_inputs, \
get_rule, get_path, generate_command, get_command_env, get_comstr
from SCons.Util.sctyping import ExecutorType
def register_custom_handler(env, name, handler) -> None:
"""Register a custom handler for SCons function actions."""
env[NINJA_CUSTOM_HANDLERS][name] = handler
def register_custom_rule_mapping(env, pre_subst_string, rule) -> None:
"""Register a function to call for a given rule."""
SCons.Tool.ninja.Globals.__NINJA_RULE_MAPPING[pre_subst_string] = rule
def register_custom_rule(env, rule, command, description: str="", deps=None, pool=None, use_depfile: bool=False, use_response_file: bool=False, response_file_content: str="$rspc") -> None:
"""Allows specification of Ninja rules from inside SCons files."""
rule_obj = {
"command": command,
"description": description if description else f"{rule} $out",
}
if use_depfile:
rule_obj["depfile"] = os.path.join(get_path(env['NINJA_DIR']), '$out.depfile')
if deps is not None:
rule_obj["deps"] = deps
if pool is not None:
rule_obj["pool"] = pool
if use_response_file:
rule_obj["rspfile"] = "$out.rsp"
rule_obj["rspfile_content"] = response_file_content
env[NINJA_RULES][rule] = rule_obj
def register_custom_pool(env, pool, size) -> None:
"""Allows the creation of custom Ninja pools"""
env[NINJA_POOLS][pool] = size
def set_build_node_callback(env, node, callback) -> None:
if not node.is_conftest():
node.attributes.ninja_build_callback = callback
def get_generic_shell_command(env, node, action, targets, sources, executor: Optional[ExecutorType] = None):
return (
"GENERATED_CMD",
{
"cmd": generate_command(env, node, action, targets, sources, executor=executor),
"env": get_command_env(env, targets, sources),
},
# Since this function is a rule mapping provider, it must return a list of dependencies,
# and usually this would be the path to a tool, such as a compiler, used for this rule.
# However this function is to generic to be able to reliably extract such deps
# from the command, so we return a placeholder empty list. It should be noted that
# generally this function will not be used solely and is more like a template to generate
# the basics for a custom provider which may have more specific options for a provider
# function for a custom NinjaRuleMapping.
[]
)
def CheckNinjaCompdbExpand(env, context):
""" Configure check testing if ninja's compdb can expand response files"""
# TODO: When would this be false?
context.Message('Checking if ninja compdb can expand response files... ')
ret, output = context.TryAction(
action='ninja -f $SOURCE -t compdb -x CMD_RSP > $TARGET',
extension='.ninja',
text=textwrap.dedent("""
rule CMD_RSP
command = $cmd @$out.rsp > fake_output.txt
description = Building $out
rspfile = $out.rsp
rspfile_content = $rspc
build fake_output.txt: CMD_RSP fake_input.txt
cmd = echo
pool = console
rspc = "test"
"""))
result = '@fake_output.txt.rsp' not in output
context.Result(result)
return result
def get_command(env, node, action): # pylint: disable=too-many-branches
"""Get the command to execute for node."""
if node.env:
sub_env = node.env
else:
sub_env = env
executor = node.get_executor()
tlist, slist = get_targets_sources(node)
# Generate a real CommandAction
if isinstance(action, SCons.Action.CommandGeneratorAction):
# pylint: disable=protected-access
action = action._generate(tlist, slist, sub_env, SUBST_CMD, executor=executor)
variables = {}
# since we will check the ninja rule map for this command str, we must make sure
# its string so its hashable.
comstr = str(get_comstr(sub_env, action, tlist, slist))
if not comstr:
return None
provider = __NINJA_RULE_MAPPING.get(comstr, get_generic_shell_command)
rule, variables, provider_deps = provider(sub_env, node, action, tlist, slist, executor=executor)
if node.get_env().get('NINJA_FORCE_SCONS_BUILD'):
rule = 'TEMPLATE'
# Get the dependencies for all targets
implicit = list({dep for tgt in tlist for dep in get_dependencies(tgt)})
# Now add in the other dependencies related to the command,
# e.g. the compiler binary. The ninja rule can be user provided so
# we must do some validation to resolve the dependency path for ninja.
for provider_dep in provider_deps:
provider_dep = sub_env.subst(provider_dep)
if not provider_dep:
continue
# If the tool is a node, then SCons will resolve the path later, if its not
# a node then we assume it generated from build and make sure it is existing.
if isinstance(provider_dep, SCons.Node.Node) or os.path.exists(provider_dep):
implicit.append(provider_dep)
continue
# in some case the tool could be in the local directory and be supplied without the ext
# such as in windows, so append the executable suffix and check.
prog_suffix = sub_env.get('PROGSUFFIX', '')
provider_dep_ext = provider_dep if provider_dep.endswith(prog_suffix) else provider_dep + prog_suffix
if os.path.exists(provider_dep_ext):
implicit.append(provider_dep_ext)
continue
# Many commands will assume the binary is in the path, so
# we accept this as a possible input from a given command.
provider_dep_abspath = sub_env.WhereIs(provider_dep) or sub_env.WhereIs(provider_dep, path=os.environ["PATH"])
if provider_dep_abspath:
implicit.append(provider_dep_abspath)
continue
# Possibly these could be ignore and the build would still work, however it may not always
# rebuild correctly, so we hard stop, and force the user to fix the issue with the provided
# ninja rule.
raise Exception("Could not resolve path for %s dependency on node '%s'" % (provider_dep, node))
ninja_build = {
"order_only": get_order_only(node),
"outputs": get_outputs(node),
"inputs": get_inputs(node),
"implicit": implicit,
"rule": get_rule(node, rule),
"variables": variables,
}
# Don't use sub_env here because we require that NINJA_POOL be set
# on a per-builder call basis to prevent accidental strange
# behavior like env['NINJA_POOL'] = 'console' and sub_env can be
# the global Environment object if node.env is None.
# Example:
#
# Allowed:
#
# env.Command("ls", NINJA_POOL="ls_pool")
#
# Not allowed and ignored:
#
# env["NINJA_POOL"] = "ls_pool"
# env.Command("ls")
#
# TODO: Why not alloe env['NINJA_POOL'] ? (bdbaddog)
if node.env and node.env.get("NINJA_POOL", None) is not None:
ninja_build["pool"] = node.env["NINJA_POOL"]
return ninja_build
def gen_get_response_file_command(env, rule, tool, tool_is_dynamic: bool=False, custom_env={}):
"""Generate a response file command provider for rule name."""
# If win32 using the environment with a response file command will cause
# ninja to fail to create the response file. Additionally since these rules
# generally are not piping through cmd.exe /c any environment variables will
# make CreateProcess fail to start.
#
# On POSIX we can still set environment variables even for compile
# commands so we do so.
use_command_env = not env["PLATFORM"] == "win32"
if "$" in tool:
tool_is_dynamic = True
def get_response_file_command(env, node, action, targets, sources, executor: Optional[ExecutorType] = None):
if hasattr(action, "process"):
cmd_list, _, _ = action.process(targets, sources, env, executor=executor)
cmd_list = [str(c).replace("$", "$$") for c in cmd_list[0]]
else:
command = generate_command(
env, node, action, targets, sources, executor=executor
)
cmd_list = shlex.split(command)
if tool_is_dynamic:
tool_command = env.subst(
tool, target=targets, source=sources, executor=executor
)
else:
tool_command = tool
try:
# Add 1 so we always keep the actual tool inside of cmd
tool_idx = cmd_list.index(tool_command) + 1
except ValueError:
raise Exception(
"Could not find tool {} in {} generated from {}".format(
tool, cmd_list, get_comstr(env, action, targets, sources)
)
)
cmd, rsp_content = cmd_list[:tool_idx], cmd_list[tool_idx:]
# Canonicalize the path to have forward (posix style) dir sep characters.
if os.altsep:
rsp_content = [rsp_content_item.replace(os.sep, os.altsep) for rsp_content_item in rsp_content]
rsp_content = ['"' + rsp_content_item + '"' for rsp_content_item in rsp_content]
rsp_content = " ".join(rsp_content)
variables = {"rspc": rsp_content, rule: cmd}
if use_command_env:
variables["env"] = get_command_env(env, targets, sources)
for key, value in custom_env.items():
variables["env"] += env.subst(
"export %s=%s;" % (key, value), target=targets, source=sources, executor=executor
) + " "
if node.get_env().get('NINJA_FORCE_SCONS_BUILD'):
ret_rule = 'TEMPLATE'
else:
if len(' '.join(cmd_list)) < env.get('MAXLINELENGTH', 2048):
ret_rule = rule
else:
ret_rule = rule + '_RSP'
return ret_rule, variables, [tool_command]
return get_response_file_command