예제 #1
0
def get_command_env(env):
    """
    Return a string that sets the environment for any environment variables that
    differ between the OS environment and the SCons command ENV.

    It will be compatible with the default shell of the operating system.
    """
    try:
        return env["NINJA_ENV_VAR_CACHE"]
    except KeyError:
        pass

    # Scan the ENV looking for any keys which do not exist in
    # os.environ or differ from it. We assume if it's a new or
    # differing key from the process environment then it's
    # important to pass down to commands in the Ninja file.
    ENV = get_default_ENV(env)
    scons_specified_env = {
        key: value
        for key, value in ENV.items()
        # TODO: Remove this filter, unless there's a good reason to keep. SCons's behavior shouldn't depend on shell's.
        if key not in os.environ or os.environ.get(key, None) != value
    }

    windows = env["PLATFORM"] == "win32"
    command_env = ""
    for key, value in scons_specified_env.items():
        # Ensure that the ENV values are all strings:
        if is_List(value):
            # If the value is a list, then we assume it is a
            # path list, because that's a pretty common list-like
            # value to stick in an environment variable:
            value = flatten_sequence(value)
            value = joinpath(map(str, value))
        else:
            # If it isn't a string or a list, then we just coerce
            # it to a string, which is the proper way to handle
            # Dir and File instances and will produce something
            # reasonable for just about everything else:
            value = str(value)

        if windows:
            command_env += "set '{}={}' && ".format(key, value)
        else:
            # We address here *only* the specific case that a user might have
            # an environment variable which somehow gets included and has
            # spaces in the value. These are escapes that Ninja handles. This
            # doesn't make builds on paths with spaces (Ninja and SCons issues)
            # nor expanding response file paths with spaces (Ninja issue) work.
            value = value.replace(r' ', r'$ ')
            command_env += "export {}='{}';".format(key, value)

    env["NINJA_ENV_VAR_CACHE"] = command_env
    return command_env
예제 #2
0
파일: ninja.py 프로젝트: halsonhe/mongo
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()
    if executor is not None:
        tlist = executor.get_all_targets()
        slist = executor.get_all_sources()
    else:
        if hasattr(node, "target_peers"):
            tlist = node.target_peers
        else:
            tlist = [node]
        slist = node.sources

    # Retrieve the repository file for all sources
    slist = [rfile(s) for s in slist]

    # Get the dependencies for all targets
    implicit = list({dep for tgt in tlist for dep in get_dependencies(tgt)})

    # Generate a real CommandAction
    if isinstance(action, SCons.Action.CommandGeneratorAction):
        # pylint: disable=protected-access
        action = action._generate(tlist, slist, sub_env, 1, executor=executor)

    rule = "CMD"

    # Actions like CommandAction have a method called process that is
    # used by SCons to generate the cmd_line they need to run. So
    # check if it's a thing like CommandAction and call it if we can.
    if hasattr(action, "process"):
        cmd_list, _, _ = action.process(tlist,
                                        slist,
                                        sub_env,
                                        executor=executor)

        # Despite being having "list" in it's name this member is not
        # actually a list. It's the pre-subst'd string of the command. We
        # use it to determine if the command we generated needs to use a
        # custom Ninja rule. By default this redirects CC/CXX commands to
        # CMD_W_DEPS but the user can inject custom Ninja rules and tie
        # them to commands by using their pre-subst'd string.
        rule = __NINJA_RULE_MAPPING.get(action.cmd_list, "CMD")

        cmd = _string_from_cmd_list(cmd_list[0])
    else:
        # Anything else works with genstring, this is most commonly hit by
        # ListActions which essentially call process on all of their
        # commands and concatenate it for us.
        genstring = action.genstring(tlist, slist, sub_env)

        # Detect if we have a custom rule for this
        # "ListActionCommandAction" type thing.
        rule = __NINJA_RULE_MAPPING.get(genstring, "CMD")

        if executor is not None:
            cmd = sub_env.subst(genstring, executor=executor)
        else:
            cmd = sub_env.subst(genstring, target=tlist, source=slist)

        # Since we're only enabling Ninja for developer builds right
        # now we skip all Manifest related work on Windows as it's not
        # necessary. We shouldn't have gotten here but on Windows
        # SCons has a ListAction which shows as a
        # CommandGeneratorAction for linking. That ListAction ends
        # with a FunctionAction (embedManifestExeCheck,
        # embedManifestDllCheck) that simply say "does
        # target[0].manifest exist?" if so execute the real command
        # action underlying me, otherwise do nothing.
        #
        # Eventually we'll want to find a way to translate this to
        # Ninja but for now, and partially because the existing Ninja
        # generator does so, we just disable it all together.
        cmd = cmd.replace("\n", " && ").strip()
        if env["PLATFORM"] == "win32" and ("embedManifestExeCheck" in cmd
                                           or "embedManifestDllCheck" in cmd):
            cmd = " && ".join(cmd.split(" && ")[0:-1])

        if cmd.endswith("&&"):
            cmd = cmd[0:-2].strip()

    outputs = get_outputs(node)
    if rule == "CMD_W_DEPS":
        # When using a depfile Ninja can only have a single output but
        # SCons will usually have emitted an output for every thing a
        # command will create because it's caching is much more
        # complex than Ninja's. This includes things like DWO
        # files. Here we make sure that Ninja only ever sees one
        # target when using a depfile. It will still have a command
        # that will create all of the outputs but most targets don't
        # depend direclty on DWO files and so this assumption is
        # safe to make.
        outputs = outputs[0:1]

    command_env = getattr(node.attributes, "NINJA_ENV_ENV", "")
    # If win32 and rule == CMD_W_DEPS then we don't want to calculate
    # an environment for this command. It's a compile command and
    # compiledb doesn't support shell syntax on Windows. We need the
    # shell syntax to use environment variables on Windows so we just
    # skip this platform / rule combination to keep the compiledb
    # working.
    #
    # On POSIX we can still set environment variables even for compile
    # commands so we do so.
    if not command_env and not (env["PLATFORM"] == "win32"
                                and rule == "CMD_W_DEPS"):
        ENV = get_default_ENV(sub_env)

        # This is taken wholesale from SCons/Action.py
        #
        # Ensure that the ENV values are all strings:
        for key, value in ENV.items():
            if not is_String(value):
                if is_List(value):
                    # If the value is a list, then we assume it is a
                    # path list, because that's a pretty common list-like
                    # value to stick in an environment variable:
                    value = flatten_sequence(value)
                    value = os.pathsep.join(map(str, value))
                else:
                    # If it isn't a string or a list, then we just coerce
                    # it to a string, which is the proper way to handle
                    # Dir and File instances and will produce something
                    # reasonable for just about everything else:
                    value = str(value)

            if env["PLATFORM"] == "win32":
                command_env += "set '{}={}' && ".format(key, value)
            else:
                command_env += "{}={} ".format(key, value)

        setattr(node.attributes, "NINJA_ENV_ENV", command_env)

    ninja_build = {
        "outputs": outputs,
        "implicit": implicit,
        "rule": rule,
        "variables": {
            "cmd": command_env + cmd
        },
    }

    # 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")
    #
    if node.env and node.env.get("NINJA_POOL", None) is not None:
        ninja_build["pool"] = node.env["NINJA_POOL"]

    return ninja_build
