diff --git a/README.md b/README.md index a0362ed..63982a4 100644 --- a/README.md +++ b/README.md @@ -38,25 +38,27 @@ cd pycapnp pip install . ``` -If you wish to install using the latest upstream C++ Cap'n Proto: +By default, the setup script will automatically use the locally installed Cap'n Proto. +If Cap'n Proto is not installed, it will bundle and build the matching Cap'n Proto library. + +To enforce bundling, the Cap'n Proto library: ```bash -pip install \ - --install-option "--libcapnp-url" \ - --install-option "https://github.com/capnproto/capnproto/archive/master.tar.gz" \ - --install-option "--force-bundled-libcapnp" . +pip install . -C force-bundled-libcapnp=True ``` -To force bundled python: +If you wish to install using the latest upstream C++ Cap'n Proto: ```bash -pip install --install-option "--force-bundled-libcapnp" . +pip install . \ + -C force-bundled-libcapnp=True \ + -C libcapnp-url="https://github.com/capnproto/capnproto/archive/master.tar.gz" ``` -Slightly more prompt error messages using distutils rather than pip. +To enforce using the installed Cap'n Proto from the system: ```bash -python setup.py install --force-bundled-libcapnp +pip install . -C force-system-libcapnp=True ``` The bundling system isn't that smart so it might be necessary to clean up the bundled build when changing versions: @@ -92,19 +94,12 @@ pipenv run pytest ### Binary Packages -Building a dumb binary distribution: - -```bash -python setup.py bdist_dumb -``` - -Building a Python wheel distributiion: +Building a Python wheel distributiion ```bash -python setup.py bdist_wheel +pip wheel . ``` - ## Documentation/Example There is some basic documentation [here](http://capnproto.github.io/pycapnp/). diff --git a/_custom_build/backend.py b/_custom_build/backend.py new file mode 100644 index 0000000..768bdb4 --- /dev/null +++ b/_custom_build/backend.py @@ -0,0 +1,31 @@ +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/capnp/lib/capnp_api.h b/capnp/lib/capnp_api.h new file mode 100644 index 0000000..aa24704 --- /dev/null +++ b/capnp/lib/capnp_api.h @@ -0,0 +1,125 @@ +/* Generated by Cython 0.29.36 */ + +#ifndef __PYX_HAVE_API__capnp__lib__capnp +#define __PYX_HAVE_API__capnp__lib__capnp +#ifdef __MINGW64__ +#define MS_WIN64 +#endif +#include "Python.h" + +static PyObject *(*__pyx_api_f_5capnp_3lib_5capnp_wrap_dynamic_struct_reader)( ::capnp::Response< ::capnp::DynamicStruct> &) = 0; +#define wrap_dynamic_struct_reader __pyx_api_f_5capnp_3lib_5capnp_wrap_dynamic_struct_reader +static ::kj::Promise *(*__pyx_api_f_5capnp_3lib_5capnp_call_server_method)(PyObject *, char *, ::capnp::CallContext< ::capnp::DynamicStruct, ::capnp::DynamicStruct> &) = 0; +#define call_server_method __pyx_api_f_5capnp_3lib_5capnp_call_server_method +static PyObject *(*__pyx_api_f_5capnp_3lib_5capnp_wrap_kj_exception)( ::kj::Exception &) = 0; +#define wrap_kj_exception __pyx_api_f_5capnp_3lib_5capnp_wrap_kj_exception +static PyObject *(*__pyx_api_f_5capnp_3lib_5capnp_wrap_kj_exception_for_reraise)( ::kj::Exception &) = 0; +#define wrap_kj_exception_for_reraise __pyx_api_f_5capnp_3lib_5capnp_wrap_kj_exception_for_reraise +static PyObject *(*__pyx_api_f_5capnp_3lib_5capnp_get_exception_info)(PyObject *, PyObject *, PyObject *) = 0; +#define get_exception_info __pyx_api_f_5capnp_3lib_5capnp_get_exception_info +static void (*__pyx_api_f_5capnp_3lib_5capnp_promise_task_add_done_callback)(PyObject *, PyObject *, ::kj::PromiseFulfiller &) = 0; +#define promise_task_add_done_callback __pyx_api_f_5capnp_3lib_5capnp_promise_task_add_done_callback +static void (*__pyx_api_f_5capnp_3lib_5capnp_promise_task_cancel)(PyObject *) = 0; +#define promise_task_cancel __pyx_api_f_5capnp_3lib_5capnp_promise_task_cancel +static void (*__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_write_start)(PyObject *, ::kj::ArrayPtr< ::kj::ArrayPtr const > , ::kj::PromiseFulfiller &) = 0; +#define _asyncio_stream_write_start __pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_write_start +static void (*__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_write_stop)(PyObject *) = 0; +#define _asyncio_stream_write_stop __pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_write_stop +static void (*__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_read_start)(PyObject *, void *, size_t, size_t, ::kj::PromiseFulfiller &) = 0; +#define _asyncio_stream_read_start __pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_read_start +static void (*__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_read_stop)(PyObject *) = 0; +#define _asyncio_stream_read_stop __pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_read_stop +static void (*__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_shutdown_write)(PyObject *) = 0; +#define _asyncio_stream_shutdown_write __pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_shutdown_write +static void (*__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_close)(PyObject *) = 0; +#define _asyncio_stream_close __pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_close +static PyObject *(*__pyx_api_f_5capnp_3lib_5capnp_make_async_message_reader)( ::kj::Own< ::capnp::MessageReader> ) = 0; +#define make_async_message_reader __pyx_api_f_5capnp_3lib_5capnp_make_async_message_reader +#if !defined(__Pyx_PyIdentifier_FromString) +#if PY_MAJOR_VERSION < 3 + #define __Pyx_PyIdentifier_FromString(s) PyString_FromString(s) +#else + #define __Pyx_PyIdentifier_FromString(s) PyUnicode_FromString(s) +#endif +#endif + +#ifndef __PYX_HAVE_RT_ImportFunction_0_29_36 +#define __PYX_HAVE_RT_ImportFunction_0_29_36 +static int __Pyx_ImportFunction_0_29_36(PyObject *module, const char *funcname, void (**f)(void), const char *sig) { + PyObject *d = 0; + PyObject *cobj = 0; + union { + void (*fp)(void); + void *p; + } tmp; + d = PyObject_GetAttrString(module, (char *)"__pyx_capi__"); + if (!d) + goto bad; + cobj = PyDict_GetItemString(d, funcname); + if (!cobj) { + PyErr_Format(PyExc_ImportError, + "%.200s does not export expected C function %.200s", + PyModule_GetName(module), funcname); + goto bad; + } +#if PY_VERSION_HEX >= 0x02070000 + if (!PyCapsule_IsValid(cobj, sig)) { + PyErr_Format(PyExc_TypeError, + "C function %.200s.%.200s has wrong signature (expected %.500s, got %.500s)", + PyModule_GetName(module), funcname, sig, PyCapsule_GetName(cobj)); + goto bad; + } + tmp.p = PyCapsule_GetPointer(cobj, sig); +#else + {const char *desc, *s1, *s2; + desc = (const char *)PyCObject_GetDesc(cobj); + if (!desc) + goto bad; + s1 = desc; s2 = sig; + while (*s1 != '\0' && *s1 == *s2) { s1++; s2++; } + if (*s1 != *s2) { + PyErr_Format(PyExc_TypeError, + "C function %.200s.%.200s has wrong signature (expected %.500s, got %.500s)", + PyModule_GetName(module), funcname, sig, desc); + goto bad; + } + tmp.p = PyCObject_AsVoidPtr(cobj);} +#endif + *f = tmp.fp; + if (!(*f)) + goto bad; + Py_DECREF(d); + return 0; +bad: + Py_XDECREF(d); + return -1; +} +#endif + + +static int import_capnp__lib__capnp(void) { + PyObject *module = 0; + module = PyImport_ImportModule("capnp.lib.capnp"); + if (!module) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "wrap_dynamic_struct_reader", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_wrap_dynamic_struct_reader, "PyObject *( ::capnp::Response< ::capnp::DynamicStruct> &)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "call_server_method", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_call_server_method, " ::kj::Promise *(PyObject *, char *, ::capnp::CallContext< ::capnp::DynamicStruct, ::capnp::DynamicStruct> &)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "wrap_kj_exception", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_wrap_kj_exception, "PyObject *( ::kj::Exception &)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "wrap_kj_exception_for_reraise", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_wrap_kj_exception_for_reraise, "PyObject *( ::kj::Exception &)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "get_exception_info", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_get_exception_info, "PyObject *(PyObject *, PyObject *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "promise_task_add_done_callback", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_promise_task_add_done_callback, "void (PyObject *, PyObject *, ::kj::PromiseFulfiller &)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "promise_task_cancel", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_promise_task_cancel, "void (PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "_asyncio_stream_write_start", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_write_start, "void (PyObject *, ::kj::ArrayPtr< ::kj::ArrayPtr const > , ::kj::PromiseFulfiller &)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "_asyncio_stream_write_stop", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_write_stop, "void (PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "_asyncio_stream_read_start", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_read_start, "void (PyObject *, void *, size_t, size_t, ::kj::PromiseFulfiller &)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "_asyncio_stream_read_stop", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_read_stop, "void (PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "_asyncio_stream_shutdown_write", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_shutdown_write, "void (PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "_asyncio_stream_close", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp__asyncio_stream_close, "void (PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_0_29_36(module, "make_async_message_reader", (void (**)(void))&__pyx_api_f_5capnp_3lib_5capnp_make_async_message_reader, "PyObject *( ::kj::Own< ::capnp::MessageReader> )") < 0) goto bad; + Py_DECREF(module); module = 0; + return 0; + bad: + Py_XDECREF(module); + return -1; +} + +#endif /* !__PYX_HAVE_API__capnp__lib__capnp */ diff --git a/docs/install.rst b/docs/install.rst index 656a8dd..abb81c0 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -15,8 +15,8 @@ To force rebuilding the pip package from source (you'll need requirments.txt or To force bundling libcapnp (or force system libcapnp), just in case setup.py isn't doing the right thing:: - pip install --no-binary :all: --install-option "--force-bundled-libcapnp" - pip install --no-binary :all: --install-option "--force-system-libcapnp" + pip install --no-binary :all: -C force-bundled-libcapnp=True + pip install --no-binary :all: -C force-system-libcapnp=True If you're using an older Linux distro (e.g. CentOS 6) you many need to set `LDFLAGS="-Wl,--no-as-needed -lrt"`:: @@ -24,7 +24,7 @@ If you're using an older Linux distro (e.g. CentOS 6) you many need to set `LDFL It's also possible to specify the libcapnp url when bundling (this may not work, there be dragons):: - pip install --no-binary :all: --install-option "--force-bundled-libcapnp" --install-option "--libcapnp-url" --install-option "https://github.com/capnproto/capnproto/archive/master.tar.gz" + pip install --no-binary :all: -C force-bundled-libcapnp=True -C libcapnp-url="https://github.com/capnproto/capnproto/archive/master.tar.gz" From Source ----------- diff --git a/pyproject.toml b/pyproject.toml index c829d6d..1551bd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,7 @@ [build-system] requires = ["setuptools", "wheel", "pkgconfig", "cython<3"] +build-backend = "backend" +backend-path = ["_custom_build"] [tool.pytest.ini_options] asyncio_mode = "auto" diff --git a/setup.cfg b/setup.cfg index 8e5df73..04ef73c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -description-file = README.md +description_file = README.md license_files = LICENSE.md diff --git a/setup.py b/setup.py index ef29917..8ba020f 100644 --- a/setup.py +++ b/setup.py @@ -15,10 +15,12 @@ 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 -_this_dir = os.path.dirname(__file__) MAJOR = 1 MINOR = 3 @@ -85,26 +87,6 @@ def run(self): shutil.rmtree(x, ignore_errors=True) -# hack to parse commandline arguments -force_bundled_libcapnp = "--force-bundled-libcapnp" in sys.argv -if force_bundled_libcapnp: - sys.argv.remove("--force-bundled-libcapnp") -force_system_libcapnp = "--force-system-libcapnp" in sys.argv -if force_system_libcapnp: - sys.argv.remove("--force-system-libcapnp") -force_cython = "--force-cython" in sys.argv -if force_cython: - sys.argv.remove("--force-cython") - # Always use cython, ignoring option -libcapnp_url = None -try: - libcapnp_url_index = sys.argv.index("--libcapnp-url") - libcapnp_url = sys.argv[libcapnp_url_index + 1] - sys.argv.remove("--libcapnp-url") - sys.argv.remove(libcapnp_url) -except Exception: - pass - from Cython.Distutils import build_ext as build_ext_c # noqa: E402 @@ -113,13 +95,29 @@ 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 force_bundled_libcapnp: + if self.force_bundled_libcapnp: need_build = True - elif force_system_libcapnp: + elif self.force_system_libcapnp: need_build = False else: # Try to use capnp executable to find include and lib path @@ -167,7 +165,7 @@ def run(self): # noqa: C901 if not os.path.exists(capnp_bin): # Not built, fetch and build - fetch_libcapnp(bundle_dir, libcapnp_url) + fetch_libcapnp(bundle_dir, self.libcapnp_url) build_libcapnp(bundle_dir, build_dir) else: print("capnproto already built at {}".format(build_dir)) diff --git a/tox.ini b/tox.ini index de20c96..2aea657 100644 --- a/tox.ini +++ b/tox.ini @@ -7,10 +7,11 @@ deps= pkgconfig Jinja2 pytest + pytest-asyncio cython<3 commands = - python setup.py install + pip install . py.test {posargs} setenv =