4
#-----------------------------------------------------------------------------
5
# Copyright (c) 2012 Brian Granger, Min Ragan-Kelley
7
# This file is part of pyzmq
9
# Distributed under the terms of the New BSD License. The full license is in
10
# the file COPYING.BSD, distributed as part of this software.
12
# The `configure` subcommand is copied and adaped from h5py
13
# h5py source used under the New BSD license
15
# h5py: <http://code.google.com/p/h5py/>
16
#-----------------------------------------------------------------------------
18
#-----------------------------------------------------------------------------
20
#-----------------------------------------------------------------------------
21
from __future__ import with_statement
28
from traceback import print_exc
30
from distutils.core import setup, Command
31
from distutils.ccompiler import get_default_compiler
32
from distutils.extension import Extension
33
from distutils.errors import CompileError, LinkError
34
from distutils.command.build import build
35
from distutils.command.build_ext import build_ext
36
from distutils.command.sdist import sdist
38
from unittest import TextTestRunner, TestLoader
40
from os.path import splitext, basename, join as pjoin
42
from subprocess import Popen, PIPE
46
from configparser import ConfigParser
48
from ConfigParser import ConfigParser
55
# local script imports:
56
from buildutils import (discover_settings, v_str, localpath, savepickle, loadpickle, detect_zmq,
57
warn, fatal, copy_and_patch_libzmq)
59
#-----------------------------------------------------------------------------
61
#-----------------------------------------------------------------------------
62
# ignore unused-function and strict-aliasing warnings, of which there
63
# will be many from the Cython generated code:
64
# note that this is only for gcc-style compilers
65
if get_default_compiler() in ('unix', 'mingw32'):
66
ignore_common_warnings=True
68
ignore_common_warnings=False
70
# the minimum zeromq version this will work against:
74
if sys.platform.startswith('win'):
76
elif sys.platform == 'darwin':
81
# whether any kind of bdist is happening
82
doing_bdist = any(arg.startswith('bdist') for arg in sys.argv[1:])
84
#-----------------------------------------------------------------------------
85
# Configuration (adapted from h5py: http://h5py.googlecode.com)
86
#-----------------------------------------------------------------------------
89
ZMQ = discover_settings()
91
if ZMQ is not None and not os.path.exists(ZMQ):
92
warn("ZMQ directory \"%s\" does not appear to exist" % ZMQ)
94
# bundle_libzmq flag for whether libzmq will be included in pyzmq:
95
if sys.platform.startswith('win'):
98
bundle_libzmq = doing_bdist
100
bundle_libzmq = False
102
# --- compiler settings -------------------------------------------------
104
def settings_from_prefix(zmq=None):
105
"""load appropriate library/include settings from ZMQ prefix"""
106
if sys.platform.startswith('win'):
108
'libraries' : ['libzmq'],
113
settings['include_dirs'] += [pjoin(zmq, 'include')]
114
settings['library_dirs'] += [pjoin(zmq, 'lib')]
117
'libraries' : ['zmq'],
122
# add pthread on freebsd
123
if sys.platform.startswith('freebsd'):
124
settings['libraries'].append('pthread')
127
settings['include_dirs'] += [pjoin(zmq, 'include')]
128
settings['library_dirs'] += [pjoin(zmq, 'lib')]
129
elif sys.platform == 'darwin' and os.path.isdir('/opt/local/lib'):
130
# allow macports default
131
settings['include_dirs'] += ['/opt/local/include']
132
settings['library_dirs'] += ['/opt/local/lib']
135
# bdist should link against bundled libzmq
136
settings['library_dirs'] = ['zmq']
137
if sys.platform == 'darwin':
139
# unused rpath args for OSX:
140
# settings['extra_link_args'] = ['-Wl,-rpath','-Wl,$ORIGIN/..']
142
settings['runtime_library_dirs'] = ['$ORIGIN/..']
143
elif sys.platform != 'darwin':
144
settings['runtime_library_dirs'] = [os.path.abspath(x) for x in settings['library_dirs']]
146
# suppress common warnings
149
if ignore_common_warnings:
150
for warning in ('unused-function', 'strict-aliasing'):
151
extra_flags.append('-Wno-'+warning)
153
settings['extra_compile_args'] = extra_flags
155
# include internal directories
156
settings['include_dirs'] += [pjoin('zmq', sub) for sub in ('utils','core','devices')]
160
COMPILER_SETTINGS = settings_from_prefix(ZMQ)
163
#-----------------------------------------------------------------------------
165
#-----------------------------------------------------------------------------
167
class Configure(Command):
168
"""Configure command adapted from h5py"""
170
description = "Discover ZMQ version and features"
172
# DON'T REMOVE: distutils demands these be here even if they do nothing.
175
def initialize_options(self):
177
self.settings = copy.copy(COMPILER_SETTINGS)
179
def finalize_options(self):
184
def create_tempdir(self):
186
os.mkdir(self.tempdir)
187
if sys.platform.startswith('win'):
188
# fetch libzmq.dll into local dir
189
local_dll = pjoin(self.tempdir, 'libzmq.dll')
190
if ZMQ is None and not os.path.exists(local_dll):
191
fatal("ZMQ directory must be specified on Windows via setup.cfg or 'python setup.py configure --zmq=/path/to/zeromq2'")
194
shutil.copy(pjoin(ZMQ, 'lib', 'libzmq.dll'), local_dll)
196
if not os.path.exists(local_dll):
197
warn("Could not copy libzmq into zmq/, which is usually necessary on Windows."
198
"Please specify zmq prefix via configure --zmq=/path/to/zmq or copy "
199
"libzmq into zmq/ manually.")
202
def erase_tempdir(self):
204
shutil.rmtree(self.tempdir)
209
return loadpickle('configure.pickle')
211
def check_zmq_version(self):
213
if zmq is not None and not os.path.isdir(zmq):
214
fatal("Custom zmq directory \"%s\" does not exist" % zmq)
216
config = self.getcached()
217
if config is None or config['options'] != self.settings:
223
vers = config['vers']
226
fatal("Detected ZMQ version: %s, but depend on zmq >= %s"%(
228
+'\n Using ZMQ=%s'%(zmq or 'unspecified'))
229
pyzmq_version = extract_version().strip('abcdefghijklmnopqrstuvwxyz')
231
if vs < pyzmq_version:
232
warn("Detected ZMQ version: %s, but pyzmq targets zmq %s."%(
234
warn("libzmq features and fixes introduced after %s will be unavailable."%vs)
237
warn("Detected ZMQ version: %s. pyzmq's support for libzmq-dev is experimental."%vs)
240
if sys.platform.startswith('win'):
241
# fetch libzmq.dll into local dir
242
local_dll = localpath('zmq','libzmq.dll')
243
if zmq is None and not os.path.exists(local_dll):
244
fatal("ZMQ directory must be specified on Windows via setup.cfg or 'python setup.py configure --zmq=/path/to/zeromq2'")
246
shutil.copy(pjoin(zmq, 'lib', 'libzmq.dll'), local_dll)
248
if not os.path.exists(local_dll):
249
warn("Could not copy libzmq into zmq/, which is usually necessary on Windows."
250
"Please specify zmq prefix via configure --zmq=/path/to/zmq or copy "
251
"libzmq into zmq/ manually.")
254
self.create_tempdir()
255
settings = self.settings
256
if bundle_libzmq and not sys.platform.startswith('win'):
257
# rpath slightly differently here, because libzmq not in .. but ../zmq:
258
settings['library_dirs'] = ['zmq']
259
if sys.platform == 'darwin':
261
# unused rpath args for OSX:
262
# settings['extra_link_args'] = ['-Wl,-rpath','-Wl,$ORIGIN/../zmq']
264
settings['runtime_library_dirs'] = ['$ORIGIN/../zmq']
267
print ("Configure: Autodetecting ZMQ settings...")
268
print (" Custom ZMQ dir: %s" % (self.zmq,))
269
config = detect_zmq(self.tempdir, **settings)
271
# if zmq unspecified on *ix, try again with explicit /usr/local
272
if self.zmq is None and not sys.platform.startswith('win'):
274
print ("Failed with default libzmq, trying again with /usr/local")
275
self.zmq = '/usr/local'
276
self.settings = settings_from_prefix(self.zmq)
279
# if we get here the second run succeeded, so we need to update compiler
280
# settings for the extensions with /usr/local prefix
281
for ext in self.distribution.ext_modules:
282
for key,value in self.settings.iteritems():
283
setattr(ext, key, value)
286
etype, evalue, tb = sys.exc_info()
287
# print the error as distutils would if we let it raise:
288
print ("error: %s" % evalue)
289
if etype is CompileError:
291
elif etype is LinkError:
294
action = 'build or run'
297
Failed to %s ZMQ test program. Please check to make sure:
299
* You have a C compiler installed
300
* A development version of Python is installed (including header files)
301
* A development version of ZMQ >= %s is installed (including header files)
302
* If ZMQ is not in a default location, supply the argument --zmq=<path>
303
* If you did recently install ZMQ to a default location,
304
try rebuilding the ld cache with `sudo ldconfig`
305
or specify zmq's location with `--zmq=/usr/local`
306
"""%(action, v_str(min_zmq)))
309
savepickle('configure.pickle', config)
310
print (" ZMQ version detected: %s" % v_str(config['vers']))
316
class TestCommand(Command):
317
"""Custom distutils command to run the test suite."""
321
def initialize_options(self):
322
self._dir = os.getcwd()
324
def finalize_options(self):
328
"""Run the test suite with nose."""
329
return nose.core.TestProgram(argv=["", '-vv', pjoin(self._dir, 'zmq', 'tests')])
331
def run_unittest(self):
332
"""Finds all the tests modules in zmq/tests/ and runs them."""
334
for t in glob(pjoin(self._dir, 'zmq', 'tests', '*.py')):
335
name = splitext(basename(t))[0]
336
if name.startswith('test_'):
337
testfiles.append('.'.join(
340
tests = TestLoader().loadTestsFromNames(testfiles)
341
t = TextTestRunner(verbosity = 2)
345
"""Run the test suite, with nose, or unittest if nose is unavailable"""
346
# crude check for inplace build:
351
fatal('\n '.join(["Could not import zmq!",
352
"You must build pyzmq with 'python setup.py build_ext --inplace' for 'python setup.py test' to work.",
353
"If you did build pyzmq in-place, then this is a real error."]))
357
print ("nose unavailable, falling back on unittest. Skipped tests will appear as ERRORs.")
358
return self.run_unittest()
360
return self.run_nose()
362
class GitRevisionCommand(Command):
363
"""find the current git revision and add it to zmq.core.verion.__revision__"""
367
def initialize_options(self):
368
self.version_py = pjoin('zmq','core','version.py')
372
p = Popen('git log -1'.split(), stdin=PIPE, stdout=PIPE, stderr=PIPE)
374
print ("No git found, skipping git revision")
378
print ("checking git branch failed")
379
print (p.stderr.read())
382
line = p.stdout.readline().decode().strip()
383
if not line.startswith('commit'):
384
print ("bad commit line: %r"%line)
387
rev = line.split()[-1]
389
# now that we have the git revision, we can apply it to version.py
390
with open(self.version_py) as f:
391
lines = f.readlines()
393
for i,line in enumerate(lines):
394
if line.startswith('__revision__'):
395
lines[i] = "__revision__ = '%s'\n"%rev
397
with open(self.version_py, 'w') as f:
400
def finalize_options(self):
403
class CleanCommand(Command):
404
"""Custom distutils command to clean the .so and .pyc files."""
408
def initialize_options(self):
410
self._clean_trees = []
411
for root, dirs, files in list(os.walk('zmq')):
413
if os.path.splitext(f)[-1] in ('.pyc', '.so', '.o', '.pyd'):
414
self._clean_me.append(pjoin(root, f))
416
if d == '__pycache__':
417
self._clean_trees.append(pjoin(root, d))
420
if os.path.exists(d):
421
self._clean_trees.append(d)
423
bundled = glob(pjoin('zmq', 'libzmq*'))
424
self._clean_me.extend(bundled)
428
def finalize_options(self):
432
for clean_me in self._clean_me:
437
for clean_tree in self._clean_trees:
439
shutil.rmtree(clean_tree)
444
class CheckSDist(sdist):
445
"""Custom sdist that ensures Cython has compiled all pyx files to c."""
447
def initialize_options(self):
448
sdist.initialize_options(self)
450
for root, dirs, files in os.walk('zmq'):
452
if f.endswith('.pyx'):
453
self._pyxfiles.append(pjoin(root, f))
455
if 'cython' in cmdclass:
456
self.run_command('cython')
458
for pyxfile in self._pyxfiles:
459
cfile = pyxfile[:-3]+'c'
460
msg = "C-source file '%s' not found."%(cfile)+\
461
" Run 'setup.py cython' before sdist."
462
assert os.path.isfile(cfile), msg
465
class CopyingBuild(build):
466
"""subclass of build that copies libzmq if doing bdist."""
469
if bundle_libzmq and not sys.platform.startswith('win'):
470
# always rebuild before bdist, because linking may be wrong:
471
self.run_command('clean')
472
copy_and_patch_libzmq(ZMQ, 'libzmq'+lib_ext)
475
class CheckingBuildExt(build_ext):
476
"""Subclass build_ext to get clearer report if Cython is neccessary."""
478
def check_cython_extensions(self, extensions):
479
for ext in extensions:
480
for src in ext.sources:
481
if not os.path.exists(src):
482
fatal("""Cython-generated file '%s' not found.
483
Cython is required to compile pyzmq from a development branch.
484
Please install Cython or download a release package of pyzmq.
487
def build_extensions(self):
488
self.check_cython_extensions(self.extensions)
489
self.check_extensions_list(self.extensions)
491
for ext in self.extensions:
492
self.build_extension(ext)
495
# check version, to prevent confusing undefined constant errors
496
configure = self.distribution.get_command_obj('configure')
497
configure.check_zmq_version()
501
#-----------------------------------------------------------------------------
503
#-----------------------------------------------------------------------------
505
cmdclass = {'test':TestCommand, 'clean':CleanCommand, 'revision':GitRevisionCommand,
506
'configure': Configure, 'build': CopyingBuild}
508
def pxd(subdir, name):
509
return os.path.abspath(pjoin('zmq', subdir, name+'.pxd'))
511
def pyx(subdir, name):
512
return os.path.abspath(pjoin('zmq', subdir, name+'.pyx'))
514
def dotc(subdir, name):
515
return os.path.abspath(pjoin('zmq', subdir, name+'.c'))
517
libzmq = pxd('core', 'libzmq')
518
buffers = pxd('utils', 'buffers')
519
message = pxd('core', 'message')
520
context = pxd('core', 'context')
521
socket = pxd('core', 'socket')
522
monqueue = pxd('devices', 'monitoredqueue')
525
core = {'constants': [libzmq],
527
'_poll':[libzmq, socket, context],
528
'stopwatch':[libzmq, pxd('core','stopwatch')],
529
'context':[context, libzmq],
530
'message':[libzmq, buffers, message],
531
'socket':[context, message, socket, libzmq, buffers],
532
'device':[libzmq, socket, context],
536
'monitoredqueue':[buffers, libzmq, monqueue, socket, context],
539
'initthreads':[libzmq],
540
'rebuffer':[buffers],
545
from Cython.Distutils import build_ext
550
cmdclass['build_ext'] = CheckingBuildExt
555
class CythonCommand(build_ext):
556
"""Custom distutils command subclassed from Cython.Distutils.build_ext
557
to compile pyx->c, and stop there. All this does is override the
558
C-compile method build_extension() with a no-op."""
559
def build_extension(self, ext):
562
class zbuild_ext(build_ext):
564
configure = self.distribution.get_command_obj('configure')
565
configure.check_zmq_version()
566
return build_ext.run(self)
568
cmdclass['cython'] = CythonCommand
569
cmdclass['build_ext'] = zbuild_ext
570
cmdclass['sdist'] = CheckSDist
573
for submod, packages in submodules.items():
574
for pkg in sorted(packages):
575
sources = [pjoin('zmq', submod, pkg+suffix)]
577
sources.extend(packages[pkg])
579
'zmq.%s.%s'%(submod, pkg),
583
extensions.append(ext)
586
package_data = {'zmq':['*.pxd'],
587
'zmq.core':['*.pxd'],
588
'zmq.devices':['*.pxd'],
589
'zmq.utils':['*.pxd', '*.h'],
593
package_data['zmq'].append('libzmq'+lib_ext)
595
def extract_version():
596
"""extract pyzmq version from core/version.py, so it's not multiply defined"""
597
with open(pjoin('zmq', 'core', 'version.py')) as f:
599
while not line.startswith("__version__"):
601
exec(line, globals())
602
if 'bdist_msi' in sys.argv:
603
# msi has strict version requirements, which requires that
604
# we strip any dev suffix
605
return re.match(r'\d+(\.\d+)+', __version__).group()
610
"""adapted from IPython's setupbase.find_packages()"""
612
for dir,subdirs,files in os.walk('zmq'):
613
package = dir.replace(os.path.sep, '.')
614
if '__init__.py' not in files:
617
packages.append(package)
620
#-----------------------------------------------------------------------------
622
#-----------------------------------------------------------------------------
626
PyZMQ is a lightweight and super-fast messaging library built on top of
627
the ZeroMQ library (http://www.zeromq.org).
632
version = extract_version(),
633
packages = find_packages(),
634
ext_modules = extensions,
635
package_data = package_data,
636
author = "Brian E. Granger, Min Ragan-Kelley",
637
author_email = "zeromq-dev@lists.zeromq.org",
638
url = 'http://github.com/zeromq/pyzmq',
639
download_url = 'http://github.com/zeromq/pyzmq/downloads',
640
description = "Python bindings for 0MQ.",
641
long_description = long_desc,
642
license = "LGPL+BSD",
645
'Development Status :: 5 - Production/Stable',
646
'Intended Audience :: Developers',
647
'Intended Audience :: Financial and Insurance Industry',
648
'Intended Audience :: Science/Research',
649
'Intended Audience :: System Administrators',
650
'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
651
'License :: OSI Approved :: BSD License',
652
'Operating System :: MacOS :: MacOS X',
653
'Operating System :: Microsoft :: Windows',
654
'Operating System :: POSIX',
655
'Topic :: System :: Networking',
656
'Programming Language :: Python :: 2',
657
'Programming Language :: Python :: 2.6',
658
'Programming Language :: Python :: 2.7',
659
'Programming Language :: Python :: 3',
660
'Programming Language :: Python :: 3.0',
661
'Programming Language :: Python :: 3.1',
662
'Programming Language :: Python :: 3.2',