Skip to content

Commit 1b97c64

Browse files
laramielcopybara-github
authored andcommitted
Load @rules_python
Replace third_party/python/python_configure with bazel/repo_rules/local_python_runtime, which creates a local python runtime/toolchain repository equivalent to @rules_python/python/local_toolchains:local_runtime_repo Reference to the python headers now use the toolchain-based indirection rather than referencing the local repository headers directly. Includes local changes to also address: bazel-contrib/rules_python#3055 See external version at: bazel-contrib/rules_python#3148 PiperOrigin-RevId: 794407197 Change-Id: I210f776470bad045a031fffb0cf1f333114d8548
1 parent e219fa8 commit 1b97c64

18 files changed

+857
-720
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ bazel_to_cmake(
8686
"--include-package=tensorstore/**"
8787
"--exclude-package=tensorstore/examples/python/**"
8888
"--exclude-package=third_party/local_proto_mirror/**"
89-
--ignore-library "//third_party:python/python_configure.bzl"
89+
--ignore-library "//bazel/repo_rules:local_python_runtime.bzl"
9090
--ignore-library "//docs:doctest.bzl"
9191
--ignore-library "//bazel:non_compile.bzl"
9292
--ignore-library "@com_google_protobuf//:protobuf.bzl"

WORKSPACE

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,32 @@ load("@bazel_features//:deps.bzl", "bazel_features_deps")
1010

1111
bazel_features_deps()
1212

13-
register_toolchains("@local_config_python//:py_toolchain")
13+
# @rules_python configuration for local python runtime / toolchain.
14+
#
15+
# py_repositories() creates internal repositories for python, and use of rules_python
16+
# fails when they are not present.
17+
load("@rules_python//python:repositories.bzl", "py_repositories")
18+
19+
py_repositories()
20+
21+
# Register the local toolchains for python:
22+
#
23+
# Step 1: Define the python runtime.
24+
# This is done by local_python_runtime() in tensorstore_dependencies()
25+
#
26+
# Step 2: Create toolchains for the runtimes
27+
# This is done by local_runtime_toolchains_repo()
28+
#
29+
# Step 3: Register the toolchains
30+
# This is done by register_toolchains()
31+
load("@rules_python//python/local_toolchains:repos.bzl", "local_runtime_toolchains_repo")
32+
33+
local_runtime_toolchains_repo(
34+
name = "local_toolchains",
35+
runtimes = ["local_config_python"],
36+
)
37+
38+
register_toolchains("@local_toolchains//:all")
1439

1540
# Register proto toolchains.
1641
load("@rules_proto//proto:toolchains.bzl", "rules_proto_toolchains")

bazel/repo_rules/get_local_runtime_info.py

Lines changed: 175 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,185 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

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-
"""
15+
"""Returns information about the local Python runtime as JSON."""
2016

2117
import json
18+
import os
2219
import sys
2320
import sysconfig
2421

22+
_IS_WINDOWS = sys.platform == "win32"
23+
_IS_DARWIN = sys.platform == "darwin"
24+
25+
26+
def _search_directories(get_config):
27+
"""Returns a list of library directories to search for shared libraries."""
28+
# There's several types of libraries with different names and a plethora
29+
# of settings, and many different config variables to check:
30+
#
31+
# LIBPL is used in python-config when shared library is not enabled:
32+
# https://github.com/python/cpython/blob/v3.12.0/Misc/python-config.in#L63
33+
#
34+
# LIBDIR may also be the python directory with library files.
35+
# https://stackoverflow.com/questions/47423246/get-pythons-lib-path
36+
# See also: MULTIARCH
37+
#
38+
# On MacOS, the LDLIBRARY may be a relative path under /Library/Frameworks,
39+
# such as "Python.framework/Versions/3.12/Python", not a file under the
40+
# LIBDIR/LIBPL directory, so include PYTHONFRAMEWORKPREFIX.
41+
lib_dirs = [
42+
get_config(x) for x in ("PYTHONFRAMEWORKPREFIX", "LIBPL", "LIBDIR")
43+
]
44+
45+
# On Debian, with multiarch enabled, prior to Python 3.10, `LIBDIR` didn't
46+
# tell the location of the libs, just the base directory. The `MULTIARCH`
47+
# sysconfig variable tells the subdirectory within it with the libs.
48+
# See:
49+
# https://wiki.debian.org/Python/MultiArch
50+
# https://git.launchpad.net/ubuntu/+source/python3.12/tree/debian/changelog#n842
51+
multiarch = get_config("MULTIARCH")
52+
if multiarch:
53+
for x in ("LIBPL", "LIBDIR"):
54+
config_value = get_config(x)
55+
if config_value and not config_value.endswith(multiarch):
56+
lib_dirs.append(os.path.join(config_value, multiarch))
57+
58+
if _IS_WINDOWS:
59+
# On Windows DLLs go in the same directory as the executable, while .lib
60+
# files live in the lib/ or libs/ subdirectory.
61+
lib_dirs.append(get_config("BINDIR"))
62+
lib_dirs.append(os.path.join(os.path.dirname(sys.executable)))
63+
lib_dirs.append(os.path.join(os.path.dirname(sys.executable), "lib"))
64+
lib_dirs.append(os.path.join(os.path.dirname(sys.executable), "libs"))
65+
elif not _IS_DARWIN:
66+
# On most systems the executable is in a bin/ directory and the libraries
67+
# are in a sibling lib/ directory.
68+
lib_dirs.append(
69+
os.path.join(os.path.dirname(os.path.dirname(sys.executable)), "lib")
70+
)
71+
72+
# Dedup and remove empty values, keeping the order.
73+
lib_dirs = [v for v in lib_dirs if v]
74+
return {k: None for k in lib_dirs}.keys()
75+
76+
77+
def _search_library_names(get_config):
78+
"""Returns a list of library files to search for shared libraries."""
79+
# Quoting configure.ac in the cpython code base:
80+
# "INSTSONAME is the name of the shared library that will be use to install
81+
# on the system - some systems like version suffix, others don't.""
82+
#
83+
# A typical INSTSONAME is 'libpython3.8.so.1.0' on Linux, or
84+
# 'Python.framework/Versions/3.9/Python' on MacOS.
85+
#
86+
# A typical LDLIBRARY is 'libpythonX.Y.so' on Linux, or 'pythonXY.dll' on
87+
# Windows, or 'Python.framework/Versions/3.9/Python' on MacOS.
88+
#
89+
# A typical LIBRARY is 'libpythonX.Y.a' on Linux.
90+
lib_names = [
91+
get_config(x)
92+
for x in (
93+
"LDLIBRARY",
94+
"INSTSONAME",
95+
"PY3LIBRARY",
96+
"LIBRARY",
97+
"DLLLIBRARY",
98+
)
99+
]
100+
101+
# Set the prefix and suffix to construct the library name used for linking.
102+
# The suffix and version are set here to the default values for the OS,
103+
# since they are used below to construct "default" library names.
104+
if _IS_DARWIN:
105+
suffix = ".dylib"
106+
prefix = "lib"
107+
elif _IS_WINDOWS:
108+
suffix = ".dll"
109+
prefix = ""
110+
else:
111+
suffix = get_config("SHLIB_SUFFIX")
112+
prefix = "lib"
113+
if not suffix:
114+
suffix = ".so"
115+
116+
version = get_config("VERSION")
117+
118+
# Ensure that the pythonXY.dll files are included in the search.
119+
lib_names.append(f"{prefix}python{version}{suffix}")
120+
121+
# If there are ABIFLAGS, also add them to the python version lib search.
122+
abiflags = get_config("ABIFLAGS") or get_config("abiflags") or ""
123+
if abiflags:
124+
lib_names.append(f"{prefix}python{version}{abiflags}{suffix}")
125+
126+
# Dedup and remove empty values, keeping the order.
127+
lib_names = [v for v in lib_names if v]
128+
return {k: None for k in lib_names}.keys()
129+
130+
131+
def _get_python_library_info():
132+
"""Returns a dictionary with the static and dynamic python libraries."""
133+
config_vars = sysconfig.get_config_vars()
134+
135+
# VERSION is X.Y in Linux/macOS and XY in Windows. This is used to
136+
# construct library paths such as python3.12, so ensure it exists.
137+
if not config_vars.get("VERSION"):
138+
if sys.platform == "win32":
139+
config_vars["VERSION"] = (
140+
f"{sys.version_info.major}{sys.version_info.minor}"
141+
)
142+
else:
143+
config_vars["VERSION"] = (
144+
f"{sys.version_info.major}.{sys.version_info.minor}"
145+
)
146+
147+
search_directories = _search_directories(config_vars.get)
148+
search_libnames = _search_library_names(config_vars.get)
149+
150+
def _add_if_exists(target, path):
151+
if os.path.exists(path) or os.path.isdir(path):
152+
target[path] = None
153+
154+
interface_libraries = {}
155+
dynamic_libraries = {}
156+
static_libraries = {}
157+
for root_dir in search_directories:
158+
for libname in search_libnames:
159+
composed_path = os.path.join(root_dir, libname)
160+
if libname.endswith(".a"):
161+
_add_if_exists(static_libraries, composed_path)
162+
continue
163+
164+
_add_if_exists(dynamic_libraries, composed_path)
165+
if libname.endswith(".dll"):
166+
# On windows a .lib file may be an "import library" or a static library.
167+
# The file could be inspected to determine which it is; typically python
168+
# is used as a shared library.
169+
#
170+
# On Windows, extensions should link with the pythonXY.lib interface
171+
# libraries.
172+
#
173+
# See: https://docs.python.org/3/extending/windows.html
174+
# https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-creation
175+
_add_if_exists(
176+
interface_libraries, os.path.join(root_dir, libname[:-3] + "lib")
177+
)
178+
elif libname.endswith(".so"):
179+
# It's possible, though unlikely, that interface stubs (.ifso) exist.
180+
_add_if_exists(
181+
interface_libraries, os.path.join(root_dir, libname[:-2] + "ifso")
182+
)
183+
184+
# When no libraries are found it's likely that the python interpreter is not
185+
# configured to use shared or static libraries (minilinux). If this seems
186+
# suspicious try running `uv tool run find_libpython --list-all -v`
187+
return {
188+
"dynamic_libraries": list(dynamic_libraries.keys()),
189+
"static_libraries": list(static_libraries.keys()),
190+
"interface_libraries": list(interface_libraries.keys()),
191+
}
192+
193+
25194
data = {
26195
"major": sys.version_info.major,
27196
"minor": sys.version_info.minor,
@@ -30,35 +199,5 @@
30199
"implementation_name": sys.implementation.name,
31200
"base_executable": sys._base_executable,
32201
}
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)))
202+
data.update(_get_python_library_info())
64203
print(json.dumps(data))

0 commit comments

Comments
 (0)