diff --git a/buildutils.py b/buildutils.py
new file mode 100644
index 0000000..83c18a6
--- /dev/null
+++ b/buildutils.py
@@ -0,0 +1,268 @@
+"""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)
+