Skip to content

Commit 9e1feb7

Browse files
laramielcopybara-github
authored andcommitted
Separate repo.bzl third_party_http_archive and third_party_python_repository rules
Cleans up some paths (mostly in comments) which are not correct. Remove a few utils.bzl functions and introduce a new "repo_utils.bzl" struct. PiperOrigin-RevId: 789905312 Change-Id: If3368d011aed9b44d7388740c81ec3caf4933ef3
1 parent e4f7a54 commit 9e1feb7

39 files changed

+1507
-353
lines changed

BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ exports_files(glob(["*"]))
1111
#[
1212
# alias(
1313
# name = name,
14-
# actual = "@tensorstore//:{target}".format(target = name),
14+
# actual = "//:{target}".format(target = name),
1515
# )
1616
# for name in [
1717
# "arm64_clang",

bazel/BUILD

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,3 @@
1-
# Copyright 2022 The TensorStore Authors
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-
15-
"""
16-
Contains build targets used by Starlark files in the bazel/ directory.
17-
"""
18-
191
package(default_visibility = ["//visibility:public"])
202

213
licenses(["notice"])

bazel/repo_rules/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
licenses(["notice"])
4+
5+
filegroup(
6+
name = "_bazel_sources",
7+
srcs = glob(["*.bzl"]),
8+
)
9+
10+
filegroup(
11+
name = "_py_scrpts",
12+
srcs = glob(["*.py"]),
13+
)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
15+
"""Returns information about the local Python runtime as JSON.
16+
17+
Forked from: @rules_python/python/private/get_local_runtime_info.py
18+
This is used by local_python_runtime.bzl before @rules_python is loaded.
19+
"""
20+
21+
import json
22+
import sys
23+
import sysconfig
24+
25+
data = {
26+
"major": sys.version_info.major,
27+
"minor": sys.version_info.minor,
28+
"micro": sys.version_info.micro,
29+
"include": sysconfig.get_path("include"),
30+
"implementation_name": sys.implementation.name,
31+
"base_executable": sys._base_executable,
32+
}
33+
34+
config_vars = [
35+
# The libpythonX.Y.so file. Usually?
36+
# It might be a static archive (.a) file instead.
37+
"LDLIBRARY",
38+
# The directory with library files. Supposedly.
39+
# It's not entirely clear how to get the directory with libraries.
40+
# There's several types of libraries with different names and a plethora
41+
# of settings.
42+
# https://stackoverflow.com/questions/47423246/get-pythons-lib-path
43+
# For now, it seems LIBDIR has what is needed, so just use that.
44+
# See also: MULTIARCH
45+
"LIBDIR",
46+
# On Debian, with multiarch enabled, prior to Python 3.10, `LIBDIR` didn't
47+
# tell the location of the libs, just the base directory. The `MULTIARCH`
48+
# sysconfig variable tells the subdirectory within it with the libs.
49+
# See:
50+
# https://wiki.debian.org/Python/MultiArch
51+
# https://git.launchpad.net/ubuntu/+source/python3.12/tree/debian/changelog#n842
52+
"MULTIARCH",
53+
# The versioned libpythonX.Y.so.N file. Usually?
54+
# It might be a static archive (.a) file instead.
55+
"INSTSONAME",
56+
# The libpythonX.so file. Usually?
57+
# It might be a static archive (a.) file instead.
58+
"PY3LIBRARY",
59+
# The platform-specific filename suffix for library files.
60+
# Includes the dot, e.g. `.so`
61+
"SHLIB_SUFFIX",
62+
]
63+
data.update(zip(config_vars, sysconfig.get_config_vars(*config_vars)))
64+
print(json.dumps(data))
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# Copyright 2025 The TensorStore Authors
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+
15+
# Based on the @rules_python//python/private/local_runtime_repo.bzl
16+
#
17+
"""Repository rule for Python local tool configuration.
18+
19+
`local_python_runtime` depends on the following environment variables:
20+
21+
* `PYTHON_BIN_PATH`: location of python binary.
22+
* `TENSORSTORE_PYTHON_CONFIG_REPO`: location of remote python configuration repository.
23+
24+
There are three essential changes here compared to @rules_python/python/local_toolchains
25+
and @rules_python/python/private/local_runtime_repo.bzl
26+
27+
1. The environment variable `PYTHON_BIN_PATH` overrides interpreter_path resolution;
28+
The environment variable `TENSORSTORE_PYTHON_CONFIG_REPO` overrides local configuration.
29+
30+
2. It happens before the @rules_python is loaded, so it cannot reference rules python.
31+
32+
"""
33+
34+
load(
35+
"//bazel/repo_rules:py_utils.bzl",
36+
"py_utils",
37+
_ENV_VARS = "ENV_VARS",
38+
)
39+
load(
40+
"//bazel/repo_rules:repo_utils.bzl",
41+
"repo_utils",
42+
)
43+
44+
_TENSORSTORE_PYTHON_CONFIG_REPO = "TENSORSTORE_PYTHON_CONFIG_REPO"
45+
46+
_TOOLCHAIN_IMPL_TEMPLATE = """\
47+
# Generated by python/python_configure.bzl
48+
49+
package(
50+
default_visibility = ["//visibility:public"],
51+
)
52+
53+
load("@rules_python//python/private:local_runtime_repo_setup.bzl", "define_local_runtime_toolchain_impl")
54+
55+
define_local_runtime_toolchain_impl(
56+
name = "local_runtime",
57+
lib_ext = "{lib_ext}",
58+
major = "{major}",
59+
minor = "{minor}",
60+
micro = "{micro}",
61+
interpreter_path = "{interpreter_path}",
62+
implementation_name = "{implementation_name}",
63+
os = "{os}",
64+
)
65+
66+
"""
67+
68+
def _local_python_repo_impl(rctx):
69+
"""Implementation of the local_python_repo repository rule."""
70+
if _TENSORSTORE_PYTHON_CONFIG_REPO in rctx.os.environ:
71+
remote_config_repo = rctx.os.environ[_TENSORSTORE_PYTHON_CONFIG_REPO]
72+
rctx.template("BUILD", Label(remote_config_repo + ":BUILD"), {})
73+
return
74+
75+
# Otherwise creates the repository containing files set up to build with Python."""
76+
logger = repo_utils.logger(rctx)
77+
78+
result = py_utils.get_python_interpreter(rctx, rctx.attr.interpreter_path)
79+
if not result.resolved_path:
80+
logger.fail(lambda: "interpreter not found: {}".format(result.describe_failure()))
81+
return
82+
else:
83+
interpreter_path = result.resolved_path
84+
85+
result = py_utils.get_python_info(
86+
rctx,
87+
interpreter_path = interpreter_path,
88+
logger = logger,
89+
)
90+
if not result.info:
91+
logger.fail(lambda: "GetPythonInfo failed: {}".format(result.describe_failure()))
92+
return
93+
else:
94+
info = result.info
95+
96+
# Example of a GetPythonInfo result from macos:
97+
# {
98+
# "major": 3,
99+
# "minor": 12,
100+
# "micro": 10,
101+
# "implementation_name": "cpython",
102+
# "base_executable": "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12",
103+
# "include": "/Library/Frameworks/Python.framework/Versions/3.12/include/python3.12",
104+
# "numpy_include": "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/numpy/_core/include",
105+
# "LDLIBRARY": "Python.framework/Versions/3.12/Python",
106+
# "LIBDIR": "/Library/Frameworks/Python.framework/Versions/3.12/lib",
107+
# "INSTSONAME": "Python.framework/Versions/3.12/Python",
108+
# "PY3LIBRARY": "",
109+
# "SHLIB_SUFFIX": ".so",
110+
# }
111+
#
112+
# We use base_executable because we want the path within a Python
113+
# installation directory ("PYTHONHOME"). The problems with sys.executable
114+
# are:
115+
# * If we're in an activated venv, then we don't want the venv's
116+
# `bin/python3` path to be used -- it isn't an actual Python installation.
117+
# * If sys.executable is a wrapper (e.g. pyenv), then (1) it may not be
118+
# located within an actual Python installation directory, and (2) it
119+
# can interfer with Python recognizing when it's within a venv.
120+
#
121+
# In some cases, it may be a symlink (usually e.g. `python3->python3.12`),
122+
# but we don't realpath() it to respect what it has decided is the
123+
# appropriate path.
124+
interpreter_path = info["base_executable"]
125+
126+
# NOTE: Keep in sync with recursive glob in define_local_runtime_toolchain_impl
127+
repo_utils.watch_tree(rctx, rctx.path(info["include"]))
128+
129+
# The cc_library.includes values have to be non-absolute paths, otherwise
130+
# the toolchain will give an error. Work around this error by making them
131+
# appear as part of this repo.
132+
include_src = repo_utils.norm_path(info["include"])
133+
numpy_include_src = include_src + "/numpy/"
134+
for src in sorted(repo_utils.read_dir(rctx, include_src, logger)):
135+
if src.startswith(numpy_include_src):
136+
continue
137+
dest = src.replace(include_src, "include")
138+
rctx.symlink(src, dest)
139+
140+
shared_lib_names = [
141+
info["PY3LIBRARY"],
142+
info["LDLIBRARY"],
143+
info["INSTSONAME"],
144+
]
145+
146+
# In some cases, the value may be empty. Not clear why.
147+
shared_lib_names = [v for v in shared_lib_names if v]
148+
149+
# In some cases, the same value is returned for multiple keys. Not clear why.
150+
shared_lib_names = {v: None for v in shared_lib_names}.keys()
151+
shared_lib_dir = info["LIBDIR"]
152+
153+
# The specific files are symlinked instead of the whole directory
154+
# because it can point to a directory that has more than just
155+
# the Python runtime shared libraries, e.g. /usr/lib, or a Python
156+
# specific directory with pip-installed shared libraries.
157+
rctx.report_progress("Symlinking external Python shared libraries")
158+
for name in shared_lib_names:
159+
origin = rctx.path("{}/{}".format(shared_lib_dir, name))
160+
161+
# The reported names don't always exist; it depends on the particulars
162+
# of the runtime installation.
163+
if origin.exists:
164+
repo_utils.watch(rctx, origin)
165+
rctx.symlink(origin, "lib/" + name)
166+
167+
rctx.file("WORKSPACE", "")
168+
rctx.file("MODULE.bazel", "")
169+
rctx.file("REPO.bazel", "")
170+
rctx.file("BUILD.bazel", _TOOLCHAIN_IMPL_TEMPLATE.format(
171+
major = info["major"],
172+
minor = info["minor"],
173+
micro = info["micro"],
174+
interpreter_path = interpreter_path,
175+
lib_ext = info["SHLIB_SUFFIX"],
176+
implementation_name = info["implementation_name"],
177+
os = "@platforms//os:{}".format(repo_utils.get_platforms_os_name(rctx)),
178+
))
179+
180+
# Public python attributes.
181+
python_attrs = {
182+
"interpreter_path": attr.string(
183+
doc = """
184+
An absolute path or program name on the `PATH` env var.
185+
186+
Values with slashes are assumed to be the path to a program. Otherwise, it is
187+
treated as something to search for on `PATH`
188+
189+
Note that, when a plain program name is used, the path to the interpreter is
190+
resolved at repository evalution time, not runtime of any resulting binaries.
191+
""",
192+
),
193+
"_get_runtime_info": attr.label(
194+
allow_single_file = True,
195+
default = "//bazel/repo_rules:get_local_runtime_info.py",
196+
),
197+
}
198+
199+
# Environment variables that are used by python_configure.
200+
PYTHON_ENV_VARS = _ENV_VARS + [
201+
# When provided, the value of this environment variable is used to
202+
# determine whether to use the remote python configuration repository.
203+
_TENSORSTORE_PYTHON_CONFIG_REPO,
204+
]
205+
206+
local_python_runtime = repository_rule(
207+
implementation = _local_python_repo_impl,
208+
local = True,
209+
configure = True,
210+
attrs = python_attrs | {
211+
"_rule_name": attr.string(default = "local_python_runtime"),
212+
},
213+
environ = PYTHON_ENV_VARS,
214+
doc = """\
215+
Detects and configures the local Python.
216+
217+
Add the following to your WORKSPACE FILE:
218+
219+
```python
220+
local_python_runtime(name = "local_config_python")
221+
```
222+
223+
Args:
224+
name: A unique name for this workspace rule.
225+
""",
226+
)

0 commit comments

Comments
 (0)