Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(toolchain): support freethreaded toolchains #2372

Merged
merged 27 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c53ada9
chore: add freethreaded interpreters to 3.13 def
aignas Oct 14, 2024
b182b52
fixup! chore: add freethreaded interpreters to 3.13 def
aignas Nov 4, 2024
02e84f7
update the hermetic_runtime_repo_setup to support free-threaded inter…
aignas Nov 5, 2024
0b57e4d
finish adding the freethreaded toolchains
aignas Nov 5, 2024
330b9f2
fixup
aignas Nov 5, 2024
503acab
revert the inclusion of 3.13 toolchain
aignas Nov 5, 2024
fe35320
Pass around the list of loaded platforms for each toolchain
aignas Nov 5, 2024
0cee209
Merge branch 'main' into exp/freethreading
aignas Nov 5, 2024
aef8f47
finish bumping the toolchains
aignas Nov 5, 2024
e58413c
buildifier
aignas Nov 5, 2024
0d6366a
fixup changelog
aignas Nov 5, 2024
1c54116
add a merge-conflict pre-commit-hook
aignas Nov 5, 2024
472cc40
comment: use enum
aignas Nov 6, 2024
c319b2b
comment: use a constant
aignas Nov 6, 2024
5fbbf9e
comment: remove sorting
aignas Nov 6, 2024
2d6e11a
fix: use the coverage enabled config setting
aignas Nov 6, 2024
465111e
comment: loaded_platform strings
aignas Nov 7, 2024
0f9954e
comment: pass loaded_platform structs
aignas Nov 7, 2024
daaa4de
use a function to define platforms
aignas Nov 7, 2024
66a102a
move the URL generation to a function instead of list comprehension
aignas Nov 7, 2024
b53073a
simplify
aignas Nov 7, 2024
ffe70cb
comment: use a constant
aignas Nov 7, 2024
b39ff67
Merge branch 'main' into exp/freethreading
aignas Nov 7, 2024
a90dd54
add docs
aignas Nov 7, 2024
c2663ba
chore: add coverage deps
aignas Nov 7, 2024
8585991
Merge branch 'main' into exp/freethreading
aignas Nov 11, 2024
f9609e9
comment: improve docs and add suggestions from rickeylev
aignas Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 # Use the ref you want to point at
hooks:
- id: check-merge-conflict
- repo: https://github.com/keith/pre-commit-buildifier
rev: 6.1.0
hooks:
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ A brief description of the categories of changes:
an implementation detail and we advise the users to use the `hub` repository
directly to avoid such breakage in the future. If `rules_python` is missing
features to allow one to do that, please raise tickets.
* (toolchains) Use the latest indygreg toolchain release [20241016] for Python versions:
* 3.9.20
* 3.10.15
* 3.11.10
* 3.12.7
* 3.13.0

[20241016]: https://github.com/indygreg/python-build-standalone/releases/tag/20241016

