feat(toolchain): support specifying x.y versions in transitions (#1720)
This is inspired by how rules_go is registering their toolchains.
Their toolchains have multiple `target_settings` values. This
allows for a simpler passing of `X.Y` version to the `py_binary` and
`py_test` rules and does not strictly require us to provide the APIs
that pass the full python version value as the closure. This is only
possible because #1555 introduced working aliases and now we can also
have this.
Summary:
- refactor: move the toolchain_def to starlark as opposed to templating
- refactor: move the version setting as well
- feat: support matching on X.Y versions
- feat: X.Y.Z will match if X.Y is used as python_version flag and the
MINOR_MAPPING has `"X.Y": "X.Y.Z"`.
- test: add tests checking the generated config settings.
- doc: add an example of how we could use the transition files directly
See
https://github.com/bazelbuild/rules_go/blob/master/go/private/go_toolchain.bzl#L181
---------
Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/.bazelrc b/.bazelrc
index eb00db9..c911ea5 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -4,8 +4,8 @@
# (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it)
# To update these lines, execute
# `bazel run @rules_bazel_integration_test//tools:update_deleted_packages`
-build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered
-query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered
+build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered
+query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/dupe_requirements,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/pip_repository_entry_points,tests/integration/py_cc_toolchain_registered
test --test_output=errors
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5416787..a137e9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,12 @@
* New Python versions available: `3.11.7`, `3.12.1` using
https://github.com/indygreg/python-build-standalone/releases/tag/20240107.
+* (toolchain) Allow setting `x.y` as the `python_version` parameter in
+ the version-aware `py_binary` and `py_test` rules. This allows users to
+ use the same rule import for testing with specific Python versions and
+ rely on toolchain configuration and how the latest version takes precedence
+ if e.g. `3.8` is selected. That also simplifies `.bazelrc` for any users
+ that set the default `python_version` string flag in that way.
## 0.29.0 - 2024-01-22
[0.29.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.29.0
diff --git a/examples/bzlmod/tests/BUILD.bazel b/examples/bzlmod/tests/BUILD.bazel
index ce7079c..9f7aa1b 100644
--- a/examples/bzlmod/tests/BUILD.bazel
+++ b/examples/bzlmod/tests/BUILD.bazel
@@ -2,6 +2,8 @@
load("@python_versions//3.11:defs.bzl", py_binary_3_11 = "py_binary", py_test_3_11 = "py_test")
load("@python_versions//3.9:defs.bzl", py_binary_3_9 = "py_binary", py_test_3_9 = "py_test")
load("@rules_python//python:defs.bzl", "py_binary", "py_test")
+load("@rules_python//python:versions.bzl", "MINOR_MAPPING")
+load("@rules_python//python/config_settings:transition.bzl", py_versioned_binary = "py_binary", py_versioned_test = "py_test")
py_binary(
name = "version_default",
@@ -27,6 +29,13 @@
main = "version.py",
)
+py_versioned_binary(
+ name = "version_3_10_versioned",
+ srcs = ["version.py"],
+ main = "version.py",
+ python_version = "3.10",
+)
+
# This is a work in progress and the commented
# tests will not work until we can support
# multiple pips with bzlmod.
@@ -52,6 +61,28 @@
deps = ["//libs/my_lib"],
)
+py_versioned_test(
+ name = "my_lib_versioned_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ python_version = "3.10",
+ deps = select(
+ {
+ "@rules_python//python/config_settings:is_python_" + MINOR_MAPPING["3.10"]: ["//libs/my_lib"],
+ },
+ no_match_error = """\
+This test is failing to find dependencies and it seems that the is_python_{version}
+does not match the transitioned configuration of python-version 3.10. Please
+look at the
+
+ @rules_python//python/config_settings:config_settings.bzl
+
+to fix any bugs.""".format(
+ version = MINOR_MAPPING["3.10"],
+ ),
+ ),
+)
+
py_test(
name = "version_default_test",
srcs = ["version_test.py"],
@@ -73,6 +104,14 @@
main = "version_test.py",
)
+py_versioned_test(
+ name = "version_versioned_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.10"},
+ main = "version_test.py",
+ python_version = "3.10",
+)
+
py_test_3_11(
name = "version_3_11_test",
srcs = ["version_test.py"],
@@ -104,6 +143,19 @@
main = "cross_version_test.py",
)
+py_versioned_test(
+ name = "version_3_10_takes_3_9_subprocess_test_2",
+ srcs = ["cross_version_test.py"],
+ data = [":version_3_9"],
+ env = {
+ "SUBPROCESS_VERSION_CHECK": "3.9",
+ "SUBPROCESS_VERSION_PY_BINARY": "$(rootpath :version_3_9)",
+ "VERSION_CHECK": "3.10",
+ },
+ main = "cross_version_test.py",
+ python_version = "3.10",
+)
+
sh_test(
name = "version_test_binary_default",
srcs = ["version_test.sh"],
diff --git a/python/config_settings/config_settings.bzl b/python/config_settings/config_settings.bzl
index f1e142c..9e6bbd6 100644
--- a/python/config_settings/config_settings.bzl
+++ b/python/config_settings/config_settings.bzl
@@ -17,6 +17,7 @@
load("@bazel_skylib//lib:selects.bzl", "selects")
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
+load("//python:versions.bzl", "MINOR_MAPPING")
def construct_config_settings(name, python_versions):
"""Constructs a set of configs for all Python versions.
@@ -36,6 +37,8 @@
minor_to_micro_versions.setdefault(minor, []).append(micro_version)
allowed_flag_values.append(micro_version)
+ allowed_flag_values.extend(list(minor_to_micro_versions))
+
string_flag(
name = "python_version",
# TODO: The default here should somehow match the MODULE config. Until
@@ -59,14 +62,44 @@
)
matches_minor_version_names = [equals_minor_version_name]
+ default_micro_version = MINOR_MAPPING[minor_version]
+
for micro_version in micro_versions:
is_micro_version_name = "is_python_" + micro_version
+ if default_micro_version != micro_version:
+ native.config_setting(
+ name = is_micro_version_name,
+ flag_values = {":python_version": micro_version},
+ visibility = ["//visibility:public"],
+ )
+ matches_minor_version_names.append(is_micro_version_name)
+ continue
+
+ # Ensure that is_python_3.9.8 is matched if python_version is set
+ # to 3.9 if MINOR_MAPPING points to 3.9.8
+ equals_micro_name = "_python_version_flag_equals_" + micro_version
native.config_setting(
- name = is_micro_version_name,
+ name = equals_micro_name,
flag_values = {":python_version": micro_version},
+ )
+
+ # An alias pointing to an underscore-prefixed config_setting_group
+ # is used because config_setting_group creates
+ # `is_{minor}_N` targets, which are easily confused with the
+ # `is_{minor}.{micro}` (dot) targets.
+ selects.config_setting_group(
+ name = "_" + is_micro_version_name,
+ match_any = [
+ equals_micro_name,
+ equals_minor_version_name,
+ ],
+ )
+ native.alias(
+ name = is_micro_version_name,
+ actual = "_" + is_micro_version_name,
visibility = ["//visibility:public"],
)
- matches_minor_version_names.append(is_micro_version_name)
+ matches_minor_version_names.append(equals_micro_name)
# This is prefixed with an underscore to prevent confusion due to how
# config_setting_group is implemented and how our micro-version targets
diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel
index 25937f0..1017b09 100644
--- a/python/private/BUILD.bazel
+++ b/python/private/BUILD.bazel
@@ -162,6 +162,14 @@
)
bzl_library(
+ name = "py_toolchain_suite_bzl",
+ srcs = ["py_toolchain_suite.bzl"],
+ deps = [
+ "@bazel_skylib//lib:selects",
+ ],
+)
+
+bzl_library(
name = "py_wheel_bzl",
srcs = ["py_wheel.bzl"],
visibility = ["//:__subpackages__"],
diff --git a/python/private/bzlmod/BUILD.bazel b/python/private/bzlmod/BUILD.bazel
index a312922..b636cca 100644
--- a/python/private/bzlmod/BUILD.bazel
+++ b/python/private/bzlmod/BUILD.bazel
@@ -73,6 +73,6 @@
deps = [
"//python:versions_bzl",
"//python/private:full_version_bzl",
- "//python/private:toolchains_repo_bzl",
+ "//python/private:py_toolchain_suite_bzl",
],
)
diff --git a/python/private/bzlmod/pythons_hub.bzl b/python/private/bzlmod/pythons_hub.bzl
index 3889e13..b002956 100644
--- a/python/private/bzlmod/pythons_hub.bzl
+++ b/python/private/bzlmod/pythons_hub.bzl
@@ -20,7 +20,6 @@
"//python/private:toolchains_repo.bzl",
"get_host_os_arch",
"get_host_platform",
- "get_repository_name",
"python_toolchain_build_file_content",
)
@@ -31,6 +30,7 @@
_HUB_BUILD_FILE_TEMPLATE = """\
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("@@{rules_python}//python/private:py_toolchain_suite.bzl", "py_toolchain_suite")
bzl_library(
name = "interpreters_bzl",
@@ -56,19 +56,24 @@
if not _have_same_length(python_versions, set_python_version_constraints, user_repository_names):
fail("all lists must have the same length")
- rules_python = get_repository_name(workspace_location)
-
# Iterate over the length of python_versions and call
# build the toolchain content by calling python_toolchain_build_file_content
- toolchains = "\n".join([python_toolchain_build_file_content(
- prefix = prefixes[i],
- python_version = full_version(python_versions[i]),
- set_python_version_constraint = set_python_version_constraints[i],
- user_repository_name = user_repository_names[i],
- rules_python = rules_python,
- ) for i in range(len(python_versions))])
+ toolchains = "\n".join(
+ [
+ python_toolchain_build_file_content(
+ prefix = prefixes[i],
+ python_version = full_version(python_versions[i]),
+ set_python_version_constraint = set_python_version_constraints[i],
+ user_repository_name = user_repository_names[i],
+ )
+ for i in range(len(python_versions))
+ ],
+ )
- return _HUB_BUILD_FILE_TEMPLATE.format(toolchains = toolchains)
+ return _HUB_BUILD_FILE_TEMPLATE.format(
+ toolchains = toolchains,
+ rules_python = workspace_location.workspace_name,
+ )
_interpreters_bzl_template = """
INTERPRETER_LABELS = {{
diff --git a/python/private/py_toolchain_suite.bzl b/python/private/py_toolchain_suite.bzl
new file mode 100644
index 0000000..5b4b6f8
--- /dev/null
+++ b/python/private/py_toolchain_suite.bzl
@@ -0,0 +1,81 @@
+# Copyright 2022 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Create the toolchain defs in a BUILD.bazel file."""
+
+load("@bazel_skylib//lib:selects.bzl", "selects")
+
+_py_toolchain_type = Label("@bazel_tools//tools/python:toolchain_type")
+_py_cc_toolchain_type = Label("//python/cc:toolchain_type")
+
+def py_toolchain_suite(*, prefix, user_repository_name, python_version, set_python_version_constraint, **kwargs):
+ """For internal use only.
+
+ Args:
+ prefix: Prefix for toolchain target names.
+ user_repository_name: The name of the user repository.
+ python_version: The full (X.Y.Z) version of the interpreter.
+ set_python_version_constraint: True or False as a string.
+ **kwargs: extra args passed to the `toolchain` calls.
+
+ """
+
+ # We have to use a String value here because bzlmod is passing in a
+ # string as we cannot have list of bools in build rule attribues.
+ # This if statement does not appear to work unless it is in the
+ # toolchain file.
+ if set_python_version_constraint == "True":
+ major_minor, _, _ = python_version.rpartition(".")
+
+ selects.config_setting_group(
+ name = prefix + "_version_setting",
+ match_any = [
+ Label("//python/config_settings:is_python_%s" % v)
+ for v in [
+ major_minor,
+ python_version,
+ ]
+ ],
+ visibility = ["//visibility:private"],
+ )
+ target_settings = [prefix + "_version_setting"]
+ elif set_python_version_constraint == "False":
+ target_settings = []
+ else:
+ fail(("Invalid set_python_version_constraint value: got {} {}, wanted " +
+ "either the string 'True' or the string 'False'; " +
+ "(did you convert bool to string?)").format(
+ type(set_python_version_constraint),
+ repr(set_python_version_constraint),
+ ))
+
+ native.toolchain(
+ name = "{prefix}_toolchain".format(prefix = prefix),
+ toolchain = "@{user_repository_name}//:python_runtimes".format(
+ user_repository_name = user_repository_name,
+ ),
+ toolchain_type = _py_toolchain_type,
+ target_settings = target_settings,
+ **kwargs
+ )
+
+ native.toolchain(
+ name = "{prefix}_py_cc_toolchain".format(prefix = prefix),
+ toolchain = "@{user_repository_name}//:py_cc_toolchain".format(
+ user_repository_name = user_repository_name,
+ ),
+ toolchain_type = _py_cc_toolchain_type,
+ target_settings = target_settings,
+ **kwargs
+ )
diff --git a/python/private/toolchains_repo.bzl b/python/private/toolchains_repo.bzl
index c7b6178..17ef1a3 100644
--- a/python/private/toolchains_repo.bzl
+++ b/python/private/toolchains_repo.bzl
@@ -40,8 +40,7 @@
prefix,
python_version,
set_python_version_constraint,
- user_repository_name,
- rules_python):
+ user_repository_name):
"""Creates the content for toolchain definitions for a build file.
Args:
@@ -51,58 +50,29 @@
have the Python version constraint added as a requirement for
matching the toolchain, "False" if not.
user_repository_name: names for the user repos
- rules_python: rules_python label
Returns:
build_content: Text containing toolchain definitions
"""
- if set_python_version_constraint == "True":
- constraint = "{rules_python}//python/config_settings:is_python_{python_version}".format(
- rules_python = rules_python,
- python_version = python_version,
- )
- target_settings = '["{}"]'.format(constraint)
- elif set_python_version_constraint == "False":
- target_settings = "[]"
- else:
- fail(("Invalid set_python_version_constraint value: got {} {}, wanted " +
- "either the string 'True' or the string 'False'; " +
- "(did you convert bool to string?)").format(
- type(set_python_version_constraint),
- repr(set_python_version_constraint),
- ))
# We create a list of toolchain content from iterating over
# the enumeration of PLATFORMS. We enumerate PLATFORMS in
# order to get us an index to increment the increment.
- return "".join([
- """
-toolchain(
- name = "{prefix}{platform}_toolchain",
+ return "\n\n".join([
+ """\
+py_toolchain_suite(
+ user_repository_name = "{user_repository_name}_{platform}",
+ prefix = "{prefix}{platform}",
target_compatible_with = {compatible_with},
- target_settings = {target_settings},
- toolchain = "@{user_repository_name}_{platform}//:python_runtimes",
- toolchain_type = "@bazel_tools//tools/python:toolchain_type",
-)
-
-toolchain(
- name = "{prefix}{platform}_py_cc_toolchain",
- target_compatible_with = {compatible_with},
- target_settings = {target_settings},
- toolchain = "@{user_repository_name}_{platform}//:py_cc_toolchain",
- toolchain_type = "@rules_python//python/cc:toolchain_type",
-
-)
-""".format(
+ python_version = "{python_version}",
+ set_python_version_constraint = "{set_python_version_constraint}",
+)""".format(
compatible_with = meta.compatible_with,
platform = platform,
- # We have to use a String value here because bzlmod is passing in a
- # string as we cannot have list of bools in build rule attribues.
- # This if statement does not appear to work unless it is in the
- # toolchain file.
- target_settings = target_settings,
+ set_python_version_constraint = set_python_version_constraint,
user_repository_name = user_repository_name,
prefix = prefix,
+ python_version = python_version,
)
for platform, meta in PLATFORMS.items()
])
@@ -116,17 +86,17 @@
# python_register_toolchains macro so you don't normally need to interact with
# these targets.
-"""
+load("@{rules_python}//python/private:py_toolchain_suite.bzl", "py_toolchain_suite")
- # Get the repository name
- rules_python = get_repository_name(rctx.attr._rules_python_workspace)
+""".format(
+ rules_python = rctx.attr._rules_python_workspace.workspace_name,
+ )
toolchains = python_toolchain_build_file_content(
prefix = "",
python_version = rctx.attr.python_version,
set_python_version_constraint = str(rctx.attr.set_python_version_constraint),
user_repository_name = rctx.attr.user_repository_name,
- rules_python = rules_python,
)
rctx.file("BUILD.bazel", build_content + toolchains)
diff --git a/tests/toolchains/config_settings/BUILD.bazel b/tests/toolchains/config_settings/BUILD.bazel
new file mode 100644
index 0000000..babd19f
--- /dev/null
+++ b/tests/toolchains/config_settings/BUILD.bazel
@@ -0,0 +1,54 @@
+# Copyright 2024 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+
+# Do bazel query for the following targets and put the results into a file
+_TARGETS = [
+ "is_python_3.12.0",
+ "is_python_3.12.1",
+ "_is_python_3.12.1",
+ "_python_version_flag_equals_3.12.1",
+ "_python_version_flag_equals_3.12",
+]
+
+[
+ genquery(
+ name = target,
+ expression = "//python/config_settings:" + target,
+ opts = ["--output=build"],
+ scope = ["//python/config_settings:" + target],
+ )
+ for target in _TARGETS
+]
+
+genrule(
+ name = "config_settings_query",
+ srcs = _TARGETS,
+ outs = ["config_settings_query.txt"],
+ # Strip comments that are specific to the host it is being run on and make the
+ # expectation output more maintainable.
+ cmd = "sed -e '/^#/d' -e '/^ generator_/d' $(SRCS) >$@",
+ target_compatible_with = select({
+ # We don't have sed available on Windows
+ "@platforms//os:windows": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ }),
+)
+
+diff_test(
+ name = "config_settings_test",
+ file1 = "want",
+ file2 = "config_settings_query",
+)
diff --git a/tests/toolchains/config_settings/want b/tests/toolchains/config_settings/want
new file mode 100644
index 0000000..0ecbd67
--- /dev/null
+++ b/tests/toolchains/config_settings/want
@@ -0,0 +1,27 @@
+config_setting(
+ name = "is_python_3.12.0",
+ visibility = ["//visibility:public"],
+ flag_values = {"//python/config_settings:python_version": "3.12.0"},
+)
+
+alias(
+ name = "is_python_3.12.1",
+ visibility = ["//visibility:public"],
+ actual = "//python/config_settings:_is_python_3.12.1",
+)
+
+alias(
+ name = "_is_python_3.12.1",
+ actual = select({"//python/config_settings:_python_version_flag_equals_3.12.1": "//python/config_settings:_python_version_flag_equals_3.12.1", "//conditions:default": "//python/config_settings:_python_version_flag_equals_3.12"}),
+)
+
+config_setting(
+ name = "_python_version_flag_equals_3.12.1",
+ flag_values = {"//python/config_settings:python_version": "3.12.1"},
+)
+
+config_setting(
+ name = "_python_version_flag_equals_3.12",
+ flag_values = {"//python/config_settings:python_version": "3.12"},
+)
+