Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
aignas committed Sep 11, 2024
1 parent 7d42a93 commit d7a90fa
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 108 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ A brief description of the categories of changes:
* (toolchains) {obj}`py_cc_toolchain.libs` and {obj}`PyCcToolchainInfo.libs` is
optional. This is to support situations where only the Python headers are
available.
* (toolchains) {flag}`//python/config_settings:python_version` now is not
restricted to the python versions that are hard-coded in
`//python:versions.bzl` in `rules_python` and can take any value. Under `bzlmod`
we are restricting the values based on the toolchains that are registered by
all modules using {bzl:obj}`python.toolchain` tag class.

### Fixed
* (whl_library): Remove `--no-index` and add `--no-build-isolation` to the
Expand Down
6 changes: 3 additions & 3 deletions python/config_settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ load(
"WhlLibcFlag",
"define_pypi_internal_flags",
)
load(":config_settings.bzl", "construct_config_settings")
load(":config_settings.bzl", "alias_config_settings")

filegroup(
name = "distribution",
Expand All @@ -25,8 +25,8 @@ filegroup(
visibility = ["//python:__pkg__"],
)

construct_config_settings(
name = "construct_config_settings",
alias_config_settings(
name = "alias_config_settings",
)

string_flag(
Expand Down
4 changes: 4 additions & 0 deletions python/config_settings/config_settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

load(
"//python/private:config_settings.bzl",
_VERSION_FLAG_VALUES = "VERSION_FLAG_VALUES",
_alias_config_settings = "alias_config_settings",
_construct_config_settings = "construct_config_settings",
_is_python_config_setting = "is_python_config_setting",
)
Expand All @@ -28,3 +30,5 @@ is_python_config_setting = _is_python_config_setting

# This is exposed for usage in rules_python only.
construct_config_settings = _construct_config_settings
alias_config_settings = _alias_config_settings
VERSION_FLAG_VALUES = _VERSION_FLAG_VALUES
17 changes: 16 additions & 1 deletion python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ bzl_library(
deps = [
"//python:versions_bzl",
"@bazel_skylib//lib:selects",
":python_version_flag_bzl",
":python_version_flag_values_bzl",
],
)

Expand Down Expand Up @@ -163,8 +165,9 @@ bzl_library(
srcs = ["pythons_hub.bzl"],
deps = [
":full_version_bzl",
":py_toolchain_suite_bzl",
"//python:versions_bzl",
":python_version_flag_values_bzl",
":text_util_bzl",
],
)

Expand Down Expand Up @@ -267,6 +270,18 @@ bzl_library(
],
)

bzl_library(
name = "python_version_flag_bzl",
srcs = ["python_version_flag.bzl"],
deps = ["@bazel_skylib//rules:common_settings"],
)

bzl_library(
name = "python_version_flag_values_bzl",
srcs = ["python_version_flag_values.bzl"],
deps = ["//python:versions_bzl"],
)

bzl_library(
name = "py_wheel_bzl",
srcs = ["py_wheel.bzl"],
Expand Down
144 changes: 46 additions & 98 deletions python/private/config_settings.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,60 +16,15 @@
"""

load("@bazel_skylib//lib:selects.bzl", "selects")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("//python:versions.bzl", "MINOR_MAPPING", "TOOL_VERSIONS")
load("//python:versions.bzl", "TOOL_VERSIONS")
load(":python_version_flag.bzl", "python_version_flag")
load(":python_version_flag_values.bzl", "flag_values")

_PYTHON_VERSION_FLAG = str(Label("//python/config_settings:python_version"))

def _ver_key(s):
major, _, s = s.partition(".")
minor, _, s = s.partition(".")
micro, _, s = s.partition(".")
return (int(major), int(minor), int(micro))
VERSION_FLAG_VALUES = flag_values(TOOL_VERSIONS.keys())

def _flag_values(python_versions):
"""Construct a map of python_version to a list of toolchain values.
This mapping maps the concept of a config setting to a list of compatible toolchain versions.
For using this in the code, the VERSION_FLAG_VALUES should be used instead.
Args:
python_versions: list of strings; all X.Y.Z python versions
Returns:
A `map[str, list[str]]`. Each key is a python_version flag value. Each value
is a list of the python_version flag values that should match when for the
`key`. For example:
```
"3.8" -> ["3.8", "3.8.1", "3.8.2", ..., "3.8.19"] # All 3.8 versions
"3.8.2" -> ["3.8.2"] # Only 3.8.2
"3.8.19" -> ["3.8.19", "3.8"] # The latest version should also match 3.8 so
as when the `3.8` toolchain is used we just use the latest `3.8` toolchain.
this makes the `select("is_python_3.8.19")` work no matter how the user
specifies the latest python version to use.
```
"""
ret = {}

for micro_version in sorted(python_versions, key = _ver_key):
minor_version, _, _ = micro_version.rpartition(".")

# This matches the raw flag value, e.g. --//python/config_settings:python_version=3.8
# It's private because matching the concept of e.g. "3.8" value is done
# using the `is_python_X.Y` config setting group, which is aware of the
# minor versions that could match instead.
ret.setdefault(minor_version, [minor_version]).append(micro_version)

# 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
default_micro_version = MINOR_MAPPING[minor_version]
ret[micro_version] = [micro_version, minor_version] if default_micro_version == micro_version else [micro_version]

return ret

VERSION_FLAG_VALUES = _flag_values(TOOL_VERSIONS.keys())

def is_python_config_setting(name, *, python_version, reuse_conditions = None, **kwargs):
def is_python_config_setting(name, *, python_version, version_flag_values, reuse_conditions = None, **kwargs):
"""Create a config setting for matching 'python_version' configuration flag.
This function is mainly intended for internal use within the `whl_library` and `pip_parse`
Expand All @@ -90,6 +45,8 @@ def is_python_config_setting(name, *, python_version, reuse_conditions = None, *
python_version: The python_version to be passed in the `flag_values` in the
`config_setting`. Depending on the version, the matching python version list
can be as described above.
version_flag_values: dict[str, str] The mapping between the version
string and the full version string.
reuse_conditions: A dict of version to version label for which we should
reuse config_setting targets instead of creating them from scratch. This
is useful when using is_python_config_setting multiple times in the
Expand All @@ -99,10 +56,10 @@ def is_python_config_setting(name, *, python_version, reuse_conditions = None, *
if python_version not in name:
fail("The name '{}' must have the python version '{}' in it".format(name, python_version))

if python_version not in VERSION_FLAG_VALUES:
fail("The 'python_version' must be known to 'rules_python', choose from the values: {}".format(VERSION_FLAG_VALUES.keys()))
if python_version not in version_flag_values:
fail("The 'python_version' must be known to 'rules_python', choose from the values: {}".format(version_flag_values.keys()))

python_versions = VERSION_FLAG_VALUES[python_version]
python_versions = version_flag_values[python_version]
extra_flag_values = kwargs.pop("flag_values", {})
if _PYTHON_VERSION_FLAG in extra_flag_values:
fail("Cannot set '{}' in the flag values".format(_PYTHON_VERSION_FLAG))
Expand All @@ -125,7 +82,7 @@ def is_python_config_setting(name, *, python_version, reuse_conditions = None, *
}
match_any = list(create_config_settings.keys())
for version, condition in reuse_conditions.items():
if len(VERSION_FLAG_VALUES[version]) == 1:
if len(version_flag_values[version]) == 1:
match_any.append(condition)
continue

Expand Down Expand Up @@ -161,74 +118,65 @@ def is_python_config_setting(name, *, python_version, reuse_conditions = None, *
visibility = kwargs.get("visibility", []),
)

def construct_config_settings(name = None): # buildifier: disable=function-docstring
def alias_config_settings(name = None, values = VERSION_FLAG_VALUES.keys()): # buildifier: disable=function-docstring
"""alias the config settings to the current package.
Args:
name: A dummy value
values: {type}`list[str]` The list of version values.
"""
for name in [
"python_version",
] + [
"is_python_version_unset" if not version else ("is_python_" + version)
for version in [""] + values
]:
native.alias(
name = name,
actual = "@pythons_hub//:" + name,
visibility = ["//visibility:public"],
)

def construct_config_settings(name = None, default_python_version = "", version_flag_values = None, **kwargs): # buildifier: disable=function-docstring
"""Create a 'python_version' config flag and construct all config settings used in rules_python.
This mainly includes the targets that are used in the toolchain and pip hub
repositories that only match on the 'python_version' flag values.
Args:
name(str): A dummy name value that is no-op for now.
default_python_version: {obj}`str` the default value for python_version_flag.
version_flag_values: dict[str, str] The mapping between the version
string and the full version string.
**kwargs: Further kwargs passed to the rules (e.g. visibility)
"""
_python_version_flag(
values = [] if not version_flag_values else ([""] + list(version_flag_values.keys()))

python_version_flag(
name = "python_version",
# TODO: The default here should somehow match the MODULE config. Until
# then, use the empty string to indicate an unknown version. This
# also prevents version-unaware targets from inadvertently matching
# a select condition when they shouldn't.
build_setting_default = "",
values = [""] + VERSION_FLAG_VALUES.keys(),
visibility = ["//visibility:public"],
build_setting_default = default_python_version,
values = values,
**kwargs,
)

native.config_setting(
name = "is_python_version_unset",
flag_values = {
Label("//python/config_settings:python_version"): "",
},
visibility = ["//visibility:public"],
**kwargs,
)

for version, matching_versions in VERSION_FLAG_VALUES.items():
version_flag_values = version_flag_values or VERSION_FLAG_VALUES
for version, matching_versions in version_flag_values.items():
is_python_config_setting(
name = "is_python_{}".format(version),
python_version = version,
version_flag_values = version_flag_values,
reuse_conditions = {
v: native.package_relative_label("is_python_{}".format(v))
for v in matching_versions
if v != version
},
visibility = ["//visibility:public"],
**kwargs,
)

def _python_version_flag_impl(ctx):
value = ctx.build_setting_value
if value not in ctx.attr.values:
fail((
"Invalid --python_version value: {actual}\nAllowed values {allowed}"
).format(
actual = value,
allowed = ", ".join(sorted(ctx.attr.values)),
))

return [
# BuildSettingInfo is the original provider returned, so continue to
# return it for compatibility
BuildSettingInfo(value = value),
# FeatureFlagInfo is returned so that config_setting respects the value
# as returned by this rule instead of as originally seen on the command
# line.
# It is also for Google compatibility, which expects the FeatureFlagInfo
# provider.
config_common.FeatureFlagInfo(value = value),
]

_python_version_flag = rule(
implementation = _python_version_flag_impl,
build_setting = config.string(flag = True),
attrs = {
"values": attr.string_list(
doc = "Allowed values.",
),
},
)
10 changes: 10 additions & 0 deletions python/private/python_repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ load(
)
load("//python/private/pypi:deps.bzl", "pypi_deps")
load(":auth.bzl", "get_auth")
load(":pythons_hub.bzl", "hub_repo")
load(":bzlmod_enabled.bzl", "BZLMOD_ENABLED")
load(":coverage_deps.bzl", "coverage_dep")
load(":full_version.bzl", "full_version")
Expand All @@ -54,6 +55,15 @@ def py_repositories():
internal_config_repo,
name = "rules_python_internal",
)
maybe(
hub_repo,
name = "pythons_hub",
default_python_version = "",
toolchain_prefixes = [],
toolchain_python_versions = [],
toolchain_set_python_version_constraints = [],
toolchain_user_repository_names = [],
)
http_archive(
name = "bazel_skylib",
sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
Expand Down
48 changes: 48 additions & 0 deletions python/private/python_version_flag.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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.

"""This module is used to construct the config settings in the BUILD file in this same package.
"""

load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")

def _python_version_flag_impl(ctx):
value = ctx.build_setting_value
if ctx.attr.values and value not in ctx.attr.values:
fail((
"Invalid --python_version value: {actual}\nAllowed values {allowed}"
).format(
actual = value,
allowed = ", ".join(sorted(ctx.attr.values)),
))

return [
# BuildSettingInfo is the original provider returned, so continue to
# return it for compatibility
BuildSettingInfo(value = value),
# FeatureFlagInfo is returned so that config_setting respects the value
# as returned by this rule instead of as originally seen on the command
# line.
# It is also for Google compatibility, which expects the FeatureFlagInfo
# provider.
config_common.FeatureFlagInfo(value = value),
]

python_version_flag = rule(
implementation = _python_version_flag_impl,
build_setting = config.string(flag = True),
attrs = {
"values": attr.string_list(doc = "Allowed values."),
},
)
Loading

0 comments on commit d7a90fa

Please sign in to comment.