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"""Starlark tests for PyRuntimeInfo provider."""
15
16load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
17load("@rules_testing//lib:test_suite.bzl", "test_suite")
18load("@rules_testing//lib:util.bzl", rt_util = "util")
19load("//python:py_runtime.bzl", "py_runtime")
20load("//python:py_runtime_pair.bzl", "py_runtime_pair")
21load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "TARGET_TOOLCHAIN_TYPE")  # buildifier: disable=bzl-visibility
22load("//python/private:util.bzl", "IS_BAZEL_7_OR_HIGHER")  # buildifier: disable=bzl-visibility
23load("//tests/support:support.bzl", "LINUX", "MAC", "PYTHON_VERSION")
24
25_LookupInfo = provider()  # buildifier: disable=provider-params
26
27def _lookup_toolchains_impl(ctx):
28    return [_LookupInfo(
29        target = ctx.toolchains[TARGET_TOOLCHAIN_TYPE],
30        exec = ctx.toolchains[EXEC_TOOLS_TOOLCHAIN_TYPE],
31    )]
32
33_lookup_toolchains = rule(
34    implementation = _lookup_toolchains_impl,
35    toolchains = [TARGET_TOOLCHAIN_TYPE, EXEC_TOOLS_TOOLCHAIN_TYPE],
36    attrs = {"_use_auto_exec_groups": attr.bool(default = True)},
37)
38
39def define_py_runtime(name, **kwargs):
40    py_runtime(
41        name = name + "_runtime",
42        **kwargs
43    )
44    py_runtime_pair(
45        name = name,
46        py3_runtime = name + "_runtime",
47    )
48
49_tests = []
50
51def _test_exec_matches_target_python_version(name):
52    rt_util.helper_target(
53        _lookup_toolchains,
54        name = name + "_subject",
55    )
56
57    # ==== Target toolchains =====
58
59    # This is never matched. It comes first to ensure the python version
60    # constraint is being respected.
61    native.toolchain(
62        name = "00_target_3.11_any",
63        toolchain_type = TARGET_TOOLCHAIN_TYPE,
64        toolchain = ":target_3.12_linux",
65        target_settings = ["//python/config_settings:is_python_3.11"],
66    )
67
68    # This is matched by the top-level target being built in what --platforms
69    # specifies.
70    native.toolchain(
71        name = "10_target_3.12_linux",
72        toolchain_type = TARGET_TOOLCHAIN_TYPE,
73        toolchain = ":target_3.12_linux",
74        target_compatible_with = ["@platforms//os:linux"],
75        target_settings = ["//python/config_settings:is_python_3.12"],
76    )
77
78    # This is matched when the exec config switches to the mac platform and
79    # then looks for a Python runtime for itself.
80    native.toolchain(
81        name = "15_target_3.12_mac",
82        toolchain_type = TARGET_TOOLCHAIN_TYPE,
83        toolchain = ":target_3.12_mac",
84        target_compatible_with = ["@platforms//os:macos"],
85        target_settings = ["//python/config_settings:is_python_3.12"],
86    )
87
88    # This is never matched. It's just here so that toolchains from the
89    # environment don't match.
90    native.toolchain(
91        name = "99_target_default",
92        toolchain_type = TARGET_TOOLCHAIN_TYPE,
93        toolchain = ":target_default",
94    )
95
96    # ==== Exec tools toolchains =====
97
98    # Register a 3.11 before to ensure it the python version is respected
99    native.toolchain(
100        name = "00_exec_3.11_any",
101        toolchain_type = EXEC_TOOLS_TOOLCHAIN_TYPE,
102        toolchain = ":exec_3.11_any",
103        target_settings = ["//python/config_settings:is_python_3.11"],
104    )
105
106    # Note that mac comes first. This is so it matches instead of linux
107    # We only ever look for mac ones, so no need to register others.
108    native.toolchain(
109        name = "10_exec_3.12_mac",
110        toolchain_type = EXEC_TOOLS_TOOLCHAIN_TYPE,
111        toolchain = ":exec_3.12",
112        exec_compatible_with = ["@platforms//os:macos"],
113        target_settings = ["//python/config_settings:is_python_3.12"],
114    )
115
116    # This is never matched. It's just here so that toolchains from the
117    # environment don't match.
118    native.toolchain(
119        name = "99_exec_default",
120        toolchain_type = EXEC_TOOLS_TOOLCHAIN_TYPE,
121        toolchain = ":exec_default",
122    )
123
124    analysis_test(
125        name = name,
126        target = name + "_subject",
127        impl = _test_exec_matches_target_python_version_impl,
128        config_settings = {
129            "//command_line_option:extra_execution_platforms": [str(MAC)],
130            "//command_line_option:extra_toolchains": ["//tests/exec_toolchain_matching:all"],
131            "//command_line_option:platforms": [str(LINUX)],
132            PYTHON_VERSION: "3.12",
133        },
134    )
135
136_tests.append(_test_exec_matches_target_python_version)
137
138def _test_exec_matches_target_python_version_impl(env, target):
139    target_runtime = target[_LookupInfo].target.py3_runtime
140    exec_runtime = target[_LookupInfo].exec.exec_tools.exec_interpreter[platform_common.ToolchainInfo].py3_runtime
141
142    env.expect.that_str(target_runtime.interpreter_path).equals("/linux/python3.12")
143    env.expect.that_str(exec_runtime.interpreter_path).equals("/mac/python3.12")
144
145    if IS_BAZEL_7_OR_HIGHER:
146        target_version = target_runtime.interpreter_version_info
147        exec_version = exec_runtime.interpreter_version_info
148
149        env.expect.that_bool(target_version == exec_version)
150
151def exec_toolchain_matching_test_suite(name):
152    test_suite(name = name, tests = _tests)
153