pycapnp/setup.py
Lasse Blaauwbroek aafec2281e Make reraise_kj_exception available to downstream
I'm using Pycapnp in a project, where we compile `.capnp` files directly to
Cython instead of using the dynamic interface (for speed). For this, we need
access to the `reraise_kj_exception` C function defined by Pycapnp. This is not
possible, because Cython does not automatically make this function available to
downstream users.

My previous solution, in #301, was rather flawed. The  file `capabilityHelper.cpp`, where
`reraise_kj_exception` is defined, was bundled into the distribution, so that
this file could be included in downstream libraries. This turns out to be a
terrible idea, because it redefines a bunch of other things like
`ReadPromiseAdapter`. For reasons not entirely clear to me, this leads to
segmentation faults. This PR revers #301.

Instead, in this PR I've made `reraise_kj_exception` a Cython-level function,
that can be used by downstream libraries. The C-level variant has been renamed
to `c_reraise_kj_exception`.
2023-11-05 13:58:13 -08:00

264 lines
8.3 KiB
Python

#!/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 = 0
MICRO = 0
TAG = "b1"
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/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/*.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",
# (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",
"License :: OSI Approved :: BSD License",
"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",
],
)