diff options
Diffstat (limited to 'setup.py')
| -rwxr-xr-x | setup.py | 1195 | 
1 files changed, 1195 insertions, 0 deletions
| diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..c86ac7e --- /dev/null +++ b/setup.py @@ -0,0 +1,1195 @@ +#!/usr/bin/env python +#----------------------------------------------------------------------------- +#  Copyright (C) PyZMQ Developers +#  Distributed under the terms of the Modified BSD License. +# +#  The `configure` subcommand is copied and adaped from h5py +#  h5py source used under the New BSD license +# +#  h5py: <http://code.google.com/p/h5py/> +# +#  The code to bundle libzmq as an Extension is from pyzmq-static +#  pyzmq-static source used under the New BSD license +# +#  pyzmq-static: <https://github.com/brandon-rhodes/pyzmq-static> +#----------------------------------------------------------------------------- + +from __future__ import with_statement + +import copy +import os +import re +import shutil +import subprocess +import sys +import time +import errno +import platform +from traceback import print_exc + + +import distutils +from distutils.core import setup, Command +from distutils.ccompiler import get_default_compiler +from distutils.ccompiler import new_compiler +from distutils.extension import Extension +from distutils.errors import CompileError, LinkError +from distutils.command.build import build +from distutils.command.build_ext import build_ext +from distutils.command.sdist import sdist +from distutils.version import LooseVersion as V + +from unittest import TextTestRunner, TestLoader +from glob import glob +from os.path import splitext, basename, join as pjoin + +from subprocess import Popen, PIPE +import logging + +try: +    from configparser import ConfigParser +except: +    from ConfigParser import ConfigParser + +try: +    import nose +except ImportError: +    nose = None + +# local script imports: +from buildutils import ( +    discover_settings, v_str, save_config, load_config, detect_zmq, merge, +    config_from_prefix, +    info, warn, fatal, debug, line, copy_and_patch_libzmq, localpath, +    fetch_libsodium, stage_libsodium_headers, fetch_libzmq, stage_platform_hpp, +    bundled_version, customize_mingw, +    test_compilation, compile_and_run, +    ) + +#----------------------------------------------------------------------------- +# Flags +#----------------------------------------------------------------------------- + +pypy = 'PyPy' in sys.version + +# reference points for zmq compatibility +min_zmq = (2,1,4) +target_zmq = bundled_version +dev_zmq = (target_zmq[0], target_zmq[1] + 1, 0) + +# set dylib ext: +if sys.platform.startswith('win'): +    lib_ext = '.dll' +elif sys.platform == 'darwin': +    lib_ext = '.dylib' +else: +    lib_ext = '.so' + +# whether any kind of bdist is happening +doing_bdist = any(arg.startswith('bdist') for arg in sys.argv[1:]) + +# allow `--zmq=foo` to be passed at any point, +# but always assign it to configure + +configure_idx = -1 +fetch_idx = -1 +for idx, arg in enumerate(list(sys.argv)): +    # track index of configure and fetch_libzmq +    if arg == 'configure': +        configure_idx = idx +    elif arg == 'fetch_libzmq': +        fetch_idx = idx +     +    if arg.startswith('--zmq='): +        sys.argv.pop(idx) +        if configure_idx < 0: +            if fetch_idx < 0: +                configure_idx = 1 +            else: +                configure_idx = fetch_idx + 1 +            sys.argv.insert(configure_idx, 'configure') +        sys.argv.insert(configure_idx + 1, arg) +        break + +#----------------------------------------------------------------------------- +# Configuration (adapted from h5py: http://h5py.googlecode.com) +#----------------------------------------------------------------------------- + +# --- compiler settings ------------------------------------------------- + +def bundled_settings(): +    """settings for linking extensions against bundled libzmq""" +    settings = {} +    settings['libraries'] = [] +    settings['library_dirs'] = [] +    settings['include_dirs'] = [pjoin("bundled", "zeromq", "include")] +    settings['runtime_library_dirs'] = [] +    # add pthread on freebsd +    # is this necessary? +    if sys.platform.startswith('freebsd'): +        settings['libraries'].append('pthread') +    elif sys.platform.startswith('win'): +        # link against libzmq in build dir: +        plat = distutils.util.get_platform() +        temp = 'temp.%s-%s' % (plat, sys.version[0:3]) +        settings['libraries'].append('libzmq') +        settings['library_dirs'].append(pjoin('build', temp, 'Release', 'buildutils')) +     +    return settings + + +def settings_from_prefix(prefix=None, bundle_libzmq_dylib=False): +    """load appropriate library/include settings from ZMQ prefix""" +    settings = {} +    settings['libraries'] = [] +    settings['include_dirs'] = [] +    settings['library_dirs'] = [] +    settings['runtime_library_dirs'] = [] +    settings['extra_link_args'] = []  +     +    if sys.platform.startswith('win'): +        settings['libraries'].append('libzmq') +         +        if prefix: +            settings['include_dirs'] += [pjoin(prefix, 'include')] +            settings['library_dirs'] += [pjoin(prefix, 'lib')] +    else: + +        # If prefix is not explicitly set, pull it from pkg-config by default. + +        if not prefix: +            try: +                p = Popen('pkg-config --variable=prefix --print-errors libzmq'.split(), stdout=PIPE, stderr=PIPE) +            except OSError as e: +                if e.errno == errno.ENOENT: +                    info("pkg-config not found") +                else: +                    warn("Running pkg-config failed - %s." % e) +                p = None +            if p is not None: +                if p.wait(): +                    info("Did not find libzmq via pkg-config:") +                    info(p.stderr.read().decode()) +                else: +                    prefix = p.stdout.readline().strip().decode() +                    info("Using zmq-prefix %s (found via pkg-config)." % prefix) + +        settings['libraries'].append('zmq') +        # add pthread on freebsd +        if sys.platform.startswith('freebsd'): +            settings['libraries'].append('pthread') +     +        if prefix: +            settings['include_dirs'] += [pjoin(prefix, 'include')] +            if not bundle_libzmq_dylib: +                if sys.platform == 'sunos5': +                  if platform.architecture()[0] == '32bit': +                    settings['library_dirs'] += [pjoin(prefix, 'lib')] +                  else: +                    settings['library_dirs'] += [pjoin(prefix, 'lib/amd64')] +                    settings['extra_link_args'] += ['-m64'] +                else:  +                   settings['library_dirs'] += [pjoin(prefix, 'lib')]   +        else: +            if sys.platform == 'darwin' and os.path.isdir('/opt/local/lib'): +                # allow macports default +                settings['include_dirs'] += ['/opt/local/include'] +                settings['library_dirs'] += ['/opt/local/lib'] +            if os.environ.get('VIRTUAL_ENV', None): +                # find libzmq installed in virtualenv +                env = os.environ['VIRTUAL_ENV'] +                settings['include_dirs'] += [pjoin(env, 'include')] +                settings['library_dirs'] += [pjoin(env, 'lib')] +     +        if bundle_libzmq_dylib: +            # bdist should link against bundled libzmq +            settings['library_dirs'].append('zmq') +            if sys.platform == 'darwin': +                pass +                # unused rpath args for OS X: +                # settings['extra_link_args'] = ['-Wl,-rpath','-Wl,$ORIGIN/..'] +            else: +                settings['runtime_library_dirs'] += ['$ORIGIN/..'] +        elif sys.platform != 'darwin': +            settings['runtime_library_dirs'] += [ +                os.path.abspath(x) for x in settings['library_dirs'] +            ] +     +    return settings + + +#----------------------------------------------------------------------------- +# Extra commands +#----------------------------------------------------------------------------- + +class Configure(build_ext): +    """Configure command adapted from h5py""" + +    description = "Discover ZMQ version and features" +     +    user_options = build_ext.user_options + [ +        ('zmq=', None, "libzmq install prefix"), +        ('build-base=', 'b', "base directory for build library"), # build_base from build +         +    ] +    def initialize_options(self): +        build_ext.initialize_options(self) +        self.zmq = None +        self.build_base = 'build' + +    # DON'T REMOVE: distutils demands these be here even if they do nothing. +    def finalize_options(self): +        build_ext.finalize_options(self) +        self.tempdir = pjoin(self.build_temp, 'scratch') +        self.has_run = False +        self.config = discover_settings(self.build_base) +        if self.zmq is not None: +            merge(self.config, config_from_prefix(self.zmq)) +        self.init_settings_from_config() +     +    def save_config(self, name, cfg): +        """write config to JSON""" +        save_config(name, cfg, self.build_base) +        # write to zmq.utils.[name].json +        save_config(name, cfg, os.path.join('zmq', 'utils')) +        # also write to build_lib, because we might be run after copying to +        # build_lib has already happened. +        build_lib_utils = os.path.join(self.build_lib, 'zmq', 'utils') +        if os.path.exists(build_lib_utils): +            save_config(name, cfg, build_lib_utils) +     +    def init_settings_from_config(self): +        """set up compiler settings, based on config""" +        cfg = self.config +         +        if cfg['libzmq_extension']: +            settings = bundled_settings() +        else: +            settings = settings_from_prefix(cfg['zmq_prefix'], self.bundle_libzmq_dylib) +     +        if 'have_sys_un_h' not in cfg: +            # don't link against anything when checking for sys/un.h +            minus_zmq = copy.deepcopy(settings) +            try: +                minus_zmq['libraries'] = [] +            except Exception: +                pass +            try: +                compile_and_run(self.tempdir, +                    pjoin('buildutils', 'check_sys_un.c'), +                    **minus_zmq +                ) +            except Exception as e: +                warn("No sys/un.h, IPC_PATH_MAX_LEN will be undefined: %s" % e) +                cfg['have_sys_un_h'] = False +            else: +                cfg['have_sys_un_h'] = True +         +            self.save_config('config', cfg) +     +        if cfg['have_sys_un_h']: +            settings['define_macros'] = [('HAVE_SYS_UN_H', 1)] +     +        settings.setdefault('define_macros', []) +     +        # include internal directories +        settings.setdefault('include_dirs', []) +        settings['include_dirs'] += [pjoin('zmq', sub) for sub in ( +            'utils', +            pjoin('backend', 'cython'), +            'devices', +        )] +         +        for ext in self.distribution.ext_modules: +            if ext.name.startswith('zmq.lib'): +                continue +            for attr, value in settings.items(): +                setattr(ext, attr, value) +         +        self.compiler_settings = settings +        self.save_config('compiler', settings) + +    def create_tempdir(self): +        self.erase_tempdir() +        os.makedirs(self.tempdir) +        if sys.platform.startswith('win'): +            # fetch libzmq.dll into local dir +            local_dll = pjoin(self.tempdir, 'libzmq.dll') +            if not self.config['zmq_prefix'] and not os.path.exists(local_dll): +                fatal("ZMQ directory must be specified on Windows via setup.cfg" +                " or 'python setup.py configure --zmq=/path/to/zeromq2'") +             +            try: +                shutil.copy(pjoin(self.config['zmq_prefix'], 'lib', 'libzmq.dll'), local_dll) +            except Exception: +                if not os.path.exists(local_dll): +                    warn("Could not copy libzmq into zmq/, which is usually necessary on Windows." +                    "Please specify zmq prefix via configure --zmq=/path/to/zmq or copy " +                    "libzmq into zmq/ manually.") + +    def erase_tempdir(self): +        try: +            shutil.rmtree(self.tempdir) +        except Exception: +            pass + +    @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 +     +    @property +    def cross_compiling(self): +        return self.config['bdist_egg'].get('plat-name', sys.platform) != sys.platform + +    @property +    def bundle_libzmq_dylib(self): +        """ +        bundle_libzmq_dylib flag for whether external libzmq library will be included in pyzmq: +        only relevant when not building libzmq extension +        """ +        if 'bundle_libzmq_dylib' in self.config: +            return self.config['bundle_libzmq_dylib'] +        elif (sys.platform.startswith('win') or self.cross_compiling) \ +                and not self.config['libzmq_extension']: +            # always bundle libzmq on Windows and cross-compilation +            return True +        elif self.config['zmq_prefix'] and not self.config['libzmq_extension']: +            # only bundle for bdists in sane environments +            return doing_bdist +        else: +            return False + +    def check_zmq_version(self): +        """check the zmq version""" +        cfg = self.config +         +        # build test program +        zmq_prefix = self.config['zmq_prefix'] +        detected = self.test_build(zmq_prefix, self.compiler_settings) +        # now check the libzmq version +         +        vers = tuple(detected['vers']) +        vs = v_str(vers) +        if vers < min_zmq: +            fatal("Detected ZMQ version: %s, but depend on ZMQ >= %s"%( +                    vs, v_str(min_zmq)) +                    +'\n       Using ZMQ=%s' % (zmq_prefix or 'unspecified')) +         +        if vers < target_zmq: +            warn("Detected ZMQ version: %s, but pyzmq targets ZMQ %s." % ( +                    vs, v_str(target_zmq)) +            ) +            warn("libzmq features and fixes introduced after %s will be unavailable." % vs) +            line() +        elif vers >= dev_zmq: +            warn("Detected ZMQ version: %s. Some new features in libzmq may not be exposed by pyzmq." % vs) +            line() + +        if sys.platform.startswith('win'): +            # fetch libzmq.dll into local dir +            local_dll = localpath('zmq','libzmq.dll') +            if not zmq_prefix and not os.path.exists(local_dll): +                fatal("ZMQ directory must be specified on Windows via setup.cfg or 'python setup.py configure --zmq=/path/to/zeromq2'") +            try: +                shutil.copy(pjoin(zmq_prefix, 'lib', 'libzmq.dll'), local_dll) +            except Exception: +                if not os.path.exists(local_dll): +                    warn("Could not copy libzmq into zmq/, which is usually necessary on Windows." +                    "Please specify zmq prefix via configure --zmq=/path/to/zmq or copy " +                    "libzmq into zmq/ manually.") +     +    def bundle_libsodium_extension(self, libzmq): +        bundledir = "bundled" +        ext_modules = self.distribution.ext_modules +        if ext_modules and any(m.name == 'zmq.libsodium' for m in ext_modules): +            # I've already been run +            return +         +        if not os.path.exists(bundledir): +            os.makedirs(bundledir) +         +        line() +        info("Using bundled libsodium") +         +        # fetch sources for libsodium +        fetch_libsodium(bundledir) +         +        # stage headers +        stage_libsodium_headers(pjoin(bundledir, 'libsodium')) +         +        # construct the Extension +        libsodium_src = pjoin(bundledir, 'libsodium', 'src', 'libsodium') +        exclude = pjoin(libsodium_src, 'crypto_stream', 'salsa20', 'amd64_xmm6') # or ref? +        exclude = pjoin(libsodium_src, 'crypto_scalarmult', 'curve25519', 'donna_c64') # or ref? +         +        libsodium_sources = [pjoin('buildutils', 'initlibsodium.c')] +         +        for dir,subdirs,files in os.walk(libsodium_src): +            if dir.startswith(exclude): +                continue +            for f in files: +                if f.endswith('.c'): +                    libsodium_sources.append(pjoin(dir, f)) +         +        libsodium = Extension( +            'zmq.libsodium', +            sources = libsodium_sources, +            include_dirs = [ +                pjoin(libsodium_src, 'include'), +                pjoin(libsodium_src, 'include', 'sodium'), +            ], +        ) +        # register the Extension +        self.distribution.ext_modules.insert(0, libsodium) +         +        if sys.byteorder == 'little': +            libsodium.define_macros.append(("NATIVE_LITTLE_ENDIAN", 1)) +        else: +            libsodium.define_macros.append(("NATIVE_BIG_ENDIAN", 1)) +         +        # tell libzmq about libsodium +        libzmq.define_macros.append(("HAVE_LIBSODIUM", 1)) +        libzmq.include_dirs.extend(libsodium.include_dirs) +         +         +     +    def bundle_libzmq_extension(self): +        bundledir = "bundled" +        ext_modules = self.distribution.ext_modules +        if ext_modules and any(m.name == 'zmq.libzmq' for m in ext_modules): +            # I've already been run +            return +         +        line() +        info("Using bundled libzmq") +         +        # fetch sources for libzmq extension: +        if not os.path.exists(bundledir): +            os.makedirs(bundledir) +         +        fetch_libzmq(bundledir) +         +        stage_platform_hpp(pjoin(bundledir, 'zeromq')) +         +        # construct the Extensions: +        libzmq = Extension( +            'zmq.libzmq', +            sources = [pjoin('buildutils', 'initlibzmq.c')] +  +                        glob(pjoin(bundledir, 'zeromq', 'src', '*.cpp')), +            include_dirs = [ +                pjoin(bundledir, 'zeromq', 'include'), +            ], +        ) +         +        # register the extension: +        self.distribution.ext_modules.insert(0, libzmq) +         +        if sys.platform.startswith('win'): +            # include defines from zeromq msvc project: +            libzmq.define_macros.append(('FD_SETSIZE', 1024)) +            libzmq.define_macros.append(('DLL_EXPORT', 1)) +             +            # When compiling the C++ code inside of libzmq itself, we want to +            # avoid "warning C4530: C++ exception handler used, but unwind +            # semantics are not enabled. Specify /EHsc". +            if self.compiler_type == 'msvc': +                libzmq.extra_compile_args.append('/EHsc') +            elif self.compiler_type == 'mingw32': +                libzmq.define_macros.append(('ZMQ_HAVE_MINGW32', 1)) + +            # And things like sockets come from libraries that must be named. + +            libzmq.libraries.extend(['rpcrt4', 'ws2_32', 'advapi32']) +        else: +            libzmq.include_dirs.append(bundledir) +             +            # check if we need to link against Realtime Extensions library +            cc = new_compiler(compiler=self.compiler_type) +            cc.output_dir = self.build_temp +            if not sys.platform.startswith(('darwin', 'freebsd')): +                line() +                info("checking for timer_create") +                if not cc.has_function('timer_create'): +                    info("no timer_create, linking librt") +                    libzmq.libraries.append('rt') +                else: +                    info("ok") +                 +                if pypy: +                    # seem to need explicit libstdc++ on linux + pypy +                    # not sure why +                    libzmq.libraries.append("stdc++") +         +            # On non-Windows, also bundle libsodium: +            self.bundle_libsodium_extension(libzmq) +         +        # update other extensions, with bundled settings +        self.config['libzmq_extension'] = True +        self.init_settings_from_config() +        self.save_config('config', self.config) +         +     +    def fallback_on_bundled(self): +        """Couldn't build, fallback after waiting a while""" +         +        line() +         +        warn('\n'.join([ +        "Failed to build or run libzmq detection test.", +        "", +        "If you expected pyzmq to link against an installed libzmq, please check to make sure:", +        "", +        "    * You have a C compiler installed", +        "    * A development version of Python is installed (including headers)", +        "    * A development version of ZMQ >= %s is installed (including headers)" % v_str(min_zmq), +        "    * If ZMQ is not in a default location, supply the argument --zmq=<path>", +        "    * If you did recently install ZMQ to a default location,", +        "      try rebuilding the ld cache with `sudo ldconfig`", +        "      or specify zmq's location with `--zmq=/usr/local`", +        "", +        ])) +         +        info('\n'.join([ +            "You can skip all this detection/waiting nonsense if you know", +            "you want pyzmq to bundle libzmq as an extension by passing:", +            "", +            "    `--zmq=bundled`", +            "", +            "I will now try to build libzmq as a Python extension", +            "unless you interrupt me (^C) in the next 10 seconds...", +            "", +        ])) +         +        for i in range(10,0,-1): +            sys.stdout.write('\r%2i...' % i) +            sys.stdout.flush() +            time.sleep(1) +         +        info("") +         +        return self.bundle_libzmq_extension() +         +     +    def test_build(self, prefix, settings): +        """do a test build ob libzmq""" +        self.create_tempdir() +        settings = settings.copy() +        if self.bundle_libzmq_dylib and not sys.platform.startswith('win'): +            # rpath slightly differently here, because libzmq not in .. but ../zmq: +            settings['library_dirs'] = ['zmq'] +            if sys.platform == 'darwin': +                pass +                # unused rpath args for OS X: +                # settings['extra_link_args'] = ['-Wl,-rpath','-Wl,$ORIGIN/../zmq'] +            else: +                settings['runtime_library_dirs'] = [ os.path.abspath(pjoin('.', 'zmq')) ] +         +        line() +        info("Configure: Autodetecting ZMQ settings...") +        info("    Custom ZMQ dir:       %s" % prefix) +        try: +            detected = detect_zmq(self.tempdir, compiler=self.compiler_type, **settings) +        finally: +            self.erase_tempdir() +         +        info("    ZMQ version detected: %s" % v_str(detected['vers'])) +         +        return detected +     + +    def finish_run(self): +        self.save_config('config', self.config) +        line() +     +    def run(self): +        cfg = self.config +         +        if cfg['libzmq_extension']: +            self.bundle_libzmq_extension() +            self.finish_run() +            return +         +        # When cross-compiling and zmq is given explicitly, we can't testbuild +        # (as we can't testrun the binary), we assume things are alright. +        if cfg['skip_check_zmq'] or self.cross_compiling: +            warn("Skipping zmq version check") +            self.finish_run() +            return +         +        zmq_prefix = cfg['zmq_prefix'] +        # There is no available default on Windows, so start with fallback unless +        # zmq was given explicitly, or libzmq extension was explicitly prohibited. +        if sys.platform.startswith("win") and \ +                not cfg['no_libzmq_extension'] and \ +                not zmq_prefix: +            self.fallback_on_bundled() +            self.finish_run() +            return +         +        if zmq_prefix and self.bundle_libzmq_dylib and not sys.platform.startswith('win'): +            copy_and_patch_libzmq(zmq_prefix, 'libzmq'+lib_ext) +         +        # first try with given config or defaults +        try: +            self.check_zmq_version() +        except Exception as e: +            # print the error as distutils would if we let it raise: +            info("\nerror: %s\n" % e) +        else: +            self.finish_run() +            return +         +        # try fallback on /usr/local on *ix if no prefix is given +        if not zmq_prefix and not sys.platform.startswith('win'): +            info("Failed with default libzmq, trying again with /usr/local") +            time.sleep(1) +            zmq_prefix = cfg['zmq_prefix'] = '/usr/local' +            self.init_settings_from_config() +            try: +                self.check_zmq_version() +            except Exception as e: +                # print the error as distutils would if we let it raise: +                info("\nerror: %s\n" % e) +            else: +                # if we get here the second run succeeded, so we need to update compiler +                # settings for the extensions with /usr/local prefix +                self.finish_run() +                return +         +        # finally, fallback on bundled +         +        if cfg['no_libzmq_extension']: +            fatal("Falling back on bundled libzmq," +            " but setup.cfg has explicitly prohibited building the libzmq extension." +            ) +         +        self.fallback_on_bundled() +         +        self.finish_run() + + +class FetchCommand(Command): +    """Fetch libzmq sources, that's it.""" +     +    description = "Fetch libzmq sources into bundled/zeromq" +     +    user_options = [ ] +     +    def initialize_options(self): +        pass +     +    def finalize_options(self): +        pass +     +    def run(self): +        # fetch sources for libzmq extension: +        bundledir = "bundled" +        if os.path.exists(bundledir): +            info("Scrubbing directory: %s" % bundledir) +            shutil.rmtree(bundledir) +        if not os.path.exists(bundledir): +            os.makedirs(bundledir) +        fetch_libsodium(bundledir) +        fetch_libzmq(bundledir) +        for tarball in glob(pjoin(bundledir, '*.tar.gz')): +            os.remove(tarball) +         + + +class TestCommand(Command): +    """Custom distutils command to run the test suite.""" + +    description = "Test PyZMQ (must have been built inplace: `setup.py build_ext --inplace`)" + +    user_options = [ ] + +    def initialize_options(self): +        self._dir = os.getcwd() + +    def finalize_options(self): +        pass +     +    def run_nose(self): +        """Run the test suite with nose.""" +        nose = 'nose.__main__' if sys.version_info < (2,7) else 'nose' +        if subprocess.call([sys.executable, '-m', nose, '-vvx', pjoin(self._dir, 'zmq', 'tests')]): +            sys.exit(1) +     +    def run_unittest(self): +        """Finds all the tests modules in zmq/tests/ and runs them.""" +        testfiles = [ ] +        for t in glob(pjoin(self._dir, 'zmq', 'tests', '*.py')): +            name = splitext(basename(t))[0] +            if name.startswith('test_'): +                testfiles.append('.'.join( +                    ['zmq.tests', name]) +                ) +        tests = TestLoader().loadTestsFromNames(testfiles) +        t = TextTestRunner(verbosity = 2) +        t.run(tests) +     +    def run(self): +        """Run the test suite, with nose, or unittest if nose is unavailable""" +        # crude check for inplace build: +        try: +            import zmq +        except ImportError: +            print_exc() +            fatal('\n       '.join(["Could not import zmq!", +            "You must build pyzmq with 'python setup.py build_ext --inplace' for 'python setup.py test' to work.", +            "If you did build pyzmq in-place, then this is a real error."])) +            sys.exit(1) +         +        info("Testing pyzmq-%s with libzmq-%s" % (zmq.pyzmq_version(), zmq.zmq_version())) +         +        if nose is None: +            warn("nose unavailable, falling back on unittest. Skipped tests will appear as ERRORs.") +            return self.run_unittest() +        else: +            return self.run_nose() + +class GitRevisionCommand(Command): +    """find the current git revision and add it to zmq.sugar.version.__revision__""" +     +    description = "Store current git revision in version.py" +     +    user_options = [ ] +     +    def initialize_options(self): +        self.version_py = pjoin('zmq','sugar','version.py') +     +    def run(self): +        try: +            p = Popen('git log -1'.split(), stdin=PIPE, stdout=PIPE, stderr=PIPE) +        except IOError: +            warn("No git found, skipping git revision") +            return +         +        if p.wait(): +            warn("checking git branch failed") +            info(p.stderr.read()) +            return +         +        line = p.stdout.readline().decode().strip() +        if not line.startswith('commit'): +            warn("bad commit line: %r" % line) +            return +         +        rev = line.split()[-1] +         +        # now that we have the git revision, we can apply it to version.py +        with open(self.version_py) as f: +            lines = f.readlines() +         +        for i,line in enumerate(lines): +            if line.startswith('__revision__'): +                lines[i] = "__revision__ = '%s'\n"%rev +                break +        with open(self.version_py, 'w') as f: +            f.writelines(lines) +     +    def finalize_options(self): +        pass + +class CleanCommand(Command): +    """Custom distutils command to clean the .so and .pyc files.""" +    user_options = [('all', 'a', +         "remove all build output, not just temporary by-products") +    ] + +    boolean_options = ['all'] + +    def initialize_options(self): +        self.all = None + +    def finalize_options(self): +        pass + +    def run(self): +        self._clean_me = [] +        self._clean_trees = [] +        for root, dirs, files in list(os.walk('buildutils')): +            for f in files: +                if os.path.splitext(f)[-1] == '.pyc': +                    self._clean_me.append(pjoin(root, f)) + +        for root, dirs, files in list(os.walk('zmq')): +            for f in files: +                if os.path.splitext(f)[-1] in ('.pyc', '.so', '.o', '.pyd', '.json'): +                    self._clean_me.append(pjoin(root, f)) +                # remove generated cython files +                if self.all and os.path.splitext(f)[-1] == '.c': +                    self._clean_me.append(pjoin(root, f)) + +            for d in dirs: +                if d == '__pycache__': +                    self._clean_trees.append(pjoin(root, d)) +         +        for d in ('build', 'conf'): +            if os.path.exists(d): +                self._clean_trees.append(d) + +        bundled = glob(pjoin('zmq', 'libzmq*')) +        self._clean_me.extend(bundled) +        for clean_me in self._clean_me: +            try: +                os.unlink(clean_me) +            except Exception: +                pass +        for clean_tree in self._clean_trees: +            try: +                shutil.rmtree(clean_tree) +            except Exception: +                pass + + +class CheckSDist(sdist): +    """Custom sdist that ensures Cython has compiled all pyx files to c.""" + +    def initialize_options(self): +        sdist.initialize_options(self) +        self._pyxfiles = [] +        for root, dirs, files in os.walk('zmq'): +            for f in files: +                if f.endswith('.pyx'): +                    self._pyxfiles.append(pjoin(root, f)) +    def run(self): +        self.run_command('fetch_libzmq') +        if 'cython' in cmdclass: +            self.run_command('cython') +        else: +            for pyxfile in self._pyxfiles: +                cfile = pyxfile[:-3]+'c' +                msg = "C-source file '%s' not found."%(cfile)+\ +                " Run 'setup.py cython' before sdist." +                assert os.path.isfile(cfile), msg +        sdist.run(self) + +class CheckingBuildExt(build_ext): +    """Subclass build_ext to get clearer report if Cython is necessary.""" +     +    def check_cython_extensions(self, extensions): +        for ext in extensions: +          for src in ext.sources: +            if not os.path.exists(src): +                fatal("""Cython-generated file '%s' not found. +                Cython >= 0.16 is required to compile pyzmq from a development branch. +                Please install Cython or download a release package of pyzmq. +                """%src) +     +    def build_extensions(self): +        self.check_cython_extensions(self.extensions) +        self.check_extensions_list(self.extensions) +         +        if self.compiler.compiler_type == 'mingw32': +            customize_mingw(self.compiler) +         +        for ext in self.extensions: +             +            self.build_extension(ext) +     +    def run(self): +        # check version, to prevent confusing undefined constant errors +        self.distribution.run_command('configure') +        build_ext.run(self) + + +class ConstantsCommand(Command): +    """Rebuild templated files for constants +     +    To be run after adding new constants to `utils/constant_names`. +    """ +    user_options = [] +    def initialize_options(self): +        return  +     +    def finalize_options(self): +        pass +     +    def run(self): +        from buildutils.constants import render_constants +        render_constants() + +#----------------------------------------------------------------------------- +# Extensions +#----------------------------------------------------------------------------- + +cmdclass = {'test':TestCommand, 'clean':CleanCommand, 'revision':GitRevisionCommand, +            'configure': Configure, 'fetch_libzmq': FetchCommand, +            'sdist': CheckSDist, 'constants': ConstantsCommand, +        } + +if 'bdist_wheel' in sys.argv and sys.platform == 'darwin': +    from wheel.bdist_wheel import bdist_wheel +     +    class bdist_wheel_mac_tag(bdist_wheel): +        """add 'current' platform tags to wheels +         +        A 10.6-intel wheel works on all 10.X >= 10.6 and arch in 32,64,intel. +         +        Since that would produce a ludicrous filename, just add the two most common: +         +        - current-intel +        - current-x86_64 +         +        partial workaround for pypa/pip#1465 +        """ +        def get_tag(self): +            import platform +            impl, abi, plat = bdist_wheel.get_tag(self) +            plat_tag_re = re.compile(r'macosx_(\d+)_(\d+)_(.+)') +            m = plat_tag_re.match(plat) +            if m: +                plat_tags = [plat] +                major, minor, arch = m.groups() +                arches = [arch] +                if arch == 'intel': +                    arches.append('x86_64') +                host_list = re.findall('\d+', platform.mac_ver()[0]) +                host = (int(host_list[0]), int(host_list[1])) +                host_s = '%s_%s' % tuple(host_list[:2]) +                target = (int(major), int(minor)) +                if host > target or len(arches) > 1: +                    for arch in arches: +                        plat_tags.append('macosx_%s_%s' % (host_s, arch)) +                 +                plat = '.'.join(sorted(set(plat_tags))) +            return (impl, abi, plat) +     +    cmdclass['bdist_wheel'] = bdist_wheel_mac_tag +     +     +def makename(path, ext): +    return os.path.abspath(pjoin('zmq', *path)) + ext + +pxd = lambda *path: makename(path, '.pxd') +pxi = lambda *path: makename(path, '.pxi') +pyx = lambda *path: makename(path, '.pyx') +dotc = lambda *path: makename(path, '.c') + +libzmq = pxd('backend', 'cython', 'libzmq') +buffers = pxd('utils', 'buffers') +message = pxd('backend', 'cython', 'message') +context = pxd('backend', 'cython', 'context') +socket = pxd('backend', 'cython', 'socket') +utils = pxd('backend', 'cython', 'utils') +checkrc = pxd('backend', 'cython', 'checkrc') +monqueue = pxd('devices', 'monitoredqueue') + +submodules = { +    'backend.cython' : {'constants': [libzmq, pxi('backend', 'cython', 'constants')], +            'error':[libzmq, checkrc], +            '_poll':[libzmq, socket, context, checkrc], +            'utils':[libzmq, utils, checkrc], +            'context':[context, libzmq, checkrc], +            'message':[libzmq, buffers, message, checkrc], +            'socket':[context, message, socket, libzmq, buffers, checkrc], +            '_device':[libzmq, socket, context, checkrc], +            '_version':[libzmq], +    }, +    'devices' : { +            'monitoredqueue':[buffers, libzmq, monqueue, socket, context, checkrc], +    }, +} + +try: +    import Cython +    if V(Cython.__version__) < V('0.16'): +        raise ImportError("Cython >= 0.16 required, found %s" % Cython.__version__) +    from Cython.Distutils import build_ext as build_ext_c +    cython=True +except Exception: +    cython=False +    suffix = '.c' +    cmdclass['build_ext'] = CheckingBuildExt +     +    class MissingCython(Command): +         +        user_options = [] +         +        def initialize_options(self): +            pass +         +        def finalize_options(self): +            pass +         +        def run(self): +            try: +                import Cython +            except ImportError: +                warn("Cython is missing") +            else: +                cv = getattr(Cython, "__version__", None) +                if cv is None or V(cv) < V('0.16'): +                    warn( +                        "Cython >= 0.16 is required for compiling Cython sources, " +                        "found: %s" % (cv or "super old") +                    ) +    cmdclass['cython'] = MissingCython +else: +     +    suffix = '.pyx' +     +    class CythonCommand(build_ext_c): +        """Custom distutils command subclassed from Cython.Distutils.build_ext +        to compile pyx->c, and stop there. All this does is override the  +        C-compile method build_extension() with a no-op.""" +         +        description = "Compile Cython sources to C" +         +        def build_extension(self, ext): +            pass +     +    class zbuild_ext(build_ext_c): +         +        def build_extensions(self): +            if self.compiler.compiler_type == 'mingw32': +                customize_mingw(self.compiler) +            return build_ext_c.build_extensions(self) +         +        def run(self): +            self.distribution.run_command('configure') +            return build_ext.run(self) +     +    cmdclass['cython'] = CythonCommand +    cmdclass['build_ext'] =  zbuild_ext + +extensions = [] +for submod, packages in submodules.items(): +    for pkg in sorted(packages): +        sources = [pjoin('zmq', submod.replace('.', os.path.sep), pkg+suffix)] +        if suffix == '.pyx': +            sources.extend(packages[pkg]) +        ext = Extension( +            'zmq.%s.%s'%(submod, pkg), +            sources = sources, +            include_dirs=[pjoin('zmq', sub) for sub in ('utils',pjoin('backend', 'cython'),'devices')], +        ) +        if suffix == '.pyx' and ext.sources[0].endswith('.c'): +            # undo setuptools stupidly clobbering cython sources: +            ext.sources = sources +        extensions.append(ext) + +if pypy: +    # add dummy extension, to ensure build_ext runs +    dummy_ext = Extension('dummy', sources=[]) +    extensions = [dummy_ext] +     +    bld_ext = cmdclass['build_ext'] +    class pypy_build_ext(bld_ext): +        """hack to build pypy extension only after building bundled libzmq +         +        otherwise it will fail when libzmq is bundled. +        """ +        def build_extensions(self): +            self.extensions.remove(dummy_ext) +            bld_ext.build_extensions(self) +            # build ffi extension after bundled libzmq, +            # because it may depend on linking it +            here = os.getcwd() +            sys.path.insert(0, self.build_lib) +            try: +                from zmq.backend.cffi import ffi +            except ImportError as e: +                warn("Couldn't get CFFI extension: %s" % e) +            else: +                ext = ffi.verifier.get_extension() +                self.extensions.append(ext) +                self.build_extension(ext) +            finally: +                sys.path.pop(0) + +     +    # How many build_ext subclasses is this? 5? Gross. +    cmdclass['build_ext'] = pypy_build_ext + + +package_data = {'zmq': ['*.pxd'], +                'zmq.backend.cython': ['*.pxd'], +                'zmq.devices': ['*.pxd'], +                'zmq.utils': ['*.pxd', '*.h', '*.json'], +} + +package_data['zmq'].append('libzmq'+lib_ext) + +def extract_version(): +    """extract pyzmq version from sugar/version.py, so it's not multiply defined""" +    with open(pjoin('zmq', 'sugar', 'version.py')) as f: +        while True: +            line = f.readline() +            if line.startswith('VERSION'): +                lines = [] +                while line and not line.startswith('def'): +                    lines.append(line) +                    line = f.readline() +                break +    exec(''.join(lines), globals()) +    return __version__ + +def find_packages(): +    """adapted from IPython's setupbase.find_packages()""" +    packages = [] +    for dir,subdirs,files in os.walk('zmq'): +        package = dir.replace(os.path.sep, '.') +        if '__init__.py' not in files: +            # not a package +            continue +        packages.append(package) +    return packages + +#----------------------------------------------------------------------------- +# Main setup +#----------------------------------------------------------------------------- + +long_desc = \ +""" +PyZMQ is the official Python binding for the ZeroMQ Messaging Library (http://www.zeromq.org). +""" + +setup_args = dict( +    name = "pyzmq", +    version = extract_version(), +    packages = find_packages(), +    ext_modules = extensions, +    package_data = package_data, +    author = "Brian E. Granger, Min Ragan-Kelley", +    author_email = "zeromq-dev@lists.zeromq.org", +    url = 'http://github.com/zeromq/pyzmq', +    download_url = 'http://github.com/zeromq/pyzmq/releases', +    description = "Python bindings for 0MQ", +    long_description = long_desc,  +    license = "LGPL+BSD", +    cmdclass = cmdclass, +    classifiers = [ +        'Development Status :: 5 - Production/Stable', +        'Intended Audience :: Developers', +        'Intended Audience :: Science/Research', +        'Intended Audience :: System Administrators', +        'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', +        'License :: OSI Approved :: BSD License', +        'Operating System :: MacOS :: MacOS X', +        'Operating System :: Microsoft :: Windows', +        'Operating System :: POSIX', +        'Topic :: System :: Networking', +        'Programming Language :: Python :: 2', +        'Programming Language :: Python :: 2.6', +        'Programming Language :: Python :: 2.7', +        'Programming Language :: Python :: 3', +        'Programming Language :: Python :: 3.2', +        'Programming Language :: Python :: 3.3', +    ], +) +if 'setuptools' in sys.modules and pypy: +    setup_args['install_requires'] = [ +        'py', +        'cffi', +    ] + +setup(**setup_args) + | 