{#v0-0-0-fixed}
### Fixed
Expand Down Expand Up @@ -80,6 +88,7 @@ A brief description of the categories of changes:
and use the `pip` backend to do the downloading. This was only available for
users setting {bzl:obj}`pip.parse.experimental_index_url`, but now users have
more options whilst we continue to work on stabilizing the experimental feature.
* (toolchain) The support for freethreaded Python toolchains is now available.
aignas marked this conversation as resolved.
Show resolved Hide resolved

{#v0-0-0-removed}
### Removed
Expand Down
10 changes: 10 additions & 0 deletions docs/api/rules_python/python/config_settings/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ Values:
:::
::::

::::{bzl:flag} py_freethreaded
Set whether to use an interpreter with the experimental freethreaded option set to true.

Values:
* `no`: Use regular Python toolchains, default.
* `yes`: Use the experimental Python toolchain with freethreaded compile option enabled.
:::{versionadded} 0.38.0
:::
::::

::::{bzl:flag} pip_whl
Set what distributions are used in the `pip` integration.

Expand Down
14 changes: 14 additions & 0 deletions python/config_settings/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ load(
"AddSrcsToRunfilesFlag",
"BootstrapImplFlag",
"ExecToolsToolchainFlag",
"FreeThreadedFlag",
"PrecompileFlag",
"PrecompileSourceRetentionFlag",
)
Expand Down Expand Up @@ -92,6 +93,19 @@ string_flag(
visibility = ["//visibility:public"],
)

string_flag(
name = "py_freethreaded",
rickeylev marked this conversation as resolved.
Show resolved Hide resolved
build_setting_default = FreeThreadedFlag.NO,
values = sorted(FreeThreadedFlag.__members__.values()),
visibility = ["//visibility:public"],
)

config_setting(
name = "is_py_freethreaded",
flag_values = {":py_freethreaded": FreeThreadedFlag.YES},
visibility = ["//visibility:public"],
)

# pip.parse related flags

string_flag(
Expand Down
1 change: 1 addition & 0 deletions python/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ bzl_library(
deps = [
":py_toolchain_suite_bzl",
":text_util_bzl",
"//python:versions_bzl",
],
)

Expand Down
12 changes: 12 additions & 0 deletions python/private/coverage_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,23 @@ _coverage_deps = {
"https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl",
"a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9",
),
"aarch64-apple-darwin-freethreaded": (
"https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl",
"502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962",
),
"aarch64-unknown-linux-gnu": (
"https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c",
),
"aarch64-unknown-linux-gnu-freethreaded": (
"https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb",
),
"x86_64-unknown-linux-gnu": (
"https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060",
),
"x86_64-unknown-linux-gnu-freethreaded": (
"https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b",
),
Expand Down
10 changes: 10 additions & 0 deletions python/private/flags.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,13 @@ PrecompileSourceRetentionFlag = enum(
OMIT_SOURCE = "omit_source",
get_effective_value = _precompile_source_retention_flag_get_effective_value,
)

# Used for matching freethreaded toolchains and would have to be used in wheels
# as well.
# buildifier: disable=name-conventions
FreeThreadedFlag = enum(
# Use freethreaded python toolchain and wheels.
YES = "yes",
# Do not use freethreaded python toolchain and wheels.
NO = "no",
)
85 changes: 74 additions & 11 deletions python/private/hermetic_runtime_repo_setup.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ load(":glob_excludes.bzl", "glob_excludes")
load(":py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
load(":semver.bzl", "semver")

_IS_FREETHREADED = Label("//python/config_settings:is_py_freethreaded")

def define_hermetic_runtime_toolchain_impl(
*,
name,
Expand All @@ -45,7 +47,7 @@ def define_hermetic_runtime_toolchain_impl(
python_version: {type}`str` The Python version, in `major.minor.micro`
format.
python_bin: {type}`str` The path to the Python binary within the
repositoroy.
repository.
coverage_tool: {type}`str` optional target to the coverage tool to
use.
"""
Expand All @@ -67,19 +69,23 @@ def define_hermetic_runtime_toolchain_impl(
exclude = [
# Unused shared libraries. `python` executable and the `:libpython` target
aignas marked this conversation as resolved.
Show resolved Hide resolved
# depend on `libpython{python_version}.so.1.0`.
"lib/libpython{major}.{minor}.so".format(**version_dict),
"lib/libpython{major}.{minor}*.so".format(**version_dict),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking/curious: I'm guessing the libX.Y* pattern works because the extracted standalone interpreters only contain appropriately relevant .so files?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for exclusion, that is why libX.Y*.so is just as good as libX.Y.so.

# static libraries
"lib/**/*.a",
# tests for the standard libraries.
"lib/python{major}.{minor}/**/test/**".format(**version_dict),
"lib/python{major}.{minor}/**/tests/**".format(**version_dict),
"**/__pycache__/*.pyc.*", # During pyc creation, temp files named *.pyc.NNN are created
"lib/python{major}.{minor}*/**/test/**".format(**version_dict),
"lib/python{major}.{minor}*/**/tests/**".format(**version_dict),
# During pyc creation, temp files named *.pyc.NNN are created
"**/__pycache__/*.pyc.*",
] + glob_excludes.version_dependent_exclusions() + extra_files_glob_exclude,
),
)
cc_import(
name = "interface",
interface_library = "libs/python{major}{minor}.lib".format(**version_dict),
interface_library = select({
_IS_FREETHREADED: "libs/python{major}{minor}t.lib".format(**version_dict),
"//conditions:default": "libs/python{major}{minor}.lib".format(**version_dict),
}),
system_provided = True,
)

Expand All @@ -96,14 +102,65 @@ def define_hermetic_runtime_toolchain_impl(
hdrs = [":includes"],
includes = [
"include",
"include/python{major}.{minor}".format(**version_dict),
"include/python{major}.{minor}m".format(**version_dict),
] + select({
_IS_FREETHREADED: [
"include/python{major}.{minor}t".format(**version_dict),
# FIXME @aignas 2024-11-05: the following looks fishy - should
# we have a config setting for `m` (i.e. DEBUG builds)?
"include/python{major}.{minor}tm".format(**version_dict),
aignas marked this conversation as resolved.
Show resolved Hide resolved
],
"//conditions:default": [
"include/python{major}.{minor}".format(**version_dict),
"include/python{major}.{minor}m".format(**version_dict),
],
}),
)
native.config_setting(
name = "is_freethreaded_linux",
flag_values = {
Label("//python/config_settings:py_freethreaded"): "yes",
},
constraint_values = [
"@platforms//os:linux",
],
visibility = ["//visibility:private"],
)
native.config_setting(
name = "is_freethreaded_osx",
flag_values = {
Label("//python/config_settings:py_freethreaded"): "yes",
},
constraint_values = [
"@platforms//os:osx",
],
visibility = ["//visibility:private"],
)
native.config_setting(
name = "is_freethreaded_windows",
flag_values = {
Label("//python/config_settings:py_freethreaded"): "yes",
},
constraint_values = [
"@platforms//os:windows",
],
visibility = ["//visibility:private"],
)

cc_library(
name = "libpython",
hdrs = [":includes"],
srcs = select({
":is_freethreaded_linux": [
"lib/libpython{major}.{minor}t.so".format(**version_dict),
"lib/libpython{major}.{minor}t.so.1.0".format(**version_dict),
],
":is_freethreaded_osx": [
"lib/libpython{major}.{minor}t.dylib".format(**version_dict),
],
":is_freethreaded_windows": [
"python3.dll",
"libs/python{major}{minor}t.lib".format(**version_dict),
],
"@platforms//os:linux": [
"lib/libpython{major}.{minor}.so".format(**version_dict),
"lib/libpython{major}.{minor}.so.1.0".format(**version_dict),
Expand Down Expand Up @@ -132,12 +189,18 @@ def define_hermetic_runtime_toolchain_impl(
"micro": str(version_info.patch),
"minor": str(version_info.minor),
},
# Convert empty string to None
coverage_tool = coverage_tool or None,
coverage_tool = select({
# Convert empty string to None
":coverage_enabled": coverage_tool or None,
"//conditions:default": None,
}),
python_version = "PY3",
implementation_name = "cpython",
# See https://peps.python.org/pep-3147/ for pyc tag infix format
pyc_tag = "cpython-{major}{minor}".format(**version_dict),
pyc_tag = select({
_IS_FREETHREADED: "cpython-{major}{minor}t".format(**version_dict),
"//conditions:default": "cpython-{major}{minor}".format(**version_dict),
}),
)

py_runtime_pair(
Expand Down
6 changes: 4 additions & 2 deletions python/private/python.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def parse_modules(*, module_ctx, _fail = fail):
def _python_impl(module_ctx):
py = parse_modules(module_ctx = module_ctx)

loaded_platforms = {}
for toolchain_info in py.toolchains:
# Ensure that we pass the full version here.
full_python_version = full_version(
Expand All @@ -228,7 +229,7 @@ def _python_impl(module_ctx):
kwargs.update(py.config.kwargs.get(toolchain_info.python_version, {}))
kwargs.update(py.config.kwargs.get(full_python_version, {}))
kwargs.update(py.config.default)
python_register_toolchains(
loaded_platforms[full_python_version] = python_register_toolchains(
name = toolchain_info.name,
_internal_bzlmod_toolchain_call = True,
**kwargs
Expand Down Expand Up @@ -257,6 +258,7 @@ def _python_impl(module_ctx):
for i in range(len(py.toolchains))
],
toolchain_user_repository_names = [t.name for t in py.toolchains],
loaded_platforms = loaded_platforms,
)

# This is require in order to support multiple version py_test
Expand Down Expand Up @@ -464,7 +466,7 @@ def _get_toolchain_config(*, modules, _fail = fail):
"url": {
platform: [item["url"]]
for platform in item["sha256"]
},
} if type(item["url"]) == type("") else item["url"],
}
for version, item in TOOL_VERSIONS.items()
}
Expand Down
7 changes: 6 additions & 1 deletion python/private/python_register_toolchains.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def python_register_toolchains(
minor_mapping: {type}`dict[str, str]` contains a mapping from `X.Y` to `X.Y.Z`
version.
**kwargs: passed to each {obj}`python_repository` call.

Returns:
on bzlmod this returns the loaded platform labels.
aignas marked this conversation as resolved.
Show resolved Hide resolved
"""
bzlmod_toolchain_call = kwargs.pop("_internal_bzlmod_toolchain_call", False)
if bzlmod_toolchain_call:
Expand Down Expand Up @@ -168,11 +171,13 @@ def python_register_toolchains(

# in bzlmod we write out our own toolchain repos
if bzlmod_toolchain_call:
return
return loaded_platforms

toolchains_repo(
name = toolchain_repo_name,
python_version = python_version,
set_python_version_constraint = set_python_version_constraint,
user_repository_name = name,
platforms = loaded_platforms,
)
return None
8 changes: 6 additions & 2 deletions python/private/python_repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""This file contains repository rules and macros to support toolchain registration.
"""

load("//python:versions.bzl", "PLATFORMS")
load("//python:versions.bzl", "FREETHREADED", "PLATFORMS")
load(":auth.bzl", "get_auth")
load(":repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
load(":text_util.bzl", "render")
Expand Down Expand Up @@ -63,8 +63,12 @@ def _python_repository_impl(rctx):
platform = rctx.attr.platform
python_version = rctx.attr.python_version
python_version_info = python_version.split(".")
python_short_version = "{0}.{1}".format(*python_version_info)
release_filename = rctx.attr.release_filename
version_suffix = "t" if FREETHREADED in release_filename else ""
python_short_version = "{0}.{1}{suffix}".format(
suffix = version_suffix,
*python_version_info
)
urls = rctx.attr.urls or [rctx.attr.url]
auth = get_auth(rctx, urls)

Expand Down
13 changes: 12 additions & 1 deletion python/private/pythons_hub.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"Repo rule used by bzlmod extension to create a repo that has a map of Python interpreters and their labels"

load("//python:versions.bzl", "PLATFORMS")
load(":text_util.bzl", "render")
load(":toolchains_repo.bzl", "python_toolchain_build_file_content")

Expand Down Expand Up @@ -46,7 +47,8 @@ def _hub_build_file_content(
python_versions,
set_python_version_constraints,
user_repository_names,
workspace_location):
workspace_location,
loaded_platforms):
"""This macro iterates over each of the lists and returns the toolchain content.

python_toolchain_build_file_content is called to generate each of the toolchain
Expand All @@ -65,6 +67,11 @@ def _hub_build_file_content(
python_version = python_versions[i],
set_python_version_constraint = set_python_version_constraints[i],
user_repository_name = user_repository_names[i],
loaded_platforms = {
k: v
for k, v in PLATFORMS.items()
if k in loaded_platforms[python_versions[i]]
},
)
for i in range(len(python_versions))
],
Expand Down Expand Up @@ -103,6 +110,7 @@ def _hub_repo_impl(rctx):
rctx.attr.toolchain_set_python_version_constraints,
rctx.attr.toolchain_user_repository_names,
rctx.attr._rules_python_workspace,
rctx.attr.loaded_platforms,
),
executable = False,
)
Expand Down Expand Up @@ -149,6 +157,9 @@ This rule also writes out the various toolchains for the different Python versio
doc = "Default Python version for the build in `X.Y` or `X.Y.Z` format.",
mandatory = True,
),
"loaded_platforms": attr.string_list_dict(
doc = "The list of loaded platforms keyed by the toolchain full python version",
),
"minor_mapping": attr.string_dict(
doc = "The minor mapping of the `X.Y` to `X.Y.Z` format that is used in config settings.",
mandatory = True,
Expand Down
Loading