Create targets for UPB release
PiperOrigin-RevId: 441496547
diff --git a/WORKSPACE b/WORKSPACE
index 0bd8479..823037b 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,8 +1,9 @@
workspace(name = "upb")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("//bazel:python_downloads.bzl", "python_source_archive", "python_nuget_package")
load("//bazel:workspace_deps.bzl", "upb_deps")
-load("//bazel:workspace_defs.bzl", "system_python")
+load("//bazel:system_python.bzl", "system_python")
upb_deps()
@@ -62,3 +63,42 @@
load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
rules_fuzzing_init()
+
+#Python Downloads
+
+python_source_archive(
+ name = "python-3.7.0",
+ sha256 = "85bb9feb6863e04fb1700b018d9d42d1caac178559ffa453d7e6a436e259fd0d",
+)
+python_nuget_package(
+ name = "nuget_python_i686_3.7.0",
+ sha256 = "a8bb49fa1ca62ad55430fcafaca1b58015e22943e66b1a87d5e7cef2556c6a54",
+)
+python_nuget_package(
+ name = "nuget_python_x86-64_3.7.0",
+ sha256 = "66eb796a5bdb1e6787b8f655a1237a6b6964af2115b7627cf4f0032cf068b4b2",
+)
+python_nuget_package(
+ name = "nuget_python_i686_3.8.0",
+ sha256 = "87a6481f5eef30b42ac12c93f06f73bd0b8692f26313b76a6615d1641c4e7bca",
+)
+python_nuget_package(
+ name = "nuget_python_x86-64_3.8.0",
+ sha256 = "96c61321ce90dd053c8a04f305a5f6cc6d91350b862db34440e4a4f069b708a0",
+)
+python_nuget_package(
+ name = "nuget_python_i686_3.9.0",
+ sha256 = "229abecbe49dc08fe5709e0b31e70edfb3b88f23335ebfc2904c44f940fd59b6",
+)
+python_nuget_package(
+ name = "nuget_python_x86-64_3.9.0",
+ sha256 = "6af58a733e7dfbfcdd50d55788134393d6ffe7ab8270effbf724bdb786558832",
+)
+python_nuget_package(
+ name = "nuget_python_i686_3.10.0",
+ sha256 = "e115e102eb90ce160ab0ef7506b750a8d7ecc385bde0a496f02a54337a8bc333",
+)
+python_nuget_package(
+ name = "nuget_python_x86-64_3.10.0",
+ sha256 = "4474c83c25625d93e772e926f95f4cd398a0abbb52793625fa30f39af3d2cc00",
+)
diff --git a/bazel/py_extension.bzl b/bazel/py_extension.bzl
deleted file mode 100644
index eacd39b..0000000
--- a/bazel/py_extension.bzl
+++ /dev/null
@@ -1,38 +0,0 @@
-load(
- "//bazel:build_defs.bzl",
- "UPB_DEFAULT_COPTS",
-)
-
-def py_extension(name, srcs, deps = []):
- version_script = name + "_version_script.lds"
- symbol = "PyInit_" + name
- native.genrule(
- name = "gen_" + version_script,
- outs = [version_script],
- cmd = "echo 'message { global: " + symbol + "; local: *; };' > $@",
- )
-
- native.cc_binary(
- name = name,
- srcs = srcs,
- copts = UPB_DEFAULT_COPTS + [
- # The Python API requires patterns that are ISO C incompatible, like
- # casts between function pointers and object pointers.
- "-Wno-pedantic",
- ],
- # We use a linker script to hide all symbols except the entry point for
- # the module.
- linkopts = select({
- "@platforms//os:linux": ["-Wl,--version-script,$(location :" + version_script + ")"],
- "@platforms//os:macos": [
- "-Wl,-exported_symbol",
- "-Wl,_" + symbol,
- ],
- }),
- linkshared = True,
- linkstatic = True,
- deps = deps + [
- ":" + version_script,
- "@system_python//:python_headers",
- ],
- )
diff --git a/bazel/pyproto_test_wrapper.bzl b/bazel/pyproto_test_wrapper.bzl
index 629ee9b..3092314 100644
--- a/bazel/pyproto_test_wrapper.bzl
+++ b/bazel/pyproto_test_wrapper.bzl
@@ -9,7 +9,8 @@
main = src,
data = ["@com_google_protobuf//:testdata"],
deps = [
- "//python:message_ext",
+ "//python:_api_implementation",
+ "//python:_message",
"@com_google_protobuf//:python_common_test_protos",
"@com_google_protobuf//:python_specific_test_protos",
"@com_google_protobuf//:python_srcs",
diff --git a/bazel/python_downloads.bzl b/bazel/python_downloads.bzl
new file mode 100644
index 0000000..e237c93
--- /dev/null
+++ b/bazel/python_downloads.bzl
@@ -0,0 +1,84 @@
+"""Helper methods to download different python versions"""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+limited_api_build_file = """
+cc_library(
+ name = "python_headers",
+ hdrs = glob(["**/Include/**/*.h"]),
+ strip_include_prefix = "Python-{}/Include",
+ visibility = ["//visibility:public"],
+)
+"""
+
+def python_source_archive(name, sha256):
+ """Helper method to create a python_headers target that will work for linux and macos.
+
+ Args:
+ name: The name of the target, should be in the form python_{VERSION}
+ sha256: The sha256 of the python package for the specified version
+ """
+ version = name.split("-")[1]
+ http_archive(
+ name = name,
+ urls = [
+ "https://www.python.org/ftp/python/{0}/Python-{0}.tgz"
+ .format(version),
+ ],
+ sha256 = sha256,
+ build_file_content = limited_api_build_file.format(version),
+ patch_cmds = [
+ "echo '#define SIZEOF_WCHAR_T 4' > Python-{}/Include/pyconfig.h"
+ .format(version),
+ ],
+ )
+
+nuget_build_file = """
+cc_import(
+ name = "python_full_api",
+ hdrs = glob(["**/*.h"]),
+ shared_library = "python{0}.dll",
+ interface_library = "libs/python{0}.lib",
+ visibility = ["@upb//python:__pkg__"],
+)
+
+cc_import(
+ name = "python_limited_api",
+ hdrs = glob(["**/*.h"]),
+ shared_library = "python{1}.dll",
+ interface_library = "libs/python{1}.lib",
+ visibility = ["@upb//python:__pkg__"],
+)
+"""
+
+def python_nuget_package(name, sha256):
+ """Helper method to create full and limited api dependencies for windows using nuget
+
+ Args:
+ name: The name of the target, should be in the form nuget_python_{CPU}_{VERSION}
+ sha256: The sha256 of the nuget package for that version
+ """
+ cpu = name.split("_")[2]
+ version = name.split("_")[3]
+
+ full_api_lib_number = version.split(".")[0] + version.split(".")[1]
+ limited_api_lib_number = version.split(".")[0]
+
+ folder_name_dict = {
+ "i686": "pythonx86",
+ "x86-64": "python",
+ }
+
+ http_archive(
+ name = name,
+ urls = [
+ "https://www.nuget.org/api/v2/package/{}/{}"
+ .format(folder_name_dict[cpu], version),
+ ],
+ sha256 = sha256,
+ strip_prefix = "tools",
+ build_file_content =
+ nuget_build_file.format(full_api_lib_number, limited_api_lib_number),
+ type = "zip",
+ patch_cmds = ["cp -r include/* ."],
+ )
diff --git a/bazel/workspace_defs.bzl b/bazel/system_python.bzl
similarity index 86%
rename from bazel/workspace_defs.bzl
rename to bazel/system_python.bzl
index 03655b2..9e20b1f 100644
--- a/bazel/workspace_defs.bzl
+++ b/bazel/system_python.bzl
@@ -27,6 +27,7 @@
_build_file = """
load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
+
cc_library(
name = "python_headers",
hdrs = glob(["python/**/*.h"]),
@@ -36,7 +37,7 @@
py_runtime(
name = "py3_runtime",
- interpreter_path = "%s",
+ interpreter_path = "{}",
python_version = "PY3",
)
@@ -52,6 +53,11 @@
)
"""
+def _get_python_version(repository_ctx):
+ py_program = "import sys; print(str(sys.version_info.major) + str(sys.version_info.minor))"
+ result = repository_ctx.execute(["python3", "-c", py_program])
+ return (result.stdout).strip()
+
def _get_config_var(repository_ctx, name):
py_program = "import sysconfig; print(sysconfig.get_config_var('%s'), end='')"
result = repository_ctx.execute(["python3", "-c", py_program % (name)])
@@ -63,7 +69,9 @@
path = _get_config_var(repository_ctx, "INCLUDEPY")
repository_ctx.symlink(path, "python")
python3 = repository_ctx.which("python3")
- repository_ctx.file("BUILD.bazel", _build_file % python3)
+ python_version = _get_python_version(repository_ctx)
+ repository_ctx.file("BUILD.bazel", _build_file.format(python3))
+ repository_ctx.file("version.bzl", "SYSTEM_PYTHON_VERSION = {}".format(python_version))
# The system_python() repository rule exposes Python headers from the system.
#
diff --git a/bazel/workspace_deps.bzl b/bazel/workspace_deps.bzl
index db3afd7..ca9b756 100644
--- a/bazel/workspace_deps.bzl
+++ b/bazel/workspace_deps.bzl
@@ -14,7 +14,7 @@
maybe(
git_repository,
name = "com_google_protobuf",
- commit = "2f91da585e96a7efe43505f714f03c7716a94ecb",
+ commit = "a69354f31b253856689ae765a9ea3217ec001873",
remote = "https://github.com/protocolbuffers/protobuf.git",
patches = [
"//bazel:protobuf.patch",
diff --git a/cmake/make_cmakelists.py b/cmake/make_cmakelists.py
index 29c73bb..655d809 100755
--- a/cmake/make_cmakelists.py
+++ b/cmake/make_cmakelists.py
@@ -234,6 +234,12 @@
def register_toolchains(self, toolchain):
pass
+ def python_source_archive(self, **kwargs):
+ pass
+
+ def python_nuget_package(self, **kwargs):
+ pass
+
class Converter(object):
def __init__(self):
diff --git a/python/BUILD b/python/BUILD
index ac97113..ac96b6c 100644
--- a/python/BUILD
+++ b/python/BUILD
@@ -24,13 +24,154 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# begin:github_only
-load("//bazel:py_proto_library.bzl", "py_proto_library")
-load("//bazel:py_extension.bzl", "py_extension")
-load("@rules_python//python:packaging.bzl", "py_wheel")
+load("//python:py_extension.bzl", "py_extension")
# end:github_only
+load("@bazel_skylib//lib:selects.bzl", "selects")
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag")
+load("//bazel:build_defs.bzl", "UPB_DEFAULT_COPTS")
+
licenses(["notice"])
+package(
+ default_visibility = ["//python/dist:__pkg__"],
+)
+
+LIMITED_API_FLAG_SELECT = {
+ ":limited_api_3.7": ["-DPy_LIMITED_API=0x03070000"],
+ ":limited_api_3.10": ["-DPy_LIMITED_API=0x030a0000"],
+ "//conditions:default": [],
+}
+
+bool_flag(
+ name = "limited_api",
+ build_setting_default = False,
+)
+
+string_flag(
+ name = "python_version",
+ build_setting_default = "system",
+ values = [
+ "system",
+ "37",
+ "38",
+ "39",
+ "310",
+ ],
+)
+
+config_setting(
+ name = "limited_api_3.7",
+ flag_values = {
+ ":limited_api": "True",
+ ":python_version": "37",
+ },
+)
+
+config_setting(
+ name = "full_api_3.7_win32",
+ flag_values = {
+ ":limited_api": "False",
+ ":python_version": "37",
+ },
+ values = {"cpu": "win32"},
+)
+
+config_setting(
+ name = "full_api_3.7_win64",
+ flag_values = {
+ ":limited_api": "False",
+ ":python_version": "37",
+ },
+ values = {"cpu": "win64"},
+)
+
+selects.config_setting_group(
+ name = "full_api_3.7",
+ match_any = [
+ ":full_api_3.7_win32",
+ ":full_api_3.7_win64",
+ ],
+)
+
+config_setting(
+ name = "full_api_3.8_win32",
+ flag_values = {
+ ":limited_api": "False",
+ ":python_version": "38",
+ },
+ values = {"cpu": "win32"},
+)
+
+config_setting(
+ name = "full_api_3.8_win64",
+ flag_values = {
+ ":limited_api": "False",
+ ":python_version": "38",
+ },
+ values = {"cpu": "win64"},
+)
+
+selects.config_setting_group(
+ name = "full_api_3.8",
+ match_any = [
+ ":full_api_3.8_win32",
+ ":full_api_3.8_win64",
+ ],
+)
+
+config_setting(
+ name = "full_api_3.9_win32",
+ flag_values = {
+ ":limited_api": "False",
+ ":python_version": "39",
+ },
+ values = {"cpu": "win32"},
+)
+
+config_setting(
+ name = "full_api_3.9_win64",
+ flag_values = {
+ ":limited_api": "False",
+ ":python_version": "39",
+ },
+ values = {"cpu": "win64"},
+)
+
+selects.config_setting_group(
+ name = "full_api_3.9",
+ match_any = [
+ "full_api_3.9_win32",
+ ":full_api_3.9_win64",
+ ],
+)
+
+config_setting(
+ name = "limited_api_3.10_win32",
+ flag_values = {
+ ":limited_api": "True",
+ ":python_version": "310",
+ },
+ values = {"cpu": "win32"},
+)
+
+config_setting(
+ name = "limited_api_3.10_win64",
+ flag_values = {
+ ":limited_api": "True",
+ ":python_version": "310",
+ },
+ values = {"cpu": "win64"},
+)
+
+selects.config_setting_group(
+ name = "limited_api_3.10",
+ match_any = [
+ ":limited_api_3.10_win32",
+ ":limited_api_3.10_win64",
+ ],
+)
+
py_extension(
name = "_message",
srcs = [
@@ -50,10 +191,15 @@
"message.h",
"protobuf.c",
"protobuf.h",
- "python.h",
+ "python_api.h",
"repeated.c",
"repeated.h",
],
+ copts = UPB_DEFAULT_COPTS + select(LIMITED_API_FLAG_SELECT) + [
+ # The Python API requires patterns that are ISO C incompatible, like
+ # casts between function pointers and object pointers.
+ "-Wno-pedantic",
+ ],
deps = [
"//:descriptor_upb_proto_reflection",
"//:reflection",
@@ -68,97 +214,13 @@
py_extension(
name = "_api_implementation",
- srcs = ["api_implementation.c"],
-)
-
-# begin:github_only
-
-py_test(
- name = "minimal_test",
srcs = [
- "minimal_test.py",
+ "api_implementation.c",
+ "python_api.h",
],
- imports = ["."],
- legacy_create_init = False,
- deps = [
- "//python:message_ext",
- "@com_google_protobuf//:python_common_test_protos",
- "@com_google_protobuf//:python_specific_test_protos",
- "@com_google_protobuf//:python_srcs",
+ copts = UPB_DEFAULT_COPTS + select(LIMITED_API_FLAG_SELECT) + [
+ # The Python API requires patterns that are ISO C incompatible, like
+ # casts between function pointers and object pointers.
+ "-Wno-pedantic",
],
)
-
-# Copy the extensions into the location recognized by Python.
-# .abi3.so indicates use of the limited API, and cross-version ABI compatibility.
-EXT_SUFFIX = ".abi3.so"
-
-genrule(
- name = "copy_message",
- srcs = [":_message"],
- outs = ["google/protobuf/pyext/_message" + EXT_SUFFIX],
- cmd = "cp $< $@",
-)
-
-genrule(
- name = "copy_api_implementation",
- srcs = [":_api_implementation"],
- outs = ["google/protobuf/internal/_api_implementation" + EXT_SUFFIX],
- cmd = "cp $< $@",
- visibility = ["//python:__subpackages__"],
-)
-
-filegroup(
- name = "extension_files",
- srcs = [
- "google/protobuf/pyext/_message" + EXT_SUFFIX,
- "google/protobuf/internal/_api_implementation" + EXT_SUFFIX,
- ],
-)
-
-py_library(
- name = "message_ext",
- data = [":extension_files"],
- imports = ["."],
- visibility = ["//python:__subpackages__"],
-)
-
-py_proto_library(
- name = "well_known_proto_pb2",
- deps = [
- "@com_google_protobuf//:any_proto",
- "@com_google_protobuf//:api_proto",
- "@com_google_protobuf//:compiler_plugin_proto",
- "@com_google_protobuf//:descriptor_proto",
- "@com_google_protobuf//:duration_proto",
- "@com_google_protobuf//:empty_proto",
- "@com_google_protobuf//:field_mask_proto",
- "@com_google_protobuf//:source_context_proto",
- "@com_google_protobuf//:struct_proto",
- "@com_google_protobuf//:timestamp_proto",
- "@com_google_protobuf//:type_proto",
- "@com_google_protobuf//:wrappers_proto",
- ],
-)
-
-py_wheel(
- name = "binary_wheel",
- abi = "abi3",
- distribution = "protobuf",
- # TODO(https://github.com/protocolbuffers/upb/issues/502): we need to make
- # this a select() that is calculated from the platform we are actually
- # building on.
- platform = "manylinux2014_x86_64",
- python_tag = "cp36",
- strip_path_prefixes = ["python/"],
- version = "4.20.0",
- deps = [
- ":extension_files",
- ":well_known_proto_pb2",
- # TODO(https://github.com/protocolbuffers/upb/issues/503): currently
- # this includes the unit tests. We should filter these out so we are
- # only distributing true source files.
- "@com_google_protobuf//:python_srcs",
- ],
-)
-
-# end:github_only
diff --git a/python/api_implementation.c b/python/api_implementation.c
index 9f96b93..744f2be 100644
--- a/python/api_implementation.c
+++ b/python/api_implementation.c
@@ -25,7 +25,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <Python.h>
+#include "python/python_api.h"
static struct PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
diff --git a/python/descriptor.h b/python/descriptor.h
index 6cb6c1c..581e1c7 100644
--- a/python/descriptor.h
+++ b/python/descriptor.h
@@ -30,7 +30,7 @@
#include <stdbool.h>
-#include "python/python.h"
+#include "python/python_api.h"
#include "upb/def.h"
typedef enum {
diff --git a/python/dist/BUILD.bazel b/python/dist/BUILD.bazel
new file mode 100644
index 0000000..33e6287
--- /dev/null
+++ b/python/dist/BUILD.bazel
@@ -0,0 +1,152 @@
+# Copyright (c) 2009-2022, Google LLC
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google LLC nor the
+# names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+load("//bazel:py_proto_library.bzl", "py_proto_library")
+load(":dist.bzl", "py_dist", "py_dist_module")
+load("@bazel_skylib//lib:selects.bzl", "selects")
+load("@com_google_protobuf//:protobuf_version.bzl", "PROTOBUF_VERSION")
+load("@rules_python//python:packaging.bzl", "py_wheel")
+
+licenses(["notice"])
+
+py_dist_module(
+ name = "message_mod",
+ extension = "//python:_message_binary",
+ module_name = "google.protobuf.pyext._message",
+)
+
+py_dist_module(
+ name = "api_implementation_mod",
+ extension = "//python:_api_implementation_binary",
+ module_name = "google.protobuf.internal.api_implementation",
+)
+
+py_proto_library(
+ name = "well_known_proto_py_pb2",
+ deps = [
+ "@com_google_protobuf//:any_proto",
+ "@com_google_protobuf//:api_proto",
+ "@com_google_protobuf//:compiler_plugin_proto",
+ "@com_google_protobuf//:descriptor_proto",
+ "@com_google_protobuf//:duration_proto",
+ "@com_google_protobuf//:empty_proto",
+ "@com_google_protobuf//:field_mask_proto",
+ "@com_google_protobuf//:source_context_proto",
+ "@com_google_protobuf//:struct_proto",
+ "@com_google_protobuf//:timestamp_proto",
+ "@com_google_protobuf//:type_proto",
+ "@com_google_protobuf//:wrappers_proto",
+ ],
+)
+
+config_setting(
+ name = "aarch64_cpu",
+ values = {"cpu": "linux-aarch_64"},
+)
+
+config_setting(
+ name = "x86_64_cpu",
+ values = {"cpu": "linux-x86_64"},
+)
+
+config_setting(
+ name = "osx-x86_64_cpu",
+ values = {"cpu": "osx-x86_64"},
+)
+
+config_setting(
+ name = "win32_cpu",
+ values = {"cpu": "win32"},
+)
+
+config_setting(
+ name = "win64_cpu",
+ values = {"cpu": "win64"},
+)
+
+py_wheel(
+ name = "binary_wheel",
+ abi = select({
+ "//python:full_api_3.7": "cp37m",
+ "//python:full_api_3.8": "cp38",
+ "//python:full_api_3.9": "cp39",
+ "//conditions:default": "abi3",
+ }),
+ distribution = "protobuf",
+ platform = select({
+ ":x86_64_cpu": "manylinux2014_x86_64",
+ ":aarch64_cpu": "manylinux2014_aarch64",
+ ":osx-x86_64_cpu": "macosx_10_9_universal",
+ ":win32_cpu": "win32",
+ ":win64_cpu": "win_amd64",
+ "//conditions:default": "any",
+ }),
+ python_tag = selects.with_or({
+ ("//python:limited_api_3.7", "//python:full_api_3.7"): "cp37",
+ "//python:full_api_3.8": "cp38",
+ "//python:full_api_3.9": "cp39",
+ "//python:limited_api_3.10": "cp310",
+ "//conditions:default": "system",
+ }),
+ strip_path_prefixes = ["python/"],
+ version = PROTOBUF_VERSION,
+ deps = [
+ ":message_mod",
+ ":api_implementation_mod",
+ ":well_known_proto_py_pb2",
+ #TODO(https://github.com/protocolbuffers/upb/issues/503): currently
+ # this includes the unit tests. We should filter these out so we are
+ # only distributing true source files.
+ "@com_google_protobuf//:python_srcs",
+ ],
+)
+
+py_dist(
+ name = "dist",
+ binary_wheel = ":binary_wheel",
+ full_api_cpus = [
+ "win32",
+ "win64",
+ ],
+ # Windows needs version-specific wheels until 3.10.
+ full_api_versions = [
+ "37",
+ "38",
+ "39",
+ ],
+ # Limited API: these wheels will satisfy any Python version >= the
+ # given version.
+ #
+ # Technically the limited API doesn't have the functions we need until
+ # 3.10, but on Linux we can get away with using 3.7 (see ../python_api.h for
+ # details).
+ limited_api_wheels = {
+ "win32": "310",
+ "win64": "310",
+ "linux-x86_64": "37",
+ "linux-aarch_64": "37",
+ },
+ tags = ["manual"],
+)
diff --git a/python/dist/dist.bzl b/python/dist/dist.bzl
new file mode 100644
index 0000000..6760a38
--- /dev/null
+++ b/python/dist/dist.bzl
@@ -0,0 +1,145 @@
+"""Rules to create python distribution files and properly name them"""
+
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
+load("@system_python//:version.bzl", "SYSTEM_PYTHON_VERSION")
+
+def _get_suffix(limited_api, python_version, cpu):
+ suffix = "pyd" if ("win" in cpu) else "so"
+
+ if limited_api == True:
+ if "win" not in cpu:
+ suffix = "abi3." + suffix
+ return "." + suffix
+
+ if "win32" in cpu or "win64" in cpu:
+ if "win32" in cpu:
+ abi = "win32"
+ elif "win64" in cpu:
+ abi = "win_amd64"
+ else:
+ fail("Unsupported CPU: " + cpu)
+ return ".cp{}-{}.{}".format(python_version, abi, suffix)
+
+ if python_version == "system":
+ python_version = SYSTEM_PYTHON_VERSION
+ if int(python_version) < 38:
+ python_version += "m"
+ abis = {
+ "darwin": "darwin",
+ "osx-x86_64": "darwin",
+ "osx-aarch_64": "darwin",
+ "linux-aarch_64": "aarch64-linux-gnu",
+ "linux-x86_64": "x86_64-linux-gnu",
+ "k8": "x86_64-linux-gnu",
+ }
+
+ return ".cpython-{}-{}.{}".format(python_version, abis[cpu], suffix)
+
+ fail("Unsupported combination of flags")
+
+def _py_dist_module_impl(ctx):
+ base_filename = ctx.attr.module_name.replace(".", "/")
+ suffix = _get_suffix(
+ limited_api = ctx.attr._limited_api[BuildSettingInfo].value,
+ python_version = ctx.attr._python_version[BuildSettingInfo].value,
+ cpu = ctx.var["TARGET_CPU"],
+ )
+ filename = base_filename + suffix
+ file = ctx.actions.declare_file(filename)
+ src = ctx.attr.extension[DefaultInfo].files.to_list()[0]
+ ctx.actions.run(
+ executable = "cp",
+ arguments = [src.path, file.path],
+ inputs = [src],
+ outputs = [file],
+ )
+ return [
+ DefaultInfo(files = depset([file])),
+ ]
+
+_py_dist_module_rule = rule(
+ output_to_genfiles = True,
+ implementation = _py_dist_module_impl,
+ fragments = ["cpp"],
+ attrs = {
+ "module_name": attr.string(mandatory = True),
+ "extension": attr.label(
+ mandatory = True,
+ providers = [CcInfo],
+ ),
+ "_limited_api": attr.label(default = "//python:limited_api"),
+ "_python_version": attr.label(default = "//python:python_version"),
+ "_cc_toolchain": attr.label(
+ default = "@bazel_tools//tools/cpp:current_cc_toolchain",
+ ),
+ },
+)
+
+def py_dist_module(name, module_name, extension):
+ file_rule = name + "_file"
+ _py_dist_module_rule(
+ name = file_rule,
+ module_name = module_name,
+ extension = extension,
+ )
+
+ # TODO(haberman): needed?
+ native.py_library(
+ name = name,
+ data = [":" + file_rule],
+ imports = ["."],
+ )
+
+def _py_dist_transition_impl(settings, attr):
+ _ignore = (settings) # @unused
+ transitions = []
+
+ for cpu, version in attr.limited_api_wheels.items():
+ transitions.append({
+ "//command_line_option:cpu": cpu,
+ "//python:python_version": version,
+ "//python:limited_api": True,
+ })
+
+ for version in attr.full_api_versions:
+ for cpu in attr.full_api_cpus:
+ transitions.append({
+ "//command_line_option:cpu": cpu,
+ "//python:python_version": version,
+ "//python:limited_api": False,
+ })
+
+ return transitions
+
+_py_dist_transition = transition(
+ implementation = _py_dist_transition_impl,
+ inputs = [],
+ outputs = [
+ "//command_line_option:cpu",
+ "//python:python_version",
+ "//python:limited_api",
+ ],
+)
+
+def _py_dist_impl(ctx):
+ return [
+ DefaultInfo(files = depset(
+ transitive = [dep[DefaultInfo].files for dep in ctx.attr.binary_wheel],
+ )),
+ ]
+
+py_dist = rule(
+ implementation = _py_dist_impl,
+ attrs = {
+ "binary_wheel": attr.label(
+ mandatory = True,
+ cfg = _py_dist_transition,
+ ),
+ "limited_api_wheels": attr.string_dict(),
+ "full_api_versions": attr.string_list(),
+ "full_api_cpus": attr.string_list(),
+ "_allowlist_function_transition": attr.label(
+ default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
+ ),
+ },
+)
diff --git a/python/extension_dict.h b/python/extension_dict.h
index f8a8fc4..a21822c 100644
--- a/python/extension_dict.h
+++ b/python/extension_dict.h
@@ -30,7 +30,7 @@
#include <stdbool.h>
-#include "python/python.h"
+#include "python/python_api.h"
PyObject* PyUpb_ExtensionDict_New(PyObject* msg);
diff --git a/python/map.h b/python/map.h
index 0c03ed0..aaa4e20 100644
--- a/python/map.h
+++ b/python/map.h
@@ -30,7 +30,7 @@
#include <stdbool.h>
-#include "python/python.h"
+#include "python/python_api.h"
#include "upb/def.h"
// Creates a new repeated field stub for field `f` of message object `parent`.
diff --git a/python/protobuf.h b/python/protobuf.h
index 0b87646..262f33e 100644
--- a/python/protobuf.h
+++ b/python/protobuf.h
@@ -31,7 +31,7 @@
#include <stdbool.h>
#include "python/descriptor.h"
-#include "python/python.h"
+#include "python/python_api.h"
#include "upb/table_internal.h"
diff --git a/python/py_extension.bzl b/python/py_extension.bzl
new file mode 100644
index 0000000..2fea049
--- /dev/null
+++ b/python/py_extension.bzl
@@ -0,0 +1,70 @@
+"""Macro to support py_extension
+"""
+
+def py_extension(name, srcs, copts, deps = []):
+ """Creates a C++ library to extend python
+
+ Args:
+ name: Name of the target
+ srcs: List of source files to create the target
+ copts: List of C++ compile options to use
+ deps: Libraries that the target depends on
+ """
+
+ version_script = name + "_version_script.lds"
+ symbol = "PyInit_" + name
+ native.genrule(
+ name = "gen_" + version_script,
+ outs = [version_script],
+ cmd = "echo 'message { global: " + symbol + "; local: *; };' > $@",
+ )
+
+ native.cc_binary(
+ name = name + "_binary",
+ srcs = srcs,
+ copts = copts,
+ linkopts = select({
+ "//python/dist:osx-x86_64_cpu": ["-undefined", "dynamic_lookup"],
+ "//conditions:default": [],
+ }),
+ linkshared = True,
+ linkstatic = True,
+ deps = deps + [
+ ":" + version_script,
+ ] + select({
+ "//python:limited_api_3.7": ["@python-3.7.0//:python_headers"],
+ "//python:full_api_3.7_win32": ["@nuget_python_i686_3.7.0//:python_full_api"],
+ "//python:full_api_3.7_win64": ["@nuget_python_x86-64_3.7.0//:python_full_api"],
+ "//python:full_api_3.8_win32": ["@nuget_python_i686_3.8.0//:python_full_api"],
+ "//python:full_api_3.8_win64": ["@nuget_python_x86-64_3.8.0//:python_full_api"],
+ "//python:full_api_3.9_win32": ["@nuget_python_i686_3.9.0//:python_full_api"],
+ "//python:full_api_3.9_win64": ["@nuget_python_x86-64_3.9.0//:python_full_api"],
+ "//python:limited_api_3.10_win32": ["@nuget_python_i686_3.10.0//:python_limited_api"],
+ "//python:limited_api_3.10_win64": ["@nuget_python_x86-64_3.10.0//:python_limited_api"],
+ "//conditions:default": ["@system_python//:python_headers"],
+ }),
+ )
+
+ EXT_SUFFIX = ".abi3.so"
+
+ module_name_map = {
+ "_message": "pyext",
+ "_api_implementation": "internal",
+ }
+
+ output_file = "google/protobuf/" + module_name_map[name] + "/" + name + EXT_SUFFIX
+
+ native.genrule(
+ name = "copy" + name,
+ srcs = [":" + name + "_binary"],
+ outs = [output_file],
+ cmd = "cp $< $@",
+ visibility = ["//python:__subpackages__"],
+ )
+
+ native.py_library(
+ name = name,
+ data = [output_file],
+ imports = ["."],
+ visibility = ["//python:__subpackages__"],
+ )
diff --git a/python/python.h b/python/python_api.h
similarity index 61%
rename from python/python.h
rename to python/python_api.h
index 7eef3e5..e8a8fcf 100644
--- a/python/python.h
+++ b/python/python_api.h
@@ -28,15 +28,33 @@
#ifndef PYUPB_PYTHON_H__
#define PYUPB_PYTHON_H__
-// We restrict ourselves to the limited API, so that we will be ABI-compatible
-// with any version of Python >= 3.6.1 (3.6.1 introduce PySlice_Unpack())
-#define Py_LIMITED_API 0x03060100
-#include <Python.h>
+// We restrict ourselves to the limited API, so that a single build can be
+// ABI-compatible with a wide range of Python versions.
+//
+// The build system will define Py_LIMITED_API as appropriate (see BUILD). We
+// only want to define it for our distribution packages, since we can do some
+// extra assertions when Py_LIMITED_API is not defined. Also Py_LIMITED_API is
+// incompatible with Py_DEBUG.
-// This function was not officially added to the limited API until Python 3.10.
-// But in practice it has been stable since Python 3.1. See:
+// #define Py_LIMITED_API <val> // Defined by build system when appropriate.
+
+#include "Python.h"
+
+// Ideally we could restrict ourselves to the limited API of 3.7, but this is
+// a very important function that was not officially added to the limited API
+// until 3.10. Without this function, there is no way of getting data from a
+// Python `str` object without a copy.
+//
+// While this function was not *officially* added to the limited API until
+// Python 3.10, In practice it has been stable since Python 3.1.
// https://bugs.python.org/issue41784
+//
+// On Linux, ELF lets us get away with using this function with the limited
+// API prior to 3.10.
+
+#if defined(__linux__) && defined(Py_LIMITED_API) && Py_LIMITED_API < 0x03100000
PyAPI_FUNC(const char*)
PyUnicode_AsUTF8AndSize(PyObject* unicode, Py_ssize_t* size);
+#endif
#endif // PYUPB_PYTHON_H__
diff --git a/python/repeated.h b/python/repeated.h
index 9cde3ea..5d74bd2 100644
--- a/python/repeated.h
+++ b/python/repeated.h
@@ -30,7 +30,7 @@
#include <stdbool.h>
-#include "python/python.h"
+#include "python/python_api.h"
#include "upb/def.h"
// Creates a new repeated field stub for field `f` of message object `parent`.