|  | 
|  | 1 | +# Copyright 2024 The Bazel Authors. All rights reserved. | 
|  | 2 | +# | 
|  | 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 4 | +# you may not use this file except in compliance with the License. | 
|  | 5 | +# You may obtain a copy of the License at | 
|  | 6 | +# | 
|  | 7 | +#    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 8 | +# | 
|  | 9 | +# Unless required by applicable law or agreed to in writing, software | 
|  | 10 | +# distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 12 | +# See the License for the specific language governing permissions and | 
|  | 13 | +# limitations under the License. | 
|  | 14 | +"""Run a py_binary with altered config settings in an sh_test. | 
|  | 15 | +
 | 
|  | 16 | +This facilitates verify running binaries with different configuration settings | 
|  | 17 | +without the overhead of a bazel-in-bazel integration test. | 
|  | 18 | +""" | 
|  | 19 | + | 
|  | 20 | +load("//python:py_binary.bzl", "py_binary") | 
|  | 21 | + | 
|  | 22 | +def _perform_transition_impl(input_settings, attr): | 
|  | 23 | +    settings = dict(input_settings) | 
|  | 24 | +    settings["//command_line_option:build_python_zip"] = attr.build_python_zip | 
|  | 25 | +    if attr.bootstrap_impl: | 
|  | 26 | +        settings["//python/config_settings:bootstrap_impl"] = attr.bootstrap_impl | 
|  | 27 | +    return settings | 
|  | 28 | + | 
|  | 29 | +_perform_transition = transition( | 
|  | 30 | +    implementation = _perform_transition_impl, | 
|  | 31 | +    inputs = [ | 
|  | 32 | +        "//python/config_settings:bootstrap_impl", | 
|  | 33 | +    ], | 
|  | 34 | +    outputs = [ | 
|  | 35 | +        "//command_line_option:build_python_zip", | 
|  | 36 | +        "//python/config_settings:bootstrap_impl", | 
|  | 37 | +    ], | 
|  | 38 | +) | 
|  | 39 | + | 
|  | 40 | +def _transition_impl(ctx): | 
|  | 41 | +    default_info = ctx.attr.target[DefaultInfo] | 
|  | 42 | +    exe_ext = default_info.files_to_run.executable.extension | 
|  | 43 | +    if exe_ext: | 
|  | 44 | +        exe_ext = "." + exe_ext | 
|  | 45 | +    exe_name = ctx.label.name + exe_ext | 
|  | 46 | + | 
|  | 47 | +    executable = ctx.actions.declare_file(exe_name) | 
|  | 48 | +    ctx.actions.symlink(output = executable, target_file = default_info.files_to_run.executable) | 
|  | 49 | + | 
|  | 50 | +    default_outputs = [executable] | 
|  | 51 | + | 
|  | 52 | +    # todo: could probably check target.owner vs src.owner to check if it should | 
|  | 53 | +    # be symlinked or included as-is | 
|  | 54 | +    # For simplicity of implementation, we're assuming the target being run is | 
|  | 55 | +    # py_binary-like. In order for Windows to work, we need to make sure the | 
|  | 56 | +    # file that the .exe launcher runs (the .zip or underlying non-exe | 
|  | 57 | +    # executable) is a sibling of the .exe file with the same base name. | 
|  | 58 | +    for src in default_info.files.to_list(): | 
|  | 59 | +        if src.extension in ("", "zip"): | 
|  | 60 | +            ext = ("." if src.extension else "") + src.extension | 
|  | 61 | +            output = ctx.actions.declare_file(ctx.label.name + ext) | 
|  | 62 | +            ctx.actions.symlink(output = output, target_file = src) | 
|  | 63 | +            default_outputs.append(output) | 
|  | 64 | + | 
|  | 65 | +    return [ | 
|  | 66 | +        DefaultInfo( | 
|  | 67 | +            executable = executable, | 
|  | 68 | +            files = depset(default_outputs), | 
|  | 69 | +            runfiles = default_info.default_runfiles, | 
|  | 70 | +        ), | 
|  | 71 | +        testing.TestEnvironment( | 
|  | 72 | +            environment = ctx.attr.env, | 
|  | 73 | +        ), | 
|  | 74 | +    ] | 
|  | 75 | + | 
|  | 76 | +transition_binary = rule( | 
|  | 77 | +    implementation = _transition_impl, | 
|  | 78 | +    attrs = { | 
|  | 79 | +        "bootstrap_impl": attr.string(), | 
|  | 80 | +        "build_python_zip": attr.string(default = "auto"), | 
|  | 81 | +        "env": attr.string_dict(), | 
|  | 82 | +        "target": attr.label(executable = True, cfg = "target"), | 
|  | 83 | +        "_allowlist_function_transition": attr.label( | 
|  | 84 | +            default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | 
|  | 85 | +        ), | 
|  | 86 | +    }, | 
|  | 87 | +    cfg = _perform_transition, | 
|  | 88 | +    executable = True, | 
|  | 89 | +) | 
|  | 90 | + | 
|  | 91 | +def sh_py_run_test(*, name, sh_src, py_src, **kwargs): | 
|  | 92 | +    bin_name = "_{}_bin".format(name) | 
|  | 93 | +    native.sh_test( | 
|  | 94 | +        name = name, | 
|  | 95 | +        srcs = [sh_src], | 
|  | 96 | +        data = [bin_name], | 
|  | 97 | +        deps = [ | 
|  | 98 | +            "@bazel_tools//tools/bash/runfiles", | 
|  | 99 | +        ], | 
|  | 100 | +        env = { | 
|  | 101 | +            "BIN_RLOCATION": "$(rlocationpath {})".format(bin_name), | 
|  | 102 | +        }, | 
|  | 103 | +    ) | 
|  | 104 | + | 
|  | 105 | +    transition_binary( | 
|  | 106 | +        name = bin_name, | 
|  | 107 | +        tags = ["manual"], | 
|  | 108 | +        target = "_{}_plain_bin".format(name), | 
|  | 109 | +        **kwargs | 
|  | 110 | +    ) | 
|  | 111 | + | 
|  | 112 | +    py_binary( | 
|  | 113 | +        name = "_{}_plain_bin".format(name), | 
|  | 114 | +        srcs = [py_src], | 
|  | 115 | +        main = py_src, | 
|  | 116 | +        tags = ["manual"], | 
|  | 117 | +    ) | 
0 commit comments