diff --git a/python/private/get_local_runtime_info.py b/python/private/get_local_runtime_info.py index e0eab962d6..a4f622700b 100644 --- a/python/private/get_local_runtime_info.py +++ b/python/private/get_local_runtime_info.py @@ -268,7 +268,9 @@ def _get_base_executable() -> str: "implementation_name": sys.implementation.name, "base_executable": _get_base_executable(), "sys_platform": sys.platform, - "platform_machine": platform.machine(), + # Normalize to lowercase: on Windows, platform.machine() returns uppercase + # "AMD64" / "ARM64", whereas PEP 508 and toolchains expect lowercase. + "platform_machine": platform.machine().lower(), } data.update(_get_python_library_info(_get_base_executable())) print(json.dumps(data)) diff --git a/python/private/local_runtime_repo_setup.bzl b/python/private/local_runtime_repo_setup.bzl index 62e46e4b98..05ca6228e0 100644 --- a/python/private/local_runtime_repo_setup.bzl +++ b/python/private/local_runtime_repo_setup.bzl @@ -137,6 +137,10 @@ def define_local_runtime_toolchain_impl( hdrs = [":includes"], defines = defines, # NOTE: Users should define Py_LIMITED_API=3 srcs = abi3_libraries + additional_dlls, + deps = select({ + "@bazel_tools//src/conditions:windows": [":abi3_interface"], + "//conditions:default": [], + }), ) cc_library( @@ -144,6 +148,10 @@ def define_local_runtime_toolchain_impl( hdrs = [":includes"], defines = defines, srcs = libraries + additional_dlls, + deps = select({ + "@bazel_tools//src/conditions:windows": [":interface"], + "//conditions:default": [], + }), ) # runtime configuration diff --git a/tests/integration/local_toolchains/BUILD.bazel b/tests/integration/local_toolchains/BUILD.bazel index 20ee7bcfe6..ae99e7cf2f 100644 --- a/tests/integration/local_toolchains/BUILD.bazel +++ b/tests/integration/local_toolchains/BUILD.bazel @@ -13,9 +13,8 @@ # limitations under the License. load("@bazel_skylib//rules:common_settings.bzl", "string_flag") -load("@rules_cc//cc:cc_library.bzl", "cc_library") load("@rules_python//python:py_test.bzl", "py_test") -load(":py_extension.bzl", "py_extension") +load("@rules_python//python/cc:py_extension.bzl", "py_extension") py_test( name = "local_runtime_test", @@ -60,28 +59,24 @@ string_flag( ) # Build rules to generate a python extension. -cc_library( - name = "echo_ext_cc", - testonly = True, - srcs = ["echo_ext.cc"], - deps = [ - "@rules_python//python/cc:current_py_cc_headers", - ], - alwayslink = True, -) - py_extension( name = "echo_ext", testonly = True, + srcs = ["echo_ext.cc"], copts = select({ "@rules_cc//cc/compiler:msvc-cl": [], "//conditions:default": ["-fvisibility=hidden"], }), - deps = [":echo_ext_cc"], + imports = ["."], ) py_test( name = "echo_test", srcs = ["echo_test.py"], + # Make this test better respect pyenv/local Python DLLs on Windows + env_inherit = [ + "PYENV_VERSION", + "PATH", + ], deps = [":echo_ext"], ) diff --git a/tests/integration/local_toolchains/echo_test.py b/tests/integration/local_toolchains/echo_test.py index 17121e0f17..655205fe29 100644 --- a/tests/integration/local_toolchains/echo_test.py +++ b/tests/integration/local_toolchains/echo_test.py @@ -5,4 +5,4 @@ class ExtensionTest(unittest.TestCase): def test_echo_extension(self): - self.assertEqual(echo_ext.echo(42, "str"), tuple(42, "str")) + self.assertEqual(echo_ext.echo(42, "str"), (42, "str")) diff --git a/tests/integration/local_toolchains/py_extension.bzl b/tests/integration/local_toolchains/py_extension.bzl deleted file mode 100644 index 5d37fd7824..0000000000 --- a/tests/integration/local_toolchains/py_extension.bzl +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright 2025 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. - -"""Macro to build a python C/C++ extension. - -There are variants of py_extension in many other projects, such as: -* https://github.com/protocolbuffers/protobuf/tree/main/python/py_extension.bzl -* https://github.com/google/riegeli/blob/master/python/riegeli/py_extension.bzl -* https://github.com/pybind/pybind11_bazel/blob/master/build_defs.bzl - -The issue for a generic verion is: -* https://github.com/bazel-contrib/rules_python/issues/824 -""" - -load("@bazel_skylib//rules:copy_file.bzl", "copy_file") -load("@rules_cc//cc:cc_binary.bzl", "cc_binary") -load("@rules_python//python:defs.bzl", "py_library") - -def py_extension( - *, - name, - deps = None, - linkopts = None, - imports = None, - visibility = None, - **kwargs): - """Creates a Python module implemented in C++. - - A Python extension has 2 essential parts: - 1. An internal shared object / pyd package for the extension, `name.pyd`/`name.so` - 2. The py_library target for the extension.` - - Python modules can depend on a py_extension. - - Args: - name: `str`. Name for this target. This is typically the module name. - deps: `list`. Required. C++ libraries to link into the module. - linkopts: `list`. Linking options for the shared library. - imports: `list`. Additional imports for the py_library rule. - visibility: `str`. Visibility for target. - **kwargs: Additional options for the cc_library rule. - """ - if not name: - fail("py_extension requires a name") - if not deps: - fail("py_extension requires a non-empty deps attribute") - if "linkshared" in kwargs: - fail("py_extension attribute linkshared not allowed") - - if not linkopts: - linkopts = [] - - testonly = kwargs.get("testonly") - tags = kwargs.pop("tags", []) - - cc_binary_so_name = name + ".so" - cc_binary_dll_name = name + ".dll" - cc_binary_pyd_name = name + ".pyd" - linker_script_name = name + ".lds" - linker_script_name_rule = name + "_lds" - shared_objects_name = name + "__shared_objects" - - # On Unix, restrict symbol visibility. - exported_symbol = "PyInit_" + name - - # Generate linker script used on non-macOS unix platforms. - native.genrule( - name = linker_script_name_rule, - outs = [linker_script_name], - cmd = "\n".join([ - "cat <<'EOF' >$@", - "{", - " global: " + exported_symbol + ";", - " local: *;", - "};", - "EOF", - ]), - ) - - for cc_binary_name in [cc_binary_dll_name, cc_binary_so_name]: - cur_linkopts = linkopts - cur_deps = deps - if cc_binary_name == cc_binary_so_name: - cur_linkopts = linkopts + select({ - "@platforms//os:macos": [ - # Avoid undefined symbol errors for CPython symbols that - # will be resolved at runtime. - "-undefined", - "dynamic_lookup", - # On macOS, the linker does not support version scripts. Use - # the `-exported_symbol` option instead to restrict symbol - # visibility. - "-Wl,-exported_symbol", - # On macOS, the symbol starts with an underscore. - "-Wl,_" + exported_symbol, - ], - # On non-macOS unix, use a version script to restrict symbol - # visibility. - "//conditions:default": [ - "-Wl,--version-script", - "-Wl,$(location :" + linker_script_name + ")", - ], - }) - cur_deps = cur_deps + select({ - "@platforms//os:macos": [], - "//conditions:default": [linker_script_name], - }) - - cc_binary( - name = cc_binary_name, - linkshared = True, - visibility = ["//visibility:private"], - deps = cur_deps, - tags = tags + ["manual"], - linkopts = cur_linkopts, - **kwargs - ) - - copy_file( - name = cc_binary_pyd_name + "__pyd_copy", - src = ":" + cc_binary_dll_name, - out = cc_binary_pyd_name, - visibility = visibility, - tags = ["manual"], - testonly = testonly, - ) - - native.filegroup( - name = shared_objects_name, - data = select({ - "@platforms//os:windows": [":" + cc_binary_pyd_name], - "//conditions:default": [":" + cc_binary_so_name], - }), - testonly = testonly, - ) - py_library( - name = name, - data = [":" + shared_objects_name], - imports = imports, - tags = tags, - testonly = testonly, - visibility = visibility, - )