PROTON-1330: [python] bundle the C source in the python source distribution
diff --git a/proton-c/bindings/python/CMakeLists.txt b/proton-c/bindings/python/CMakeLists.txt
index fc28417..51a7c43 100644
--- a/proton-c/bindings/python/CMakeLists.txt
+++ b/proton-c/bindings/python/CMakeLists.txt
@@ -122,3 +122,52 @@
COMPONENT Python)
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "html;tutorial")
+
+#
+# Set up the directory 'dist' for building the python native package
+# for Pypi/pip
+#
+
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dist)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c)
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/dist/setup.py
+)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README.rst.in
+ ${CMAKE_CURRENT_BINARY_DIR}/dist/README.rst
+)
+
+set(py_dist_files
+ ${CMAKE_CURRENT_SOURCE_DIR}/setuputils
+ ${CMAKE_CURRENT_SOURCE_DIR}/proton
+ ${CMAKE_CURRENT_SOURCE_DIR}/cproton.i
+ ${CMAKE_CURRENT_SOURCE_DIR}/MANIFEST.in
+ ${CMAKE_CURRENT_SOURCE_DIR}/docs
+)
+set(py_dist_proton_c_files
+ ${CMAKE_SOURCE_DIR}/proton-c/include
+ ${CMAKE_SOURCE_DIR}/proton-c/src
+)
+
+file(COPY ${py_dist_files}
+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/dist
+)
+file(COPY ${py_dist_proton_c_files}
+ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c
+)
+
+configure_file(
+ ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c/include/proton/version.h.in
+ ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c/include/proton/version.h
+)
+
+# generate the protocol and codec headers
+execute_process(
+ COMMAND ${env_py} PYTHONPATH=${CMAKE_SOURCE_DIR}/proton-c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c/src/codec/encodings.h.py
+ OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c/src/codec/encodings.h
+)
+execute_process(
+ COMMAND ${env_py} PYTHONPATH=${CMAKE_SOURCE_DIR}/proton-c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c/src/protocol.h.py
+ OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/dist/proton-c/src/protocol.h
+)
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
+
diff --git a/proton-c/bindings/python/PACKAGING.txt b/proton-c/bindings/python/PACKAGING.txt
new file mode 100644
index 0000000..16e4534
--- /dev/null
+++ b/proton-c/bindings/python/PACKAGING.txt
@@ -0,0 +1,28 @@
+This document describes how to build a native Python source package
+for distribution via Pypi.
+
+First configure the project using 'cmake'. You do not need to build or
+install the project. See the INSTALL.md file at the project's top
+directory for details.
+
+Once you have configured (via cmake) the project, go to the Python
+bindings directory in the build directory. For example, assuming the
+build directory is named 'build':
+
+$ cd build/proton-c/bindings/python
+
+In that directory there will be a 'dist' subdirectory - move to that
+directory.
+
+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
+
+
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>`_.
+
diff --git a/proton-c/bindings/python/setup.py b/proton-c/bindings/python/setup.py.in
similarity index 64%
rename from proton-c/bindings/python/setup.py
rename to proton-c/bindings/python/setup.py.in
index 1a74f0f..bcc8e3f 100755
--- a/proton-c/bindings/python/setup.py
+++ b/proton-c/bindings/python/setup.py.in
@@ -26,34 +26,18 @@
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 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.
-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.
+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 later - Configure - is called from the former to setup/discover what's
+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.
"""
@@ -72,11 +56,16 @@
from distutils.command.sdist import sdist
from distutils import errors
-from setuputils import bundle
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):
@@ -132,85 +121,20 @@
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.
+ 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')
-
- 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_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')
- #
- # 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, 'codec', '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'))
+ 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
@@ -241,11 +165,12 @@
# 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'):
+ 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)
@@ -270,7 +195,8 @@
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
@@ -292,8 +218,8 @@
output_dir=self.build_temp)
#
- # Now update the _cproton extension instance to include the objects and
- # libraries
+ # 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
@@ -308,39 +234,34 @@
# 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 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('includedir')
+ 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('libdir')
+ 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.bundle_proton:
- # Proton not installed or compatible
- self.bundle_libqpid_proton_extension()
- else:
+ 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()
@@ -374,7 +295,7 @@
'sdist': CheckSDist}
setup(name='python-qpid-proton',
- version=bundle.bundled_version_str,
+ version=_PROTON_VERSION_STR + os.environ.get('PROTON_VERSION_SUFFIX', ''),
description='An AMQP based messaging library.',
author='Apache Qpid',
author_email='proton@qpid.apache.org',
diff --git a/proton-c/bindings/python/setuputils/bundle.py b/proton-c/bindings/python/setuputils/bundle.py
deleted file mode 100644
index c85daf1..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, 15, 0)
-min_qpid_proton_str = "%i.%i.%i" % min_qpid_proton
-
-bundled_version = (0, 15, 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)
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 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:
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