"""Detect zmq version""" # # Copyright (c) 2011 Min Ragan-Kelley # # This file is part of pyzmq, copied and adapted from h5py. # h5py source used under the New BSD license # # h5py: # BSD license: # # pyzmq is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # pyzmq is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . import shutil import sys import os import logging import pickle from distutils import ccompiler from subprocess import Popen, PIPE try: from configparser import ConfigParser except: from ConfigParser import ConfigParser pjoin = os.path.join #----------------------------------------------------------------------------- # Logging (adapted from h5py: http://h5py.googlecode.com) #----------------------------------------------------------------------------- logger = logging.getLogger() logger.addHandler(logging.StreamHandler(sys.stderr)) def debug(what): pass def fatal(instring, code=1): logger.error("Fatal: "+instring) exit(code) def warn(instring): logger.error("Warning: "+instring) #----------------------------------------------------------------------------- # Utility functions (adapted from h5py: http://h5py.googlecode.com) #----------------------------------------------------------------------------- def detect_zmq(basedir, **compiler_attrs): """Compile, link & execute a test program, in empty directory `basedir`. The C compiler will be updated with any keywords given via setattr. Parameters ---------- basedir : path The location where the test program will be compiled and run **compiler_attrs : dict Any extra compiler attributes, which will be set via ``setattr(cc)``. Returns ------- A dict of properties for zmq compilation, with the following two keys: vers : tuple The ZMQ version as a tuple of ints, e.g. (2,2,0) options : dict The compiler options used to compile the test function, e.g. `include_dirs`, `library_dirs`, `libs`, etc. """ cc = ccompiler.new_compiler() for name, val in compiler_attrs.items(): setattr(cc, name, val) cfile = pjoin(basedir, 'vers.c') efile = pjoin(basedir, 'vers') f = open(cfile, 'w') try: f.write( r""" #include #include "zmq.h" int main(){ unsigned int major, minor, patch; zmq_version(&major, &minor, &patch); fprintf(stdout, "vers: %d.%d.%d\n", major, minor, patch); return 0; } """) finally: f.close() if sys.platform == 'darwin': # allow for missing UB arch, since it will still work: preargs = ['-undefined', 'dynamic_lookup'] else: preargs = None objs = cc.compile([cfile]) cc.link_executable(objs, efile, extra_preargs=preargs) result = Popen(efile, stdout=PIPE, stderr=PIPE) so, se = result.communicate() # for py3k: so = so.decode() se = se.decode() if result.returncode: msg = "Error running version detection script:\n%s\n%s" % (so,se) logging.error(msg) raise IOError(msg) handlers = {'vers': lambda val: tuple(int(v) for v in val.split('.'))} props = {} for line in (x for x in so.split('\n') if x): key, val = line.split(':') props[key] = handlers[key](val) props['options'] = compiler_attrs return props def localpath(*args): plist = [os.path.dirname(__file__)]+list(args) return os.path.abspath(pjoin(*plist)) def loadpickle(name): """ Load object from pickle file, or None if it can't be opened """ name = pjoin('conf', name) try: f = open(name,'rb') except IOError: # raise return None try: return pickle.load(f) except Exception: # raise return None finally: f.close() def savepickle(name, data): """ Save to pickle file, exiting if it can't be written """ if not os.path.exists('conf'): os.mkdir('conf') name = pjoin('conf', name) try: f = open(name, 'wb') except IOError: fatal("Can't open pickle file \"%s\" for writing" % name) try: pickle.dump(data, f, 0) finally: f.close() def v_str(v_tuple): """turn (2,0,1) into '2.0.1'.""" return ".".join(str(x) for x in v_tuple) def get_eargs(): """ Look for options in environment vars """ settings = {} zmq = os.environ.get("ZMQ_DIR", '') if zmq != '': debug("Found environ var ZMQ_DIR=%s" % zmq) settings['zmq'] = zmq return settings def get_cfg_args(): """ Look for options in setup.cfg """ settings = {} zmq = '' if not os.path.exists('setup.cfg'): return settings cfg = ConfigParser() cfg.read('setup.cfg') if 'build_ext' in cfg.sections() and \ cfg.has_option('build_ext', 'include_dirs'): includes = cfg.get('build_ext', 'include_dirs') include = includes.split(os.pathsep)[0] if include.endswith('include') and os.path.isdir(include): zmq = include[:-8] if zmq != '': debug("Found ZMQ=%s in setup.cfg" % zmq) settings['zmq'] = zmq return settings def get_cargs(): """ Look for global options in the command line """ settings = loadpickle('buildconf.pickle') if settings is None: settings = {} for arg in sys.argv[:]: if arg.find('--zmq=') == 0: zmq = arg.split('=')[-1] if zmq.lower() == 'default': settings.pop('zmq', None) else: settings['zmq'] = zmq sys.argv.remove(arg) savepickle('buildconf.pickle', settings) return settings def discover_settings(): """ Discover custom settings for ZMQ path""" settings = get_cfg_args() # lowest priority settings.update(get_eargs()) settings.update(get_cargs()) # highest priority return settings.get('zmq') def copy_and_patch_libzmq(ZMQ, libzmq): """copy libzmq into source dir, and patch it if necessary. This command is necessary prior to running a bdist on Linux or OS X. """ if sys.platform.startswith('win'): return # copy libzmq into zmq for bdist local = localpath('zmq',libzmq) if ZMQ is None and not os.path.exists(local): fatal("Please specify zmq prefix via `setup.py configure --zmq=/path/to/zmq` " "or copy libzmq into zmq/ manually prior to running bdist.") try: lib = pjoin(ZMQ, 'lib', libzmq) print ("copying %s -> %s"%(lib, local)) shutil.copy(lib, local) except Exception: if not os.path.exists(local): fatal("Could not copy libzmq into zmq/, which is necessary for bdist. " "Please specify zmq prefix via `setup.py configure --zmq=/path/to/zmq` " "or copy libzmq into zmq/ manually.") finally: # link libzmq.dylib -> libzmq.1.dylib link = localpath('zmq',libzmq.replace('.1','')) if not os.path.exists(link): os.symlink(libzmq, link) if sys.platform == 'darwin': # patch install_name on darwin, instead of using rpath cmd = ['install_name_tool', '-id', '@loader_path/../%s'%libzmq, local] try: p = Popen(cmd, stdout=PIPE,stderr=PIPE) except OSError: fatal("install_name_tool not found, cannot patch libzmq for bundling.") out,err = p.communicate() if p.returncode: fatal("Could not patch bundled libzmq install_name: %s"%err, p.returncode)