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