예제 #3
0
def ninja_builder(env, target, source):
    """Generate a build.ninja for source."""
    if not isinstance(source, list):
        source = [source]
    if not isinstance(target, list):
        target = [target]

    # Only build if we're building the alias that's actually been
    # asked for so we don't generate a bunch of build.ninja files.
    alias_name = str(source[0])
    ninja_alias_name = "{}{}".format(NINJA_ALIAS_PREFIX, alias_name)
    if ninja_alias_name not in COMMAND_LINE_TARGETS:
        return 0
    else:
        print("Generating:", str(target[0]))

    ninja_syntax_file = env[NINJA_SYNTAX]
    if isinstance(ninja_syntax_file, str):
        ninja_syntax_file = env.File(ninja_syntax_file).get_abspath()
    ninja_syntax_mod_dir = os.path.dirname(ninja_syntax_file)
    sys.path.append(ninja_syntax_mod_dir)
    ninja_syntax_mod_name = os.path.basename(ninja_syntax_file)
    ninja_syntax = importlib.import_module(
        ninja_syntax_mod_name.replace(".py", ""))

    generated_build_ninja = target[0].get_abspath()

    content = io.StringIO()
    ninja = ninja_syntax.Writer(content, width=100)
    ninja.comment(
        "Generated by scons for target {}. DO NOT EDIT.".format(alias_name))
    ninja.comment("vim: set textwidth=0 :")
    ninja.comment("-*- eval: (auto-fill-mode -1) -*-")

    ninja.variable("PYTHON", sys.executable)
    ninja.variable(
        "SCONS_INVOCATION",
        "{} {} -Q $out".format(
            sys.executable,
            " ".join([
                arg for arg in sys.argv

                # TODO: the "ninja" in arg part of this should be
                # handled better, as it stands this is for
                # filtering out MongoDB's ninja flag
                if arg not in COMMAND_LINE_TARGETS and "ninja" not in arg
            ]),
        ),
    )

    ninja.variable(
        "SCONS_INVOCATION_W_TARGETS",
        "$SCONS_INVOCATION {}".format(" ".join(
            [arg for arg in sys.argv if arg in COMMAND_LINE_TARGETS])),
    )

    ninja.rule(
        "SCONS",
        command="$SCONS_INVOCATION $out",
        description="SCONSGEN $out",

        # restat
        #    if present, causes Ninja to re-stat the command’s outputs
        #    after execution of the command. Each output whose
        #    modification time the command did not change will be
        #    treated as though it had never needed to be built. This
        #    may cause the output’s reverse dependencies to be removed
        #    from the list of pending build actions.
        restat=1,
    )

    for rule in RULES:
        ninja.rule(rule, **RULES[rule])

    for var, val in VARS.items():
        ninja.variable(var, val)

    environ = get_default_ENV(env)
    for var, val in environ.items():
        ninja.variable(var, val)

    for src in source:
        handle_build_node(env, ninja, src, ninja_file=generated_build_ninja)

    ninja.build(
        generated_build_ninja,
        rule="SCONS",
        implicit=glob("**SCons*", recursive=True),
    )

    ninja.build(
        "scons-invocation",
        rule="cmd",
        pool="console",
        variables={"cmd": "echo $SCONS_INVOCATION_W_TARGETS"},
    )

    ninja.default(pathable(source[0]).get_path())

    with open(generated_build_ninja, "w", encoding="utf-8") as build_ninja:
        build_ninja.write(content.getvalue())

    build_ninja_file = env.File("#build.ninja")
    build_ninja_path = build_ninja_file.get_abspath()
    print("Linking build.ninja to {}".format(generated_build_ninja))
    if os.path.islink(build_ninja_path):
        os.remove(build_ninja_path)
    os.symlink(generated_build_ninja, build_ninja_path)
    return 0