# -*- coding: utf-8 -*- """ gevent build utilities. """ from __future__ import print_function, absolute_import, division import re import os import os.path import sys from subprocess import check_call from glob import glob from setuptools.command.build_ext import build_ext from setuptools.command.sdist import sdist ## Exported configurations PYPY = hasattr(sys, 'pypy_version_info') WIN = sys.platform.startswith('win') CFFI_WIN_BUILD_ANYWAY = os.environ.get("PYPY_WIN_BUILD_ANYWAY") RUNNING_ON_TRAVIS = os.environ.get('TRAVIS') RUNNING_ON_APPVEYOR = os.environ.get('APPVEYOR') RUNNING_ON_CI = RUNNING_ON_TRAVIS or RUNNING_ON_APPVEYOR LIBRARIES = [] DEFINE_MACROS = [] if WIN: LIBRARIES += ['ws2_32'] DEFINE_MACROS += [('FD_SETSIZE', '1024'), ('_WIN32', '1')] ### File handling THIS_DIR = os.path.dirname(__file__) def quoted_abspath(*segments): return '"' + os.path.abspath(os.path.join(*segments)) + '"' def read(name, *args): """Read a file path relative to this file.""" with open(os.path.join(THIS_DIR, name)) as f: return f.read(*args) def read_version(name="src/gevent/__init__.py"): contents = read(name) version = re.search(r"__version__\s*=\s*'(.*)'", contents, re.M).group(1) assert version, "could not read version" return version def dep_abspath(depname, *extra): return os.path.abspath(os.path.join('deps', depname, *extra)) def quoted_dep_abspath(depname): return quoted_abspath(dep_abspath(depname)) def glob_many(*globs): """ Return a list of all the glob patterns expanded. """ result = [] for pattern in globs: result.extend(glob(pattern)) return sorted(result) ## Configuration def _parse_environ(key): value = os.environ.get(key) if not value: return value = value.lower().strip() if value in ('1', 'true', 'on', 'yes'): return True elif value in ('0', 'false', 'off', 'no'): return False raise ValueError('Environment variable %r has invalid value %r. ' 'Please set it to 1, 0 or an empty string' % (key, value)) IGNORE_CFFI = _parse_environ("GEVENT_NO_CFFI_BUILD") def _get_config_value(key, defkey, path=None): """ Find a boolean value, configured in the environment at *key* or *defkey* (typically, *defkey* will be shared by several calls). If those don't exist, then check for the existence of *path* and return that (if path is given) """ value = _parse_environ(key) if value is None: value = _parse_environ(defkey) if value is not None: return value return os.path.exists(path) if path is not None else False def should_embed(dep_name): """ Check the configuration for the dep_name and see if it should be embedded. Environment keys are derived from the dep name: libev becomes LIBEV_EMBED and c-ares becomes CARES_EMBED. """ path = dep_abspath(dep_name) defkey = 'EMBED' key = dep_name.replace('-', '').upper() + '_' + defkey return _get_config_value(key, defkey, path) ## Headers def make_universal_header(filename, *defines): defines = [('#define %s ' % define, define) for define in defines] with open(filename, 'r') as f: lines = f.read().split('\n') ifdef = 0 with open(filename, 'w') as f: for line in lines: if line.startswith('#ifdef'): ifdef += 1 elif line.startswith('#endif'): ifdef -= 1 elif not ifdef: for prefix, define in defines: if line.startswith(prefix): line = '#ifdef __LP64__\n#define %s 8\n#else\n#define %s 4\n#endif' % (define, define) break print(line, file=f) # Processes def _system(cmd, cwd=None, env=None, **kwargs): sys.stdout.write('Running %r in %s\n' % (cmd, cwd or os.getcwd())) if 'shell' not in kwargs: kwargs['shell'] = True return check_call(cmd, cwd=cwd, env=env, **kwargs) def system(cmd, cwd=None, env=None, **kwargs): if _system(cmd, cwd=cwd, env=env, **kwargs): sys.exit(1) ## Distutils extensions class BuildFailed(Exception): pass from distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError # pylint:disable=no-name-in-module,import-error ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) class ConfiguringBuildExt(build_ext): def gevent_prepare(self, ext): configure = getattr(ext, 'configure', None) if configure: configure(self, ext) def build_extension(self, ext): self.gevent_prepare(ext) try: result = build_ext.build_extension(self, ext) except ext_errors: if getattr(ext, 'optional', False): raise BuildFailed() else: raise return result class MakeSdist(sdist): """ An sdist that runs make if needed, and makes sure that the Makefile doesn't make it into the dist archive. """ _ran_make = False @classmethod def make(cls, targets=''): # NOTE: We have two copies of the makefile, one # for posix, one for windows. Our sdist command takes # care of renaming the posix one so it doesn't get into # the .tar.gz file (we don't want to re-run make in a released # file). We trigger off the presence/absence of that file altogether # to skip both posix and unix branches. # See https://github.com/gevent/gevent/issues/757 if cls._ran_make: return if os.path.exists('Makefile'): if WIN: # make.cmd handles checking for PyPy and only making the # right things, so we can ignore the targets system("appveyor\\make.cmd") else: if "PYTHON" not in os.environ: os.environ["PYTHON"] = sys.executable # Let the user specify the make program, helpful for BSD # where GNU make might be called gmake make_program = os.environ.get('MAKE', 'make') system(make_program + ' ' + targets) cls._ran_make = True def run(self): renamed = False if os.path.exists('Makefile'): self.make() os.rename('Makefile', 'Makefile.ext') renamed = True try: return sdist.run(self) finally: if renamed: os.rename('Makefile.ext', 'Makefile') from setuptools import Extension as _Extension class Extension(_Extension): # This class exists currently mostly to make pylint # happy in terms of attributes we use. def __init__(self, *args, **kwargs): self.libraries = [] self.define_macros = [] # Python 2 has this as an old-style class for some reason # so super() doesn't work. _Extension.__init__(self, *args, **kwargs) # pylint:disable=no-member,non-parent-init-called