| #!/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 commands and steps are done normally without any kind |
| of monkey patching. |
| """ |
| |
| import os |
| import shutil |
| |
| from setuptools import setup, Extension |
| from setuptools.command.sdist import sdist |
| from setuptools.command.build_ext import build_ext |
| from setuptools.command.build_py import build_py |
| |
| import distutils.sysconfig as ds_sys |
| from distutils.ccompiler import new_compiler, get_default_compiler |
| |
| 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 Swig(build_ext): |
| def run(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 = Extension('_cproton', |
| sources=['cproton.i'], |
| swig_opts=['-threads', '-Iinclude']) |
| |
| if 'SWIG' in os.environ: |
| self.swig = os.environ['SWIG'] |
| |
| self.swig_sources(ext.sources, ext) |
| |
| |
| class CheckSDist(sdist): |
| def run(self): |
| self.distribution.run_command('swig') |
| sdist.run(self) |
| |
| |
| 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 use_bundled_proton(self): |
| """The proper version of libqpid-proton-core 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_src = os.path.join(proton_base, 'src') |
| proton_core_src = os.path.join(proton_base, 'src', 'core') |
| proton_include = os.path.join(proton_base, 'include') |
| |
| log.debug("Using Proton C sources: %s" % proton_base) |
| |
| # Collect all the Proton C files packaged in the sdist and strip out |
| # anything windows and configuration-dependent |
| |
| sources = [] |
| for root, _, files in os.walk(proton_core_src): |
| for file_ in files: |
| if file_.endswith(('.c', '.cpp')): |
| sources.append(os.path.join(root, file_)) |
| |
| # Look for any optional libraries that proton needs, and adjust the |
| # source list and compile flags as necessary. |
| libraries = [] |
| includes = [] |
| macros = [] |
| |
| # -D flags (None means no value, just define) |
| macros += [('PROTON_DECLARE_STATIC', None)] |
| |
| if self.compiler_type=='msvc': |
| sources += [ |
| os.path.join(proton_src, 'compiler', 'msvc', 'snprintf.c'), |
| os.path.join(proton_src, 'compiler', 'msvc', 'start.c') |
| ] |
| elif self.compiler_type=='unix': |
| sources += [ |
| os.path.join(proton_src, 'compiler' , 'gcc', 'start.c') |
| ] |
| |
| # 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'] |
| includes += [misc.pkg_config_get_var('openssl', 'includedir')] |
| sources.append(os.path.join(proton_src, 'ssl', 'openssl.c')) |
| elif os.name=='nt': |
| libraries += ['crypt32', 'secur32'] |
| sources.append(os.path.join(proton_src, 'ssl', 'schannel.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 |
| |
| # 0.10 added an implementation for cyrus. Check |
| # if it is available before adding the implementation to the sources |
| # list. 'sasl.c` and 'default_sasl.c' are added and one of the existing |
| # implementations will be used. |
| sources.append(os.path.join(proton_src, 'sasl', 'sasl.c')) |
| sources.append(os.path.join(proton_src, 'sasl', 'default_sasl.c')) |
| |
| # Skip the SASL detection on Windows. |
| # MSbuild scans output of Exec tasks and fails the build if it notices error-like |
| # strings there. This is a known issue with CMake and msbuild, see |
| # * https://github.com/Microsoft/msbuild/issues/2424 |
| # * https://cmake.org/pipermail/cmake-developers/2015-October/026775.html |
| if cc.compiler_type!='msvc': |
| 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: |
| log.warn("Cyrus SASL not installed - only the ANONYMOUS and PLAIN mechanisms will be supported!") |
| sources.append(os.path.join(proton_src, 'sasl', 'cyrus_stub.c')) |
| else: |
| log.warn("Windows - only the ANONYMOUS and PLAIN mechanisms will be supported!") |
| sources.append(os.path.join(proton_src, 'sasl', 'cyrus_stub.c')) |
| |
| # Hack for Windows/msvc: We need to compile proton as C++, but it seems the only way to |
| # force this in setup.py is to use a .cpp extension! So copy all the source files to .cpp |
| # and use these as the compile sources |
| if self.compiler_type=='msvc': |
| targets = [] |
| target_base = os.path.join(self.build_temp, 'srcs') |
| try: |
| # Might need to make intermediate directories use os.makedirs() not os.mkdir() |
| os.makedirs(target_base) |
| except OSError: |
| pass |
| |
| for f in sources: |
| # We know each file ends in '.c' as we filtered on that above so just add 'pp' to end |
| target = os.path.join(target_base, os.path.basename(f) + 'pp') |
| shutil.copy(f, target) |
| targets.append(target) |
| |
| # Copy .h files into temp tree too as we need them to compile |
| for root, _, files in os.walk(proton_core_src): |
| for file_ in files: |
| if file_.endswith('.h'): |
| shutil.copy(os.path.join(root, file_), os.path.join(target_base, file_)) |
| |
| # Copy ssl/sasl .h files |
| shutil.copy(os.path.join(proton_src, 'sasl', 'sasl-internal.h'), os.path.join(target_base, 'sasl-internal.h')) |
| shutil.copy(os.path.join(proton_src, 'ssl', 'ssl-internal.h'), os.path.join(target_base, 'ssl-internal.h')) |
| |
| sources = targets |
| |
| # 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) |
| |
| extra = [] |
| if self.compiler_type=='unix': |
| extra.append('-std=gnu99') |
| objects = cc.compile(sources, |
| macros=macros, |
| include_dirs=[build_include, |
| proton_include, |
| proton_src]+includes, |
| # compiler command line options: |
| extra_preargs=extra, |
| 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) |
| |
| # lastly replace the libqpid-proton-core 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-core', 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-core', '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-core', '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() |
| |
| |
| class BuildExtFirst(build_py): |
| def run(self): |
| # Make sure swig runs first and adds file etc |
| self.distribution.run_command('build_ext') |
| build_py.run(self) |
| |
| |
| class CheckingBuildExt(build_ext): |
| def run(self): |
| # Discover qpid-proton and prerequisites in the system |
| self.distribution.run_command('configure') |
| build_ext.run(self) |
| |
| |
| 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='users@qpid.apache.org', |
| 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.5", |
| "Programming Language :: Python :: 3.6", |
| "Programming Language :: Python :: 3.7", |
| "Programming Language :: Python :: 3.8"], |
| cmdclass = { |
| 'configure': Configure, |
| 'swig': Swig, |
| 'build_py': BuildExtFirst, |
| 'build_ext': CheckingBuildExt, |
| 'sdist': CheckSDist |
| }, |
| extras_require={ |
| 'opentracing': ['opentracing', 'jaeger_client'] |
| }, |
| # 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_wrap.c'], |
| extra_compile_args=['-pthread', '-DPROTON_DECLARE_STATIC'], |
| libraries=['qpid-proton-core'])]) |