Repository: qpid-proton Updated Branches: refs/heads/master eac0bb663 -> 888862f9d
PROTON-1330: [python] bundle the C source in the python source distribution This closes #88 Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/888862f9 Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/888862f9 Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/888862f9 Branch: refs/heads/master Commit: 888862f9d149f11516fdec35bdb1e0fa1998c3c1 Parents: eac0bb6 Author: Ken Giusti <[email protected]> Authored: Tue Nov 1 13:21:15 2016 -0400 Committer: Ken Giusti <[email protected]> Committed: Wed Nov 16 08:58:22 2016 -0500 ---------------------------------------------------------------------- proton-c/CMakeLists.txt | 67 +++- proton-c/bindings/python/CMakeLists.txt | 55 +++ proton-c/bindings/python/MANIFEST.in | 3 + proton-c/bindings/python/PACKAGING.txt | 26 ++ proton-c/bindings/python/README.rst.in | 11 + proton-c/bindings/python/setup.py | 401 --------------------- proton-c/bindings/python/setup.py.in | 323 +++++++++++++++++ proton-c/bindings/python/setuputils/bundle.py | 92 ----- proton-c/bindings/python/setuputils/misc.py | 38 +- proton-c/tox.ini.in | 3 +- 10 files changed, 506 insertions(+), 513 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/proton-c/CMakeLists.txt b/proton-c/CMakeLists.txt index 3cf01cd..7753f1f 100644 --- a/proton-c/CMakeLists.txt +++ b/proton-c/CMakeLists.txt @@ -91,6 +91,11 @@ add_custom_command ( DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/protocol.h.py ) +add_custom_target( + generated_c_files + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h ${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h + ) + # Select IO impl if(PN_WINAPI) set (pn_io_impl src/reactor/io/windows/io.c src/reactor/io/windows/iocp.c src/reactor/io/windows/write_pipeline.c) @@ -300,16 +305,38 @@ pn_absolute_install_dir(EXEC_PREFIX "." ${CMAKE_INSTALL_PREFIX}) pn_absolute_install_dir(LIBDIR ${LIB_INSTALL_DIR} ${CMAKE_INSTALL_PREFIX}) pn_absolute_install_dir(INCLUDEDIR ${INCLUDE_INSTALL_DIR} ${CMAKE_INSTALL_PREFIX}) -add_subdirectory(bindings) add_subdirectory(docs/api) add_subdirectory(../tests/tools/apps/c ../tests/tools/apps/c) +# for full source distribution: +set (qpid-proton-platform-all + src/platform/platform.c + src/reactor/io/windows/io.c + src/reactor/io/windows/iocp.c + src/reactor/io/windows/write_pipeline.c + src/reactor/io/windows/selector.c + src/reactor/io/posix/io.c + src/reactor/io/posix/selector.c + ) + +# platform specific library build: set (qpid-proton-platform-io src/platform/platform.c ${pn_io_impl} ${pn_selector_impl} ) +# for full source distribution: +set (qpid-proton-layers-all + src/sasl/sasl.c + src/sasl/cyrus_sasl.c + src/sasl/none_sasl.c + src/ssl/openssl.c + src/ssl/schannel.c + src/ssl/ssl_stub.c + ) + +# for current build system's environment: set (qpid-proton-layers ${pn_sasl_impl} ${pn_ssl_impl} @@ -347,6 +374,37 @@ set (qpid-proton-core set (qpid-proton-include-generated ${CMAKE_CURRENT_BINARY_DIR}/src/encodings.h ${CMAKE_CURRENT_BINARY_DIR}/src/protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/include/proton/version.h + ) + +set (qpid-proton-private-includes + src/extra/scanner.h + src/messenger/store.h + src/messenger/subscription.h + src/messenger/messenger.h + src/messenger/transform.h + src/ssl/ssl-internal.h + src/sasl/sasl-internal.h + src/core/autodetect.h + src/core/log_private.h + src/core/config.h + src/core/encoder.h + src/core/dispatch_actions.h + src/core/engine-internal.h + src/core/transport.h + src/core/framing.h + src/core/buffer.h + src/core/util.h + src/core/dispatcher.h + src/core/data.h + src/core/decoder.h + src/reactor/io/windows/iocp.h + src/reactor/selector.h + src/reactor/io.h + src/reactor/reactor.h + src/reactor/selectable.h + src/platform/platform.h + src/platform/platform_fmt.h ) set (qpid-proton-extra @@ -376,6 +434,7 @@ set (qpid-proton-include include/proton/codec.h include/proton/condition.h include/proton/connection.h + include/proton/connection_engine.h include/proton/delivery.h include/proton/disposition.h include/proton/engine.h @@ -404,6 +463,10 @@ set (qpid-proton-include-extra include/proton/url.h ) +# note: process bindings after the source lists have been defined so +# the bindings can reference them +add_subdirectory(bindings) + source_group("API Header Files" FILES ${qpid-proton-include} ${qpid-proton-include-extra}) set_source_files_properties ( @@ -441,6 +504,7 @@ add_library ( ${qpid-proton-include} ${qpid-proton-include-generated} ) +add_dependencies(qpid-proton-core generated_c_files) target_link_libraries (qpid-proton-core ${UUID_LIB} ${SSL_LIB} ${SASL_LIB} ${TIME_LIB} ${PLATFORM_LIBS}) @@ -466,6 +530,7 @@ add_library( ${qpid-proton-platform-io} ${qpid-proton-include-extra} ) +add_dependencies(qpid-proton generated_c_files) target_link_libraries (qpid-proton ${UUID_LIB} ${SSL_LIB} ${SASL_LIB} ${TIME_LIB} ${PLATFORM_LIBS}) http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/CMakeLists.txt b/proton-c/bindings/python/CMakeLists.txt index fc28417..fe732d9 100644 --- a/proton-c/bindings/python/CMakeLists.txt +++ b/proton-c/bindings/python/CMakeLists.txt @@ -64,6 +64,13 @@ set (pysrc proton/wrapper.py proton/_compat.py ) +# extra files included in the source distribution +set(py_dist_files + cproton.i + MANIFEST.in + setuputils + docs + ) macro (py_compile directory files artifacts) foreach (src_file ${files}) @@ -122,3 +129,51 @@ install(TARGETS ${SWIG_MODULE_cproton_REAL_NAME} COMPONENT Python) set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "html;tutorial") + +# +# Set up the directory 'dist' for building the python native package +# source distribution for Pypi/pip +# + +set(py_dist_dir ${CMAKE_CURRENT_BINARY_DIR}/dist) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in + ${py_dist_dir}/setup.py +) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README.rst.in + ${py_dist_dir}/README.rst +) + +file(COPY ${py_dist_files} DESTINATION ${py_dist_dir}) + +file(MAKE_DIRECTORY ${py_dist_dir}/proton) +file(COPY ${pysrc} DESTINATION ${py_dist_dir}/proton) + +add_custom_target(py_src_dist ALL) +add_dependencies(py_src_dist generated_c_files) +file(MAKE_DIRECTORY ${py_dist_dir}/proton-c) + +# copy generated source files from the binary dir to the dist +foreach(sfile ${qpid-proton-include-generated}) + string(REPLACE ${CMAKE_BINARY_DIR} ${py_dist_dir} dfile ${sfile}) + add_custom_command(TARGET py_src_dist + COMMAND ${CMAKE_COMMAND} -E + copy ${sfile} ${dfile}) +endforeach() + +# copy the proton C sources to the dist +set (all_src + ${qpid-proton-core} + ${qpid-proton-extra} + ${qpid-proton-include} + ${qpid-proton-include-extra} + ${qpid-proton-layers-all} + ${qpid-proton-platform-all} + ${qpid-proton-private-includes} + include/proton/cproton.i +) +foreach(sfile ${all_src}) + add_custom_command(TARGET py_src_dist + COMMAND ${CMAKE_COMMAND} -E + copy ${CMAKE_SOURCE_DIR}/proton-c/${sfile} ${py_dist_dir}/proton-c/${sfile}) +endforeach() http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/MANIFEST.in ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/MANIFEST.in b/proton-c/bindings/python/MANIFEST.in index a89a962..a37ad72 100644 --- a/proton-c/bindings/python/MANIFEST.in +++ b/proton-c/bindings/python/MANIFEST.in @@ -1,2 +1,5 @@ graft docs graft setuputils +graft proton-c +global-exclude proton-c *.pyc *.pyo + http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/PACKAGING.txt ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/PACKAGING.txt b/proton-c/bindings/python/PACKAGING.txt new file mode 100644 index 0000000..fa127f0 --- /dev/null +++ b/proton-c/bindings/python/PACKAGING.txt @@ -0,0 +1,26 @@ +This document describes how to build a native Python source package. +This can be used to install the Python bindings via pip. + +First configure the project using 'cmake' then build it. You do not +need to install the project. See the INSTALL.md file at the project's +top directory for details on building. + +Once you have built the project, there should be a 'dist' directory in +the Python bindings directory in the build directory. + +For example, assuming the build directory is named 'build': + +$ cd build/proton-c/bindings/python/dist + +You can now run the setup.py script from within the dist directory to +build your source distribution package. + +To build a Python source distribution: + +$ python ./setup.py sdist + +To build and install: + +$ python ./setup.py build install + + http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/README.rst.in ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/README.rst.in b/proton-c/bindings/python/README.rst.in new file mode 100644 index 0000000..8be1dd3 --- /dev/null +++ b/proton-c/bindings/python/README.rst.in @@ -0,0 +1,11 @@ +Python bindings for Qpid Proton +=============================== + +This module provides version @PN_VERSION_MAJOR@.@PN_VERSION_MINOR@.@PN_VERSION_POINT@ of the Proton AMQP messaging toolkit. + +Qpid Proton is a high-performance, lightweight messaging library. It +can be used in the widest range of messaging applications, including +brokers, client libraries, routers, bridges, proxies, and more. Proton +makes it trivial to integrate with the AMQP 1.0 ecosystem from any +platform, environment, or language. More about `Proton <http://qpid.apache.org/proton/index.html>`_. + http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/setup.py ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/setup.py b/proton-c/bindings/python/setup.py deleted file mode 100755 index 3606bed..0000000 --- a/proton-c/bindings/python/setup.py +++ /dev/null @@ -1,401 +0,0 @@ -#!/usr/bin/env python -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. - -""" -python-qpid-proton setup script - -DISCLAIMER: This script took lots of inspirations from PyZMQ, which is licensed -under the 'MODIFIED BSD LICENSE'. - -Although inspired by the work in PyZMQ, this script and the modules it depends -on were largely simplified to meet the requirements of the library. - -The default behavior of this script is to build the registered `_cproton` -extension using the system qpid-proton library. However, before doing that, the -script will try to discover the available qpid-proton's version and whether it's -suitable for the version of this library. This allows us to have a tight release -between the versions of the bindings and qpid-proton's version. - -The versions used to verify this are in `setuputils.bundle` and they should be -increased on every release. Note that `bundled_version` matches the current -released version. The motivation behind this is that we don't know how many -new releases will be made in the `0.9` series, therefore we need to target the -latest possible. - -If qpid-proton is found in the system and the available versions are match the -required ones, then the install process will continue normally. - -If the available versions are not good for the bindings or the library is -missing, then the following will happen: - -The setup script will attempt to download the C source for qpid-proton - see -`setuputils.bundle.fetch_libqpid_proton` - and it will include the proton C -code into the extension itself. - -While the above removes the need of *always* having qpid-proton installed, it -does not solve the need of having `swig` and the libraries qpid-proton requires -installed to make this setup work. - -From the Python side, this scripts overrides 1 command - build_ext - and it adds a -new one. The later - Configure - is called from the former to setup/discover what's -in the system. The rest of the comands and steps are done normally without any kind -of monkey patching. -""" - -import glob -import os -import subprocess -import sys - -import distutils.spawn as ds_spawn -import distutils.sysconfig as ds_sys -from distutils.ccompiler import new_compiler, get_default_compiler -from distutils.core import setup, Extension -from distutils.command.build import build -from distutils.command.build_ext import build_ext -from distutils.command.sdist import sdist -from distutils import errors - -from setuputils import bundle -from setuputils import log -from setuputils import misc - - -class CheckSDist(sdist): - - def run(self): - self.distribution.run_command('configure') - - # Append the source that was removed during - # the configuration step. - _cproton = self.distribution.ext_modules[-1] - _cproton.sources.append('cproton.i') - - try: - sdist.run(self) - finally: - for src in ['cproton.py', 'cproton_wrap.c']: - if os.path.exists(src): - os.remove(src) - - -class Configure(build_ext): - description = "Discover Qpid Proton version" - - @property - def compiler_type(self): - compiler = self.compiler - if compiler is None: - return get_default_compiler() - elif isinstance(compiler, str): - return compiler - else: - return compiler.compiler_type - - def prepare_swig_wrap(self): - """Run swig against the sources. This will cause swig to compile the - cproton.i file into a .c file called cproton_wrap.c, and create - cproton.py. - """ - ext = self.distribution.ext_modules[-1] - - if 'SWIG' in os.environ: - self.swig = os.environ['SWIG'] - - try: - # This will actually call swig to generate the files - # and list the sources. - self.swig_sources(ext.sources, ext) - except (errors.DistutilsExecError, errors.DistutilsPlatformError) as e: - if not (os.path.exists('cproton_wrap.c') or - os.path.exists('cproton.py')): - raise e - - # now remove the cproton.i file from the source list so we don't run - # swig again. - ext.sources = ext.sources[1:] - ext.swig_opts = [] - - def bundle_libqpid_proton_extension(self): - """The proper version of libqpid-proton is not present on the system, - so attempt to retrieve the proper libqpid-proton sources and - include them in the extension. - """ - setup_path = os.path.dirname(os.path.realpath(__file__)) - base = self.get_finalized_command('build').build_base - build_include = os.path.join(base, 'include') - - log.info("Bundling qpid-proton into the extension") - - # QPID_PROTON_SRC - (optional) pathname to the Proton C sources. Can - # be used to override where this setup gets the Proton C sources from - # (see bundle.fetch_libqpid_proton()) - if 'QPID_PROTON_SRC' not in os.environ: - if not os.path.exists(os.path.join(setup_path, 'CMakeLists.txt')): - bundledir = os.path.join(base, "bundled") - if not os.path.exists(bundledir): - os.makedirs(bundledir) - bundle.fetch_libqpid_proton(bundledir) - libqpid_proton_dir = os.path.abspath(os.path.join(bundledir, 'qpid-proton')) - else: - # setup.py is being invoked from within the proton source tree - # (CMakeLists.txt is not present in the python source dist). - # In this case build using the local sources. This use case is - # specifically for developers working on the proton source - # code. - proton_c = os.path.join(setup_path, '..', '..', '..') - libqpid_proton_dir = os.path.abspath(proton_c) - else: - libqpid_proton_dir = os.path.abspath(os.environ['QPID_PROTON_SRC']) - - log.debug("Using libqpid-proton src: %s" % libqpid_proton_dir) - - proton_base = os.path.join(libqpid_proton_dir, 'proton-c') - proton_src = os.path.join(proton_base, 'src') - proton_include = os.path.join(proton_base, 'include') - - # - # Create any generated header files, and put them in build_include: - # - if not os.path.exists(build_include): - os.makedirs(build_include) - os.mkdir(os.path.join(build_include, 'proton')) - - # Create copy of environment variables and modify PYTHONPATH to preserve - # all others environment variables defined by user. When `env` is specified - # Popen will not inherit environment variables of the current process. - proton_envs = os.environ.copy() - default_path = proton_envs.get('PYTHONPATH') - proton_envs['PYTHONPATH'] = proton_base if not default_path else '{0}{1}{2}'.format( - proton_base, os.pathsep, default_path) - - # Generate `protocol.h` by calling the python - # script found in the source dir. - with open(os.path.join(build_include, 'protocol.h'), 'wb') as header: - subprocess.Popen([sys.executable, os.path.join(proton_src, 'protocol.h.py')], - env=proton_envs, stdout=header) - - # Generate `encodings.h` by calling the python - # script found in the source dir. - with open(os.path.join(build_include, 'encodings.h'), 'wb') as header: - subprocess.Popen([sys.executable, os.path.join(proton_src, 'encodings.h.py')], - env=proton_envs, stdout=header) - - # Create a custom, temporary, version.h file mapping the - # major and minor versions from the downloaded tarball. This version should - # match the ones in the bundle module - with open(os.path.join(build_include, 'proton', 'version.h'), "wb") as ver: - version_text = """ -#ifndef _PROTON_VERSION_H -#define _PROTON_VERSION_H 1 -#define PN_VERSION_MAJOR %i -#define PN_VERSION_MINOR %i -#define PN_VERSION_POINT %i -#endif /* version.h */ -""" % bundle.bundled_version - ver.write(version_text.encode('utf-8')) - - # Collect all the Proton C files that need to be built. - # we could've used `glob(.., '*', '*.c')` but I preferred going - # with an explicit list of subdirs that we can control and expand - # depending on the version. Specifically, lets avoid adding things - # we don't need. - - sources = [] - for subdir in ['core', 'core/object', 'compiler', - 'extra', 'message', 'reactor', 'messenger', 'handlers', - 'platform', 'reactor/io/posix']: - - sources.extend(glob.glob(os.path.join(proton_src, subdir, '*.c'))) - - sources.extend(filter(lambda x: not x.endswith('dump.c'), - glob.iglob(os.path.join(proton_src, '*.c')))) - - # Look for any optional libraries that proton needs, and adjust the - # source list and compile flags as necessary. - libraries = [] - - # -D flags (None means no value, just define) - macros=[('qpid_proton_EXPORTS', None), - ('USE_ATOLL', None), - ('USE_STRERROR_R', None)] - - # Check whether openssl is installed by poking - # pkg-config for a minimum version 0. If it's installed, it should - # return True and we'll use it. Otherwise, we'll use the stub. - if misc.pkg_config_version(atleast='0', module='openssl'): - libraries += ['ssl', 'crypto'] - sources.append(os.path.join(proton_src, 'ssl', 'openssl.c')) - else: - sources.append(os.path.join(proton_src, 'ssl', 'ssl_stub.c')) - - # create a temp compiler to check for optional compile-time features - cc = new_compiler(compiler=self.compiler_type) - cc.output_dir = self.build_temp - - # Some systems need to link to `rt`. Check whether `clock_gettime` is - # around and if librt is needed - if cc.has_function('clock_gettime'): - macros.append(('USE_CLOCK_GETTIME', None)) - else: - if cc.has_function('clock_gettime', libraries=['rt']): - libraries.append('rt') - macros.append(('USE_CLOCK_GETTIME', None)) - - # 0.10 added an implementation for cyrus. Check - # if it is available before adding the implementation to the sources - # list. Eventually, `sasl.c` will be added and one of the existing - # implementations will be used. - if cc.has_function('sasl_client_done', includes=['sasl/sasl.h'], - libraries=['sasl2']): - libraries.append('sasl2') - sources.append(os.path.join(proton_src, 'sasl', 'cyrus_sasl.c')) - else: - sources.append(os.path.join(proton_src, 'sasl', 'none_sasl.c')) - - sources.append(os.path.join(proton_src, 'sasl', 'sasl.c')) - - # compile all the proton sources. We'll add the resulting list of - # objects to the _cproton extension as 'extra objects'. We do this - # instead of just lumping all the sources into the extension to prevent - # any proton-specific compilation flags from affecting the compilation - # of the generated swig code - - cc = new_compiler(compiler=self.compiler_type) - ds_sys.customize_compiler(cc) - - objects = cc.compile(sources, - macros=macros, - include_dirs=[build_include, - proton_include, - proton_src], - # compiler command line options: - extra_postargs=['-std=gnu99'], - output_dir=self.build_temp) - - # - # Now update the _cproton extension instance to include the objects and - # libraries - # - _cproton = self.distribution.ext_modules[-1] - _cproton.extra_objects = objects - _cproton.include_dirs.append(build_include) - _cproton.include_dirs.append(proton_include) - - # swig will need to access the proton headers: - _cproton.swig_opts.append('-I%s' % build_include) - _cproton.swig_opts.append('-I%s' % proton_include) - - # lastly replace the libqpid-proton dependency with libraries required - # by the Proton objects: - _cproton.libraries=libraries - - def check_qpid_proton_version(self): - """check the qpid_proton version""" - - target_version = bundle.bundled_version_str - return (misc.pkg_config_version(max_version=target_version) and - misc.pkg_config_version(atleast=bundle.min_qpid_proton_str)) - - @property - def bundle_proton(self): - """Need to bundle proton if the conditions below are met.""" - return ('QPID_PROTON_SRC' in os.environ) or \ - (not self.check_qpid_proton_version()) - - def use_installed_proton(self): - """The Proton development headers and library are installed, update the - _cproton extension to tell it where to find the library and headers. - """ - _cproton = self.distribution.ext_modules[-1] - incs = misc.pkg_config_get_var('includedir') - for i in incs.split(): - _cproton.swig_opts.append('-I%s' % i) - _cproton.include_dirs.append(i) - ldirs = misc.pkg_config_get_var('libdir') - _cproton.library_dirs.extend(ldirs.split()) - - def run(self): - # check if the Proton library and headers are installed and are - # compatible with this version of the binding. - if self.bundle_proton: - # Proton not installed or compatible - self.bundle_libqpid_proton_extension() - else: - self.use_installed_proton() - self.prepare_swig_wrap() - - -class CustomBuildOrder(build): - # The sole purpose of this class is to re-order - # the commands execution so that `build_ext` is executed *before* - # build_py. We need this to make sure `cproton.py` is generated - # before the python modules are collected. Otherwise, it won't - # be installed. - sub_commands = [ - ('build_ext', build.has_ext_modules), - ('build_py', build.has_pure_modules), - ('build_clib', build.has_c_libraries), - ('build_scripts', build.has_scripts), - ] - - -class CheckingBuildExt(build_ext): - """Subclass build_ext to build qpid-proton using `cmake`""" - - def run(self): - # Discover qpid-proton in the system - self.distribution.run_command('configure') - build_ext.run(self) - - -# Override `build_ext` and add `configure` -cmdclass = {'configure': Configure, - 'build': CustomBuildOrder, - 'build_ext': CheckingBuildExt, - 'sdist': CheckSDist} - -setup(name='python-qpid-proton', - version=bundle.bundled_version_str, - description='An AMQP based messaging library.', - author='Apache Qpid', - author_email='[email protected]', - url='http://qpid.apache.org/proton/', - packages=['proton'], - py_modules=['cproton'], - license="Apache Software License", - classifiers=["License :: OSI Approved :: Apache Software License", - "Intended Audience :: Developers", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5"], - cmdclass=cmdclass, - # Note well: the following extension instance is modified during the - # installation! If you make changes below, you may need to update the - # Configure class above - ext_modules=[Extension('_cproton', - sources=['cproton.i', 'cproton_wrap.c'], - swig_opts=['-threads'], - extra_compile_args=['-pthread'], - libraries=['qpid-proton'])]) http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/setup.py.in ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/setup.py.in b/proton-c/bindings/python/setup.py.in new file mode 100755 index 0000000..57f4368 --- /dev/null +++ b/proton-c/bindings/python/setup.py.in @@ -0,0 +1,323 @@ +#!/usr/bin/env python +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +""" +python-qpid-proton setup script + +DISCLAIMER: This script took lots of inspirations from PyZMQ, which is licensed +under the 'MODIFIED BSD LICENSE'. + +Although inspired by the work in PyZMQ, this script and the modules it depends +on were largely simplified to meet the requirements of the library. + +The behavior of this script is to build the registered `_cproton` extension +using the installed Qpid Proton C library and header files. If the library and +headers are not installed, or the installed version does not match the version +of these python bindings, then the script will attempt to build the extension +using the Proton C sources included in the python source distribution package. + +While the above removes the need of *always* having Qpid Proton C development +files installed, it does not solve the need of having `swig` and the libraries +qpid-proton requires installed to make this setup work. + +From the Python side, this scripts overrides 1 command - build_ext - and it adds a +new one. The latter - Configure - is called from the former to setup/discover what's +in the system. The rest of the comands and steps are done normally without any kind +of monkey patching. +""" + +import glob +import os +import subprocess +import sys + +import distutils.spawn as ds_spawn +import distutils.sysconfig as ds_sys +from distutils.ccompiler import new_compiler, get_default_compiler +from distutils.core import setup, Extension +from distutils.command.build import build +from distutils.command.build_ext import build_ext +from distutils.command.sdist import sdist +from distutils import errors + +from setuputils import log +from setuputils import misc + + +_PROTON_VERSION=(@PN_VERSION_MAJOR@, + @PN_VERSION_MINOR@, + @PN_VERSION_POINT@) +_PROTON_VERSION_STR = "%d.%d.%d" % _PROTON_VERSION + + +class CheckSDist(sdist): + + def run(self): + self.distribution.run_command('configure') + + # Append the source that was removed during + # the configuration step. + _cproton = self.distribution.ext_modules[-1] + _cproton.sources.append('cproton.i') + + try: + sdist.run(self) + finally: + for src in ['cproton.py', 'cproton_wrap.c']: + if os.path.exists(src): + os.remove(src) + + +class Configure(build_ext): + description = "Discover Qpid Proton version" + + @property + def compiler_type(self): + compiler = self.compiler + if compiler is None: + return get_default_compiler() + elif isinstance(compiler, str): + return compiler + else: + return compiler.compiler_type + + def prepare_swig_wrap(self): + """Run swig against the sources. This will cause swig to compile the + cproton.i file into a .c file called cproton_wrap.c, and create + cproton.py. + """ + ext = self.distribution.ext_modules[-1] + + if 'SWIG' in os.environ: + self.swig = os.environ['SWIG'] + + try: + # This will actually call swig to generate the files + # and list the sources. + self.swig_sources(ext.sources, ext) + except (errors.DistutilsExecError, errors.DistutilsPlatformError) as e: + if not (os.path.exists('cproton_wrap.c') or + os.path.exists('cproton.py')): + raise e + + # now remove the cproton.i file from the source list so we don't run + # swig again. + ext.sources = ext.sources[1:] + ext.swig_opts = [] + + def use_bundled_proton(self): + """The proper version of libqpid-proton is not installed on the system, + so use the included proton-c sources to build the extension + """ + log.info("Building the bundled proton-c sources into the extension") + + setup_path = os.path.dirname(os.path.realpath(__file__)) + base = self.get_finalized_command('build').build_base + build_include = os.path.join(base, 'include') + proton_base = os.path.abspath(os.path.join(setup_path, 'proton-c')) + proton_src = os.path.join(proton_base, 'src') + proton_include = os.path.join(proton_base, 'include') + + log.debug("Using Proton C sources: %s" % proton_base) + + # Collect all the Proton C files that need to be built. + # we could've used `glob(.., '*', '*.c')` but I preferred going + # with an explicit list of subdirs that we can control and expand + # depending on the version. Specifically, lets avoid adding things + # we don't need. + + sources = [] + for subdir in ['core', 'core/object', 'compiler', + 'extra', 'message', 'reactor', 'messenger', 'handlers', + 'platform', 'reactor/io/posix']: + + sources.extend(glob.glob(os.path.join(proton_src, subdir, '*.c'))) + + sources.extend(filter(lambda x: not x.endswith('dump.c'), + glob.iglob(os.path.join(proton_src, '*.c')))) + + # Look for any optional libraries that proton needs, and adjust the + # source list and compile flags as necessary. + libraries = [] + + # -D flags (None means no value, just define) + macros=[('qpid_proton_EXPORTS', None), + ('USE_ATOLL', None), + ('USE_STRERROR_R', None)] + + # Check whether openssl is installed by poking + # pkg-config for a minimum version 0. If it's installed, it should + # return True and we'll use it. Otherwise, we'll use the stub. + if misc.pkg_config_version_installed('openssl', atleast='0'): + libraries += ['ssl', 'crypto'] + sources.append(os.path.join(proton_src, 'ssl', 'openssl.c')) + else: + sources.append(os.path.join(proton_src, 'ssl', 'ssl_stub.c')) + log.warn("OpenSSL not installed - disabling SSL support!") + + # create a temp compiler to check for optional compile-time features + cc = new_compiler(compiler=self.compiler_type) + cc.output_dir = self.build_temp + + # Some systems need to link to `rt`. Check whether `clock_gettime` is + # around and if librt is needed + if cc.has_function('clock_gettime'): + macros.append(('USE_CLOCK_GETTIME', None)) + else: + if cc.has_function('clock_gettime', libraries=['rt']): + libraries.append('rt') + macros.append(('USE_CLOCK_GETTIME', None)) + + # 0.10 added an implementation for cyrus. Check + # if it is available before adding the implementation to the sources + # list. Eventually, `sasl.c` will be added and one of the existing + # implementations will be used. + if cc.has_function('sasl_client_done', includes=['sasl/sasl.h'], + libraries=['sasl2']): + libraries.append('sasl2') + sources.append(os.path.join(proton_src, 'sasl', 'cyrus_sasl.c')) + else: + sources.append(os.path.join(proton_src, 'sasl', 'none_sasl.c')) + log.warn("Cyrus SASL not installed - only the ANONYMOUS and" + " PLAIN mechanisms will be supported!") + sources.append(os.path.join(proton_src, 'sasl', 'sasl.c')) + + # compile all the proton sources. We'll add the resulting list of + # objects to the _cproton extension as 'extra objects'. We do this + # instead of just lumping all the sources into the extension to prevent + # any proton-specific compilation flags from affecting the compilation + # of the generated swig code + + cc = new_compiler(compiler=self.compiler_type) + ds_sys.customize_compiler(cc) + + objects = cc.compile(sources, + macros=macros, + include_dirs=[build_include, + proton_include, + proton_src], + # compiler command line options: + extra_postargs=['-std=gnu99'], + output_dir=self.build_temp) + + # + # Now update the _cproton extension instance passed to setup to include + # the objects and libraries + # + _cproton = self.distribution.ext_modules[-1] + _cproton.extra_objects = objects + _cproton.include_dirs.append(build_include) + _cproton.include_dirs.append(proton_include) + + # swig will need to access the proton headers: + _cproton.swig_opts.append('-I%s' % build_include) + _cproton.swig_opts.append('-I%s' % proton_include) + + # lastly replace the libqpid-proton dependency with libraries required + # by the Proton objects: + _cproton.libraries=libraries + + def libqpid_proton_installed(self, version): + """Check to see if the proper version of the Proton development library + and headers are already installed + """ + return misc.pkg_config_version_installed('libqpid-proton', version) + + def use_installed_proton(self): + """The Proton development headers and library are installed, update the + _cproton extension to tell it where to find the library and headers. + """ + # update the Extension instance passed to setup() to use the installed + # headers and link library + _cproton = self.distribution.ext_modules[-1] + incs = misc.pkg_config_get_var('libqpid-proton', 'includedir') + for i in incs.split(): + _cproton.swig_opts.append('-I%s' % i) + _cproton.include_dirs.append(i) + ldirs = misc.pkg_config_get_var('libqpid-proton', 'libdir') + _cproton.library_dirs.extend(ldirs.split()) + + def run(self): + # check if the Proton library and headers are installed and are + # compatible with this version of the binding. + if self.libqpid_proton_installed(_PROTON_VERSION_STR): + self.use_installed_proton() + else: + # Proton not installed or compatible, use bundled proton-c sources + self.use_bundled_proton() + self.prepare_swig_wrap() + + +class CustomBuildOrder(build): + # The sole purpose of this class is to re-order + # the commands execution so that `build_ext` is executed *before* + # build_py. We need this to make sure `cproton.py` is generated + # before the python modules are collected. Otherwise, it won't + # be installed. + sub_commands = [ + ('build_ext', build.has_ext_modules), + ('build_py', build.has_pure_modules), + ('build_clib', build.has_c_libraries), + ('build_scripts', build.has_scripts), + ] + + +class CheckingBuildExt(build_ext): + """Subclass build_ext to build qpid-proton using `cmake`""" + + def run(self): + # Discover qpid-proton in the system + self.distribution.run_command('configure') + build_ext.run(self) + + +# Override `build_ext` and add `configure` +cmdclass = {'configure': Configure, + 'build': CustomBuildOrder, + 'build_ext': CheckingBuildExt, + 'sdist': CheckSDist} + +setup(name='python-qpid-proton', + version=_PROTON_VERSION_STR + os.environ.get('PROTON_VERSION_SUFFIX', ''), + description='An AMQP based messaging library.', + author='Apache Qpid', + author_email='[email protected]', + url='http://qpid.apache.org/proton/', + packages=['proton'], + py_modules=['cproton'], + license="Apache Software License", + classifiers=["License :: OSI Approved :: Apache Software License", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5"], + cmdclass=cmdclass, + # Note well: the following extension instance is modified during the + # installation! If you make changes below, you may need to update the + # Configure class above + ext_modules=[Extension('_cproton', + sources=['cproton.i', 'cproton_wrap.c'], + swig_opts=['-threads'], + extra_compile_args=['-pthread'], + libraries=['qpid-proton'])]) http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/setuputils/bundle.py ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/setuputils/bundle.py b/proton-c/bindings/python/setuputils/bundle.py deleted file mode 100644 index 1bf3450..0000000 --- a/proton-c/bindings/python/setuputils/bundle.py +++ /dev/null @@ -1,92 +0,0 @@ -#----------------------------------------------------------------------------- -# 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. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# This bundling code is largely adapted from pyzmq's code -# PyZMQ Developers, which is itself Modified BSD licensed. -#----------------------------------------------------------------------------- - -import os -import shutil -import stat -import sys -import tarfile -from glob import glob -from subprocess import Popen, PIPE - -try: - # py2 - from urllib2 import urlopen -except ImportError: - # py3 - from urllib.request import urlopen - -from . import log - - -#----------------------------------------------------------------------------- -# Constants -#----------------------------------------------------------------------------- -min_qpid_proton = (0, 16, 0) -min_qpid_proton_str = "%i.%i.%i" % min_qpid_proton - -bundled_version = (0, 16, 0) -bundled_version_str = "%i.%i.%i" % bundled_version -libqpid_proton = "qpid-proton-%s.tar.gz" % bundled_version_str -libqpid_proton_url = ("http://www.apache.org/dist/qpid/proton/%s/%s" % - (bundled_version_str, libqpid_proton)) - -HERE = os.path.dirname(__file__) -ROOT = os.path.dirname(HERE) - - -def fetch_archive(savedir, url, fname): - """Download an archive to a specific location - - :param savedir: Destination dir - :param url: URL where the archive should be downloaded from - :param fname: Archive's filename - """ - dest = os.path.join(savedir, fname) - - if os.path.exists(dest): - log.info("already have %s" % fname) - return dest - - log.info("fetching %s into %s" % (url, savedir)) - if not os.path.exists(savedir): - os.makedirs(savedir) - req = urlopen(url) - with open(dest, 'wb') as f: - f.write(req.read()) - return dest - - -def fetch_libqpid_proton(savedir): - """Download qpid-proton to `savedir`.""" - def _c_only(members): - # just extract the files necessary to build the shared library - for tarinfo in members: - npath = os.path.normpath(tarinfo.name) - if ("proton-c/src" in npath or - "proton-c/include" in npath or - "proton-c/mllib" in npath): - yield tarinfo - dest = os.path.join(savedir, 'qpid-proton') - if os.path.exists(dest): - log.info("already have %s" % dest) - return - fname = fetch_archive(savedir, libqpid_proton_url, libqpid_proton) - tf = tarfile.open(fname) - member = tf.firstmember.path - if member == '.': - member = tf.getmembers()[1].path - with_version = os.path.join(savedir, member) - tf.extractall(savedir, members=_c_only(tf)) - tf.close() - shutil.move(with_version, dest) http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/bindings/python/setuputils/misc.py ---------------------------------------------------------------------- diff --git a/proton-c/bindings/python/setuputils/misc.py b/proton-c/bindings/python/setuputils/misc.py index b1864a0..54a8fde 100644 --- a/proton-c/bindings/python/setuputils/misc.py +++ b/proton-c/bindings/python/setuputils/misc.py @@ -40,39 +40,43 @@ def _call_pkg_config(args): -def pkg_config_version(atleast=None, max_version=None, module='libqpid-proton'): - """Check the qpid_proton version using pkg-config +def pkg_config_version_installed(package, version=None, atleast=None): + """Check if version of a package is is installed This function returns True/False depending on whether - the library is found and atleast/max_version are met. + the package is found and is the correct version. - :param atleast: The minimum required version - :param max_version: The maximum supported version. This - basically represents the target version. + :param version: The exact version of the package required + :param atleast: True if installed package is at least this version """ - if atleast and max_version: - log.fatal('Specify either atleast or max_version') + if version is None and atleast is None: + log.fatal('package version string required') + elif version and atleast: + log.fatal('Specify either version or atleast, not both') - p = _call_pkg_config(['--%s-version=%s' % (atleast and 'atleast' or 'max', - atleast or max_version), - module]) + check = 'exact' if version else 'atleast' + p = _call_pkg_config(['--%s-version=%s' % (check, + version or atleast), + package]) if p: out,err = p.communicate() if p.returncode: - log.info("Did not find %s via pkg-config: %s" % (module, err)) + log.info("Did not find %s via pkg-config: %s" % (package, err)) return False - log.info("Using %s (found via pkg-config)." % module) + log.info("Using %s version %s (found via pkg-config)" % + (package, + _call_pkg_config(['--modversion', package]).communicate()[0])) return True return False -def pkg_config_get_var(name, module='libqpid-proton'): - """Retrieve the value of the named module variable as a string +def pkg_config_get_var(package, name): + """Retrieve the value of the named package variable as a string """ - p = _call_pkg_config(['--variable=%s' % name, module]) + p = _call_pkg_config(['--variable=%s' % name, package]) if not p: - log.warn("pkg-config: var %s get failed, package %s", name, module) + log.warn("pkg-config: var %s get failed, package %s", name, package) return "" out,err = p.communicate() if p.returncode: http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/888862f9/proton-c/tox.ini.in ---------------------------------------------------------------------- diff --git a/proton-c/tox.ini.in b/proton-c/tox.ini.in index 7a9c8eb..2a6b09d 100644 --- a/proton-c/tox.ini.in +++ b/proton-c/tox.ini.in @@ -2,14 +2,13 @@ envlist = py26,py27,py33,py34,py35 minversion = 1.4 skipdist = True -setupdir = @py_src@ +setupdir = @py_bin@/dist [testenv] usedevelop = False setenv = VIRTUAL_ENV={envdir} DEBUG=True - QPID_PROTON_SRC=@CMAKE_SOURCE_DIR@ passenv = PKG_CONFIG_PATH CFLAGS --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
