diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 093d2e28f..dcd4364da 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -44,14 +44,10 @@ jobs: output-dir: wheelhouse env: CIBW_ARCHS: ${{ matrix.arch }} - # Drop EOL CPython 3.8 (also avoids the macOS x86_64-only-installer warning) CIBW_SKIP: "cp38-*" CIBW_TEST_REQUIRES: pytest pytest-asyncio CIBW_TEST_COMMAND: pytest {project} - # Force libcapnp to build for the target arch on macOS (the runner - # is arm64, so without this an x86_64 wheel ends up linking against - # an arm64 libkj/libcapnp and failing to load at test time). - CMAKE_OSX_ARCHITECTURES: "${{ runner.os == 'macOS' && matrix.arch || '' }}" + CIBW_BEFORE_ALL_LINUX: "pip install conan && conan profile detect --force && conan install --requires=m4/1.4.19 --build=m4*" - uses: actions/upload-artifact@v6 with: @@ -62,7 +58,7 @@ jobs: # on release tags rather than every push/PR. build_wheels_exotic: name: Build wheels (exotic) ${{ matrix.arch }} - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/conan-py-build' runs-on: ubuntu-latest strategy: fail-fast: false @@ -81,6 +77,7 @@ jobs: env: CIBW_ARCHS: ${{ matrix.arch }} CIBW_SKIP: "cp38-*" + CIBW_BEFORE_ALL_LINUX: "pip install conan && conan profile detect --force && conan install --requires=m4/1.4.19 --build=m4*" # Tests under QEMU are extremely slow; skip them for these arches. CIBW_TEST_SKIP: "*" diff --git a/.gitignore b/.gitignore index 428343efe..d692ce664 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ nosetests.xml # Cpp files capnp/*.cpp -capnp/version.py MANIFEST docs/_build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..8a6c13ddd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.15) +project(pycapnp LANGUAGES CXX) + +find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module) +# FindPython3 skips SOABI detection when CMAKE_OSX_ARCHITECTURES differs from the runner +# arch (e.g. x86_64 wheel on arm64 runner), leaving Python3_SOABI empty. On macOS, +# Rosetta 2 can transparently execute the cross-compile target Python, so re-derive it. +if(APPLE AND NOT Python3_SOABI) + execute_process( + COMMAND "${Python3_EXECUTABLE}" -c + "import sysconfig; print(sysconfig.get_config_var('SOABI') or '', end='')" + OUTPUT_VARIABLE Python3_SOABI + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() +find_package(CapnProto REQUIRED) + +set(CAPNP_PYX "${CMAKE_SOURCE_DIR}/capnp/lib/capnp.pyx") +set(CAPNP_CPP "${CMAKE_CURRENT_BINARY_DIR}/capnp/lib/capnp.cpp") + +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/capnp/lib") + +add_custom_command( + OUTPUT "${CAPNP_CPP}" + COMMAND "${Python3_EXECUTABLE}" -m cython + --cplus + -I "${CMAKE_SOURCE_DIR}" + "${CAPNP_PYX}" + -o "${CAPNP_CPP}" + DEPENDS "${CAPNP_PYX}" + "${CMAKE_SOURCE_DIR}/capnp/includes/capnp_cpp.pxd" + "${CMAKE_SOURCE_DIR}/capnp/includes/schema_cpp.pxd" + "${CMAKE_SOURCE_DIR}/capnp/helpers/helpers.pxd" + "${CMAKE_SOURCE_DIR}/capnp/lib/capnp.pxd" + COMMENT "Running Cython on capnp/lib/capnp.pyx" +) + +Python3_add_library(capnp_ext MODULE WITH_SOABI + "${CAPNP_CPP}" + "${CMAKE_SOURCE_DIR}/capnp/helpers/capabilityHelper.cpp" + "${CMAKE_SOURCE_DIR}/capnp/includes/PyCustomMessageBuilder.cpp" +) + +set_target_properties(capnp_ext PROPERTIES + OUTPUT_NAME "capnp" + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON +) + +target_include_directories(capnp_ext PRIVATE + "${CMAKE_SOURCE_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_SOURCE_DIR}/capnp/includes" +) + +target_link_libraries(capnp_ext PRIVATE + CapnProto::capnpc + CapnProto::capnp-rpc + CapnProto::capnp + CapnProto::kj-async + CapnProto::kj +) + +install(TARGETS capnp_ext DESTINATION capnp/lib) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/capnp/lib/capnp_api.h" + DESTINATION capnp/lib) diff --git a/_custom_build/backend.py b/_custom_build/backend.py deleted file mode 100644 index 883035433..000000000 --- a/_custom_build/backend.py +++ /dev/null @@ -1,29 +0,0 @@ -import sys - -from setuptools.build_meta import * # noqa: F401, F403 -from setuptools.build_meta import build_wheel - -backend_class = build_wheel.__self__.__class__ - - -class _CustomBuildMetaBackend(backend_class): - def run_setup(self, setup_script="setup.py"): - if self.config_settings: - flags = [] - if self.config_settings.get("force-bundled-libcapnp"): - flags.append("--force-bundled-libcapnp") - if self.config_settings.get("force-system-libcapnp"): - flags.append("--force-system-libcapnp") - if self.config_settings.get("libcapnp-url"): - flags.append("--libcapnp-url") - flags.append(self.config_settings["libcapnp-url"]) - if flags: - sys.argv = sys.argv[:1] + ["build_ext"] + flags + sys.argv[1:] - return super().run_setup(setup_script) - - def build_wheel(self, wheel_directory, config_settings=None, metadata_directory=None): - self.config_settings = config_settings - return super().build_wheel(wheel_directory, config_settings, metadata_directory) - - -build_wheel = _CustomBuildMetaBackend().build_wheel diff --git a/buildutils/__init__.py b/buildutils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/buildutils/build.py b/buildutils/build.py deleted file mode 100644 index e79bdf09f..000000000 --- a/buildutils/build.py +++ /dev/null @@ -1,87 +0,0 @@ -"Build the bundled capnp distribution" - -import subprocess -import os -import shutil -import struct -import sys - - -def build_libcapnp(bundle_dir, build_dir): # noqa: C901 - """ - Build capnproto - """ - bundle_dir = os.path.abspath(bundle_dir) - capnp_dir = os.path.join(bundle_dir, "capnproto-c++") - build_dir = os.path.abspath(build_dir) - tmp_dir = os.path.join(capnp_dir, "build{}".format(8 * struct.calcsize("P"))) - - # Clean the tmp build directory every time - if os.path.exists(tmp_dir): - shutil.rmtree(tmp_dir) - os.mkdir(tmp_dir) - - cxxflags = os.environ.get("CXXFLAGS", None) - ldflags = os.environ.get("LDFLAGS", None) - os.environ["CXXFLAGS"] = (cxxflags or "") + " -O2 -DNDEBUG" - os.environ["LDFLAGS"] = ldflags or "" - - # Enable ninja for compilation if available - build_type = [] - if shutil.which("ninja") and os.name != "nt": - build_type = ["-G", "Ninja"] - - # Determine python shell architecture for Windows - python_arch = 8 * struct.calcsize("P") - build_arch = [] - build_flags = [] - if os.name == "nt": - if python_arch == 64: - build_arch_flag = "x64" - elif python_arch == 32: - build_arch_flag = "Win32" - else: - raise RuntimeError("Unknown windows build arch") - build_arch = ["-A", build_arch_flag] - build_flags = ["--config", "Release"] - print("Building module for {}".format(python_arch)) - - if not shutil.which("cmake"): - raise RuntimeError("Could not find cmake in your path!") - - args = [ - "cmake", - "-DCMAKE_POSITION_INDEPENDENT_CODE=1", - "-DBUILD_TESTING=OFF", - "-DBUILD_SHARED_LIBS=OFF", - "-DCMAKE_INSTALL_PREFIX:PATH={}".format(build_dir), - capnp_dir, - ] - args.extend(build_type) - args.extend(build_arch) - conf = subprocess.Popen(args, cwd=tmp_dir, stdout=sys.stdout) - returncode = conf.wait() - if returncode != 0: - raise RuntimeError("CMake failed {}".format(returncode)) - - # Run build through cmake - args = [ - "cmake", - "--build", - ".", - "--target", - "install", - ] - args.extend(build_flags) - build = subprocess.Popen(args, cwd=tmp_dir, stdout=sys.stdout) - returncode = build.wait() - if cxxflags is None: - del os.environ["CXXFLAGS"] - else: - os.environ["CXXFLAGS"] = cxxflags - if ldflags is None: - del os.environ["LDFLAGS"] - else: - os.environ["LDFLAGS"] = ldflags - if returncode != 0: - raise RuntimeError("capnproto compilation failed: {}".format(returncode)) diff --git a/buildutils/bundle.py b/buildutils/bundle.py deleted file mode 100644 index f6c28eea5..000000000 --- a/buildutils/bundle.py +++ /dev/null @@ -1,96 +0,0 @@ -"""utilities for fetching build dependencies.""" - -# -# Copyright (C) PyZMQ Developers -# Distributed under the terms of the Modified BSD License. -# -# This bundling code is largely adapted from pyzmq-static's get.sh by -# Brandon Craig-Rhodes, which is itself BSD licensed. -# -# Adapted for use in pycapnp from pyzmq. See https://github.com/zeromq/pyzmq -# for original project. - -import fileinput # noqa -import os -import shutil -import tarfile - -from urllib.request import urlopen - -pjoin = os.path.join - - -# -# Constants -# - - -bundled_version = (1, 0, 1) -libcapnp_name = "capnproto-c++-%i.%i.%i.tar.gz" % (bundled_version) -libcapnp_url = "https://capnproto.org/" + libcapnp_name - -HERE = os.path.dirname(__file__) -ROOT = os.path.dirname(HERE) - - -# -# Utilities -# - - -def untgz(archive): - """Remove .tar.gz""" - return archive.replace(".tar.gz", "") - - -def localpath(*args): - """construct an absolute path from a list relative to the root pycapnp directory""" - plist = [ROOT] + list(args) - return os.path.abspath(pjoin(*plist)) - - -def fetch_archive(savedir, url, force=False): - """download an archive to a specific location""" - req = urlopen(url) - # Lookup filename - fname = req.info().get_filename() - if not fname: - fname = os.path.basename(url) - dest = pjoin(savedir, fname) - if os.path.exists(dest) and not force: - print("already have %s" % fname) - return dest - print("fetching %s into %s" % (url, savedir)) - if not os.path.exists(savedir): - os.makedirs(savedir) - with open(dest, "wb") as f: - f.write(req.read()) - return dest - - -# -# libcapnp -# - - -def fetch_libcapnp(savedir, url=None): - """download and extract libcapnp""" - is_preconfigured = False - if url is None: - url = libcapnp_url - is_preconfigured = True - dest = pjoin(savedir, "capnproto-c++") - if os.path.exists(dest): - print("already have %s" % dest) - return - fname = fetch_archive(savedir, url) - tf = tarfile.open(fname) - with_version = pjoin(savedir, tf.firstmember.path) - tf.extractall(savedir) - tf.close() - # remove version suffix: - if is_preconfigured: - shutil.move(with_version, dest) - else: - cpp_dir = os.path.join(with_version, "c++") - shutil.move(cpp_dir, dest) diff --git a/capnp/version.py b/capnp/version.py new file mode 100644 index 000000000..97450bef5 --- /dev/null +++ b/capnp/version.py @@ -0,0 +1,9 @@ +from importlib.metadata import version as _pkg_version + +from .lib.capnp import _CAPNP_VERSION_MAJOR as LIBCAPNP_VERSION_MAJOR # noqa: F401 +from .lib.capnp import _CAPNP_VERSION_MINOR as LIBCAPNP_VERSION_MINOR # noqa: F401 +from .lib.capnp import _CAPNP_VERSION_MICRO as LIBCAPNP_VERSION_MICRO # noqa: F401 +from .lib.capnp import _CAPNP_VERSION as LIBCAPNP_VERSION # noqa: F401 + +version = _pkg_version("pycapnp") +short_version = version diff --git a/conan-host.profile b/conan-host.profile new file mode 100644 index 000000000..c88ead274 --- /dev/null +++ b/conan-host.profile @@ -0,0 +1,14 @@ +{% set cibw_arch = os.environ.get("CIBW_ARCHS", "") %} +{% set conan_arch = {"x86_64": "x86_64", "arm64": "armv8", "x86": "x86"}.get(cibw_arch, "") %} +{% set dt = os.environ.get("MACOSX_DEPLOYMENT_TARGET", "").replace(".", "_") %} +include(default) +{% if conan_arch %} + +[settings] +arch={{ conan_arch }} +{% endif %} +{% if platform.system() == 'Darwin' and cibw_arch in ("x86_64", "arm64") and dt %} + +[buildenv] +WHEEL_ARCH=macosx_{{ dt }}_{{ cibw_arch }} +{% endif %} diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 000000000..3aad5c616 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,37 @@ +import os +import sys +import sysconfig + +from conan import ConanFile +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout +from conan.tools.files import copy + + +class PycapnpConan(ConanFile): + name = "pycapnp" + settings = "os", "compiler", "build_type", "arch" + + def requirements(self): + self.requires("capnproto/1.0.1") + + def layout(self): + cmake_layout(self) + + def generate(self): + tc = CMakeToolchain(self) + tc.cache_variables["Python3_EXECUTABLE"] = sys.executable + tc.cache_variables["Python3_INCLUDE_DIR"] = sysconfig.get_path("include") + tc.generate() + CMakeDeps(self).generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + capnp_include = os.path.join(str(self.dependencies["capnproto"].package_folder), "include", "capnp") + # excludes="*/*" skips subdirectories, top-level schemas only + copy(self, "*.capnp", src=capnp_include, dst=os.path.join(self.package_folder, "capnp"), excludes="*/*") diff --git a/pyproject.toml b/pyproject.toml index 925cc2238..19e9ee161 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,52 @@ [build-system] -requires = ["setuptools", "wheel", "pkgconfig", "cython>=3.0"] -build-backend = "backend" -backend-path = ["_custom_build"] +requires = ["conan-py-build>=0.5.3", "cython>=3.0"] +build-backend = "conan_py_build.build" + +[project] +name = "pycapnp" +version = "2.2.3" +description = "A cython wrapping of the C++ Cap'n Proto library" +readme = "README.md" +requires-python = ">=3.8" +license = "BSD-2-Clause" +license-files = ["LICENSE.md"] +authors = [ + {name = "Jacob Alexander", email = "haata@kiibohd.com"}, +] +keywords = ["capnp", "capnproto", "Cap'n Proto", "pycapnp"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows :: Windows 10", + "Operating System :: POSIX", + "Programming Language :: C++", + "Programming Language :: Cython", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Communications", +] + +[project.urls] +Homepage = "https://github.com/capnproto/pycapnp" + +[project.scripts] +capnpc-cython = "capnp._gen:main" + +[tool.conan-py-build] +extra-arguments = ["-o=capnproto/*:with_openssl=False", "-o=capnproto/*:with_zlib=False"] +extra-profile-host = "conan-host.profile" + +[tool.conan-py-build.wheel] +packages = ["capnp"] +exclude = ["**/*.cpp"] + +[tool.conan-py-build.sdist] +include = ["capnp", "CHANGELOG.md", "MANIFEST.in", "requirements.txt"] [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 04ef73cab..000000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[metadata] -description_file = README.md -license_files = LICENSE.md diff --git a/setup.py b/setup.py deleted file mode 100644 index 4ec9c47c7..000000000 --- a/setup.py +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env python -""" -pycapnp distutils setup.py -""" - -import glob -import os -import shutil -import struct -import sys - -import pkgconfig - -from distutils.command.clean import clean as _clean - -from setuptools import setup, Extension - -_this_dir = os.path.dirname(__file__) -sys.path.insert(1, _this_dir) - -from buildutils.build import build_libcapnp -from buildutils.bundle import fetch_libcapnp - -MAJOR = 2 -MINOR = 2 -MICRO = 3 -TAG = "" -VERSION = "%d.%d.%d%s" % (MAJOR, MINOR, MICRO, TAG) - - -# Write version info -def write_version_py(filename=None): - """ - Generate pycapnp version - """ - cnt = """\ -from .lib.capnp import _CAPNP_VERSION_MAJOR as LIBCAPNP_VERSION_MAJOR # noqa: F401 -from .lib.capnp import _CAPNP_VERSION_MINOR as LIBCAPNP_VERSION_MINOR # noqa: F401 -from .lib.capnp import _CAPNP_VERSION_MICRO as LIBCAPNP_VERSION_MICRO # noqa: F401 -from .lib.capnp import _CAPNP_VERSION as LIBCAPNP_VERSION # noqa: F401 - -version = '%s' -short_version = '%s' -""" - if not filename: - filename = os.path.join(os.path.dirname(__file__), "capnp", "version.py") - - a = open(filename, "w") - try: - a.write(cnt % (VERSION, VERSION)) - finally: - a.close() - - -write_version_py() - -# Try to use README.md and CHANGELOG.md as description and changelog -with open("README.md", encoding="utf-8") as f: - long_description = f.read() -with open("CHANGELOG.md", encoding="utf-8") as f: - changelog = f.read() -changelog = "\nChangelog\n=============\n" + changelog -long_description += changelog - - -class clean(_clean): - """ - Clean command, invoked with `python setup.py clean` - """ - - def run(self): - _clean.run(self) - for x in [ - os.path.join("capnp", "lib", "capnp.cpp"), - os.path.join("capnp", "lib", "capnp.h"), - os.path.join("capnp", "version.py"), - "build", - "build32", - "build64", - "bundled", - ] + glob.glob(os.path.join("capnp", "*.capnp")): - print("removing %s" % x) - try: - os.remove(x) - except OSError: - shutil.rmtree(x, ignore_errors=True) - - -from Cython.Distutils import build_ext as build_ext_c # noqa: E402 - - -class build_libcapnp_ext(build_ext_c): - """ - Build capnproto library - """ - - user_options = build_ext_c.user_options + [ - ("force-bundled-libcapnp", None, "Bundle capnp library into the installer"), - ("force-system-libcapnp", None, "Use system capnp library"), - ("libcapnp-url=", "u", "URL to download libcapnp from (only if bundled)"), - ] - - def initialize_options(self): - build_ext_c.initialize_options(self) - self.force_bundled_libcapnp = None - self.force_system_libcapnp = None - self.libcapnp_url = None - - def finalize_options(self): - # print('The custom option for install is ', self.custom_option) - build_ext_c.finalize_options(self) - - def build_extension(self, ext): - build_ext_c.build_extension(self, ext) - - def run(self): # noqa: C901 - if self.force_bundled_libcapnp: - need_build = True - elif self.force_system_libcapnp: - need_build = False - else: - # Try to use capnp executable to find include and lib path - capnp_executable = shutil.which("capnp") - if capnp_executable: - capnp_dir = os.path.dirname(capnp_executable) - self.include_dirs += [os.path.join(capnp_dir, "..", "include")] - self.library_dirs += [os.path.join(capnp_dir, "..", "lib{}".format(8 * struct.calcsize("P")))] - self.library_dirs += [os.path.join(capnp_dir, "..", "lib")] - - # Look for capnproto using pkg-config (and minimum version) - try: - if pkgconfig.installed("capnp", ">= 0.7.0"): - need_build = False - else: - need_build = True - except EnvironmentError: - # pkg-config not available in path - need_build = True - - if need_build: - print( - "*WARNING* no libcapnp detected or rebuild forced. " - "Attempting to build it from source now. " - "If you have C++ Cap'n Proto installed, it may be out of date or is not being detected. " - "This may take a while..." - ) - bundle_dir = os.path.join(_this_dir, "bundled") - if not os.path.exists(bundle_dir): - os.mkdir(bundle_dir) - build_dir = os.path.join(_this_dir, "build{}".format(8 * struct.calcsize("P"))) - if not os.path.exists(build_dir): - os.mkdir(build_dir) - - # Check if we've already built capnproto - capnp_bin = os.path.join(build_dir, "bin", "capnp") - if os.name == "nt": - capnp_bin = os.path.join(build_dir, "bin", "capnp.exe") - - if not os.path.exists(capnp_bin): - # Not built, fetch and build - fetch_libcapnp(bundle_dir, self.libcapnp_url) - build_libcapnp(bundle_dir, build_dir) - else: - print("capnproto already built at {}".format(build_dir)) - - self.include_dirs = [os.path.join(build_dir, "include")] + self.include_dirs - self.library_dirs = [ - os.path.join(build_dir, "lib{}".format(8 * struct.calcsize("P"))), - os.path.join(build_dir, "lib"), - ] + self.library_dirs - - # Copy .capnp files from source - src_glob = glob.glob(os.path.join(build_dir, "include", "capnp", "*.capnp")) - dst_dir = os.path.join(self.build_lib, "capnp") - os.makedirs(dst_dir, exist_ok=True) - for file in src_glob: - print("copying {} -> {}".format(file, dst_dir)) - shutil.copy(file, dst_dir) - - return build_ext_c.run(self) - - -extra_compile_args = ["--std=c++14"] -extra_link_args = [] -if os.name == "nt": - extra_compile_args = ["/std:c++14", "/MD"] - extra_link_args = ["/MANIFEST"] - -import Cython.Build # noqa: E402 -import Cython # noqa: E402 - -extensions = [ - Extension( - "*", - [ - "capnp/helpers/capabilityHelper.cpp", - "capnp/includes/PyCustomMessageBuilder.cpp", - "capnp/lib/*.pyx", - ], - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - language="c++", - ) -] - -setup( - python_requires=">=3.8", - name="pycapnp", - packages=["capnp"], - version=VERSION, - package_data={ - "capnp": [ - "*.pxd", - "*.h", - "*.capnp", - "helpers/*.pxd", - "helpers/*.h", - "includes/*.h", - "includes/*.pxd", - "lib/*.pxd", - "lib/*.py", - "lib/*.pyx", - "lib/*.h", - "templates/*", - ] - }, - ext_modules=Cython.Build.cythonize(extensions), - cmdclass={"clean": clean, "build_ext": build_libcapnp_ext}, - install_requires=[], - entry_points={"console_scripts": ["capnpc-cython = capnp._gen:main"]}, - # PyPi info - description="A cython wrapping of the C++ Cap'n Proto library", - long_description=long_description, - long_description_content_type="text/markdown", - license="BSD-2-Clause", - # (setup.py only supports 1 author...) - author="Jacob Alexander", # <- Current maintainer; Original author -> Jason Paryani - author_email="haata@kiibohd.com", - url="https://github.com/capnproto/pycapnp", - download_url="https://github.com/haata/pycapnp/archive/v%s.zip" % VERSION, - keywords=["capnp", "capnproto", "Cap'n Proto", "pycapnp"], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows :: Windows 10", - "Operating System :: POSIX", - "Programming Language :: C++", - "Programming Language :: Cython", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Communications", - ], -)