From 7d5c3dcd969161322deed6c43f8a6a3cb92c3369 Mon Sep 17 00:00:00 2001 From: Micah Anderson Date: Tue, 11 Nov 2014 11:53:55 -0500 Subject: upgrade to 14.4.1 --- setup.py | 1227 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1227 insertions(+) create mode 100755 setup.py (limited to 'setup.py') diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..e568f49 --- /dev/null +++ b/setup.py @@ -0,0 +1,1227 @@ +#!/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: +# +# The code to bundle libzmq as an Extension is from pyzmq-static +# pyzmq-static source used under the New BSD license +# +# pyzmq-static: +#----------------------------------------------------------------------------- + +from __future__ import with_statement, print_function + +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, + patch_lib_paths, + ) + +#----------------------------------------------------------------------------- +# 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 sys.platform == 'sunos5': + if platform.architecture()[0] == '32bit': + settings['extra_link_args'] += ['-m32'] + else: + settings['extra_link_args'] += ['-m64'] + if prefix: + settings['include_dirs'] += [pjoin(prefix, 'include')] + if not bundle_libzmq_dylib: + if sys.platform == 'sunos5' and platform.architecture()[0] == '64bit': + settings['library_dirs'] += [pjoin(prefix, 'lib/amd64')] + 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=", + " * 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 d in ('build', 'dist', 'conf'): + if os.path.exists(d): + self._clean_trees.append(d) + + for root, dirs, files in os.walk('buildutils'): + if any(root.startswith(pre) for pre in self._clean_trees): + continue + for f in files: + if os.path.splitext(f)[-1] == '.pyc': + self._clean_me.append(pjoin(root, f)) + + if '__pycache__' in dirs: + self._clean_trees.append(pjoin(root, '__pycache__')) + + for root, dirs, files in os.walk('zmq'): + if any(root.startswith(pre) for pre in self._clean_trees): + continue + + for f in files: + if os.path.splitext(f)[-1] in ('.pyc', '.so', '.o', '.pyd', '.json'): + self._clean_me.append(pjoin(root, f)) + + for d in dirs: + if d == '__pycache__': + self._clean_trees.append(pjoin(root, d)) + + # remove generated cython files + if self.all: + for root, dirs, files in os.walk(pjoin('zmq', 'backend', 'cython')): + if os.path.splitext(f)[-1] == '.c': + self._clean_me.append(pjoin(root, f)) + + bundled = glob(pjoin('zmq', 'libzmq*')) + self._clean_me.extend([ b for b in bundled if b not in _clean_me ]) + + for clean_me in self._clean_me: + print("removing %s" % clean_me) + try: + os.unlink(clean_me) + except Exception as e: + print(e, file=sys.stderr) + for clean_tree in self._clean_trees: + print("removing %s/" % clean_tree) + try: + shutil.rmtree(clean_tree) + except Exception as e: + print(e, file=sys.stderr) + + +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 build_extension(self, ext): + build_ext.build_extension(self, ext) + ext_path = self.get_ext_fullpath(ext.name) + patch_lib_paths(ext_path, self.compiler.library_dirs) + + 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 build_extension(self, ext): + build_ext_c.build_extension(self, ext) + ext_path = self.get_ext_fullpath(ext.name) + patch_lib_paths(ext_path, self.compiler.library_dirs) + + def run(self): + self.distribution.run_command('configure') + + return build_ext_c.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() + if self.inplace: + sys.path.insert(0, '') + else: + 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() + if not ext.name.startswith('zmq.'): + ext.name = 'zmq.backend.cffi.' + ext.name + 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.backend.cffi': ['*.h', '*.c'], + '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) + -- cgit v1.2.3