Skip to content

Commit 5d9f9cb

Browse files
committed
local toolchain with pip
1 parent f5ab3bc commit 5d9f9cb

File tree

11 files changed

+234
-16
lines changed

11 files changed

+234
-16
lines changed

python/private/get_local_runtime_info.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,12 @@ def _get_base_executable():
205205
# is missing.
206206
return sys.executable
207207

208-
209208
data = {
210209
"major": sys.version_info.major,
211210
"minor": sys.version_info.minor,
212211
"micro": sys.version_info.micro,
212+
"releaselevel": sys.version_info.releaselevel,
213+
"serial": sys.version_info.serial,
213214
"include": sysconfig.get_path("include"),
214215
"implementation_name": sys.implementation.name,
215216
"base_executable": _get_base_executable(),

python/private/local_runtime_repo.bzl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
load(":enum.bzl", "enum")
1818
load(":repo_utils.bzl", "REPO_DEBUG_ENV_VAR", "repo_utils")
19+
load(":text_util.bzl", "render")
1920

2021
# buildifier: disable=name-conventions
2122
_OnFailure = enum(
@@ -42,6 +43,16 @@ define_local_runtime_toolchain_impl(
4243
)
4344
"""
4445

46+
RUNTIME_INFO_BZL_TEMPLATE = """
47+
# Generated by python/private/local_runtime_repo.bzl
48+
49+
load("@rules_python//python/private:local_runtime_repo_setup.bzl", "define_local_runtime_toolchain_impl")
50+
51+
info = create_info_struct(
52+
{info}
53+
)
54+
"""
55+
4556
def _norm_path(path):
4657
"""Returns a path using '/' separators and no trailing slash."""
4758
path = path.replace("\\", "/")
@@ -181,6 +192,17 @@ def _local_runtime_repo_impl(rctx):
181192
rctx.file("REPO.bazel", "")
182193
rctx.file("BUILD.bazel", build_bazel)
183194

195+
# JSON format for repo-phase code
196+
rctx.file("runtime_info.json", json.encode_indent(info))
197+
198+
# bzl format for loading-phase code
199+
rctx.file("runtime_info.bzl", RUNTIME_INFO_BZL_TEMPLATE.format(
200+
info = render.dict(info),
201+
))
202+
203+
# Text format for `python.toolchain.python_version_file`
204+
rctx.file("version.txt", "{major}.{minor}".format(**info))
205+
184206
local_runtime_repo = repository_rule(
185207
implementation = _local_runtime_repo_impl,
186208
doc = """

python/private/local_runtime_repo_setup.bzl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,21 @@ def define_local_runtime_toolchain_impl(
167167
],
168168
visibility = ["//visibility:public"],
169169
)
170+
171+
def create_info_struct(info):
172+
self = struct(
173+
_info = info,
174+
get_info = lambda *a, **k: _info_get_info(self, *a, **k),
175+
get_version_major_minor = lambda *a, **k: _info_get_version_major_minor(self, *a, **k),
176+
get_version_full = lambda *a, **k: _info_get_version_full(self, *a, **k),
177+
)
178+
return self
179+
180+
def _info_get_info(self):
181+
return self._info
182+
183+
def _info_get_version_major_minor(self):
184+
return "{major}.{minor}".format(**self._info)
185+
186+
def _info_get_version_full(self):
187+
return "{major}.{minor}.{micro}".format(**self._info)

python/private/pypi/extension.bzl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
load("@bazel_features//:features.bzl", "bazel_features")
1818
load("@pythons_hub//:interpreters.bzl", "INTERPRETER_LABELS")
19-
load("@pythons_hub//:versions.bzl", "MINOR_MAPPING")
19+
load("@pythons_hub//:versions.bzl", "DEFAULT_PYTHON_VERSION", "MINOR_MAPPING")
2020
load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config")
2121
load("//python/private:auth.bzl", "AUTH_ATTRS")
2222
load("//python/private:normalize_name.bzl", "normalize_name")
@@ -80,7 +80,11 @@ def build_config(
8080
evaluation of the extension.
8181
8282
Returns:
83-
A struct with the configuration.
83+
A struct with the configuration, attributes:
84+
* `auth_patterns`: dict of authentication patterns
85+
* `netrc`: netrc file or None
86+
* `platforms`: dict[str, ??] of platform configs
87+
* `enable_pipstar`: bool
8488
"""
8589
defaults = {
8690
"platforms": {},
@@ -229,6 +233,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
229233
whl_overrides = whl_overrides,
230234
simpleapi_download_fn = simpleapi_download,
231235
simpleapi_cache = simpleapi_cache,
236+
default_python_version = DEFAULT_PYTHON_VERSION,
232237
# TODO @aignas 2025-09-06: do not use kwargs
233238
minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING),
234239
evaluate_markers_fn = kwargs.get("evaluate_markers", None),
@@ -647,7 +652,7 @@ find in case extra indexes are specified.
647652
default = True,
648653
),
649654
"python_version": attr.string(
650-
mandatory = True,
655+
##mandatory = True,
651656
doc = """
652657
The Python version the dependencies are targetting, in Major.Minor format
653658
(e.g., "3.11") or patch level granularity (e.g. "3.11.1").

python/private/pypi/hub_builder.bzl

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def hub_builder(
2424
module_name,
2525
config,
2626
whl_overrides,
27+
default_python_version,
2728
minor_mapping,
2829
available_interpreters,
2930
simpleapi_download_fn,
@@ -69,8 +70,10 @@ def hub_builder(
6970
_get_index_urls = {},
7071
_use_downloader = {},
7172
_simpleapi_cache = simpleapi_cache,
73+
_get_python_version = lambda *a, **k: _get_python_version(self, *a, **k),
7274
# instance constants
7375
_config = config,
76+
_default_python_version = default_python_version,
7477
_whl_overrides = whl_overrides,
7578
_evaluate_markers_fn = evaluate_markers_fn,
7679
_logger = logger,
@@ -102,7 +105,7 @@ def _build(self):
102105
)
103106

104107
def _pip_parse(self, module_ctx, pip_attr):
105-
python_version = pip_attr.python_version
108+
python_version = self._get_python_version(pip_attr)
106109
if python_version in self._platforms:
107110
fail((
108111
"Duplicate pip python version '{version}' for hub " +
@@ -230,7 +233,7 @@ def _set_get_index_urls(self, pip_attr):
230233
# here
231234
return
232235

233-
python_version = pip_attr.python_version
236+
python_version = self._get_python_version(pip_attr)
234237
self._use_downloader.setdefault(python_version, {}).update({
235238
normalize_name(s): False
236239
for s in pip_attr.simpleapi_skip
@@ -259,7 +262,7 @@ def _detect_interpreter(self, pip_attr):
259262
python_interpreter_target = pip_attr.python_interpreter_target
260263
if python_interpreter_target == None and not pip_attr.python_interpreter:
261264
python_name = "python_{}_host".format(
262-
pip_attr.python_version.replace(".", "_"),
265+
self._get_python_version(pip_attr).replace(".", "_"),
263266
)
264267
if python_name not in self._available_interpreters:
265268
fail((
@@ -269,7 +272,7 @@ def _detect_interpreter(self, pip_attr):
269272
"Expected to find {python_name} among registered versions:\n {labels}"
270273
).format(
271274
hub_name = self.name,
272-
version = pip_attr.python_version,
275+
version = self._get_python_version(pip_attr),
273276
python_name = python_name,
274277
labels = " \n".join(self._available_interpreters),
275278
))
@@ -332,7 +335,7 @@ def _evaluate_markers(self, pip_attr):
332335
if self._config.enable_pipstar:
333336
return lambda _, requirements: evaluate_markers_star(
334337
requirements = requirements,
335-
platforms = self._platforms[pip_attr.python_version],
338+
platforms = self._platforms[self._get_python_version(pip_attr)],
336339
)
337340

338341
interpreter = _detect_interpreter(self, pip_attr)
@@ -355,7 +358,7 @@ def _evaluate_markers(self, pip_attr):
355358
module_ctx,
356359
requirements = {
357360
k: {
358-
p: self._platforms[pip_attr.python_version][p].triple
361+
p: self._platforms[self._get_python_version(pip_attr)][p].triple
359362
for p in plats
360363
}
361364
for k, plats in requirements.items()
@@ -379,7 +382,7 @@ def _create_whl_repos(
379382
pip_attr: {type}`struct` - the struct that comes from the tag class iteration.
380383
"""
381384
logger = self._logger
382-
platforms = self._platforms[pip_attr.python_version]
385+
platforms = self._platforms[self._get_python_version(pip_attr)]
383386
requirements_by_platform = parse_requirements(
384387
module_ctx,
385388
requirements_by_platform = requirements_files_by_platform(
@@ -391,14 +394,14 @@ def _create_whl_repos(
391394
extra_pip_args = pip_attr.extra_pip_args,
392395
platforms = sorted(platforms), # here we only need keys
393396
python_version = full_version(
394-
version = pip_attr.python_version,
397+
version = self._get_python_version(pip_attr),
395398
minor_mapping = self._minor_mapping,
396399
),
397400
logger = logger,
398401
),
399402
platforms = platforms,
400403
extra_pip_args = pip_attr.extra_pip_args,
401-
get_index_urls = self._get_index_urls.get(pip_attr.python_version),
404+
get_index_urls = self._get_index_urls.get(self._get_python_version(pip_attr)),
402405
evaluate_markers = _evaluate_markers(self, pip_attr),
403406
logger = logger,
404407
)
@@ -431,15 +434,15 @@ def _create_whl_repos(
431434
whl_library_args = whl_library_args,
432435
download_only = pip_attr.download_only,
433436
netrc = self._config.netrc or pip_attr.netrc,
434-
use_downloader = _use_downloader(self, pip_attr.python_version, whl.name),
437+
use_downloader = _use_downloader(self, self._get_python_version(pip_attr), whl.name),
435438
auth_patterns = self._config.auth_patterns or pip_attr.auth_patterns,
436-
python_version = _major_minor_version(pip_attr.python_version),
439+
python_version = _major_minor_version(self._get_python_version(pip_attr)),
437440
is_multiple_versions = whl.is_multiple_versions,
438441
enable_pipstar = self._config.enable_pipstar,
439442
)
440443
_add_whl_library(
441444
self,
442-
python_version = pip_attr.python_version,
445+
python_version = self._get_python_version(pip_attr),
443446
whl = whl,
444447
repo = repo,
445448
)
@@ -579,3 +582,10 @@ def _use_downloader(self, python_version, whl_name):
579582
normalize_name(whl_name),
580583
self._get_index_urls.get(python_version) != None,
581584
)
585+
586+
def _get_python_version(self, pip_attr):
587+
python_version = pip_attr.python_version
588+
if python_version:
589+
return python_version
590+
else:
591+
return self._default_python_version

tests/integration/local_toolchains/BUILD.bazel

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@
1414

1515
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
1616
load("@rules_cc//cc:cc_library.bzl", "cc_library")
17+
load("@rules_python//python:py_binary.bzl", "py_binary")
1718
load("@rules_python//python:py_test.bzl", "py_test")
1819
load(":py_extension.bzl", "py_extension")
1920

21+
package(
22+
default_visibility = ["//:__subpackages__"],
23+
)
24+
2025
py_test(
2126
name = "local_runtime_test",
2227
srcs = ["local_runtime_test.py"],
@@ -35,6 +40,14 @@ py_test(
3540
},
3641
)
3742

43+
py_binary(
44+
name = "bin",
45+
srcs = ["bin.py"],
46+
deps = [
47+
"@pypi//more_itertools",
48+
],
49+
)
50+
3851
config_setting(
3952
name = "is_py_local",
4053
flag_values = {
@@ -56,6 +69,11 @@ string_flag(
5669
build_setting_default = "",
5770
)
5871

72+
filegroup(
73+
name = "pyproject",
74+
srcs = ["pyproject.toml"],
75+
)
76+
5977
# Build rules to generate a python extension.
6078
cc_library(
6179
name = "echo_ext_cc",

0 commit comments

Comments
 (0)