# -------------------------------------------------------------------- from pathlib import Path import re import os import subprocess import sys import glob import copy import warnings from distutils import log from distutils import sysconfig from distutils.util import execute from distutils.util import split_quoted from distutils.errors import DistutilsError from distutils.text_file import TextFile try: from cStringIO import StringIO except ImportError: from io import StringIO try: import setuptools except ImportError: setuptools = None if setuptools: from setuptools import setup as _setup from setuptools import Extension as _Extension from setuptools import Command else: from distutils.core import setup as _setup from distutils.core import Extension as _Extension from distutils.core import Command def import_command(cmd): try: from importlib import import_module except ImportError: def import_module(n): return __import__(n, fromlist=[None]) try: if not setuptools: raise ImportError mod = import_module('setuptools.command.' + cmd) return getattr(mod, cmd) except ImportError: mod = import_module('distutils.command.' + cmd) return getattr(mod, cmd) _config = import_command('config') _build = import_command('build') _build_ext = import_command('build_ext') _install = import_command('install') try: from setuptools import modified except ImportError: try: from setuptools import dep_util as modified except ImportError: from distutils import dep_util as modified try: from packaging.version import Version except ImportError: try: from setuptools.extern.packaging.version import Version except ImportError: from distutils.version import StrictVersion as Version # -------------------------------------------------------------------- # Cython CYTHON = '3.0.0' def cython_req(): return CYTHON def cython_chk(VERSION, verbose=True): # def warn(message): if not verbose: return ruler, ws, nl = '*' * 80, ' ', '\n' pyexe = sys.executable advise = '$ %s -m pip install --upgrade cython' % pyexe def printer(*s): sys.stderr.write(' '.join(s) + '\n') printer(ruler, nl) printer(ws, message, nl) printer(ws, ws, advise, nl) printer(ruler) # try: import Cython except ImportError: warn('You need Cython to generate C source files.') return False # CYTHON_VERSION = Cython.__version__ m = re.match(r'(\d+\.\d+(?:\.\d+)?).*', CYTHON_VERSION) if not m: warn(f'Cannot parse Cython version string {CYTHON_VERSION!r}') return False REQUIRED = Version(VERSION) PROVIDED = Version(m.groups()[0]) if PROVIDED < REQUIRED: warn(f'You need Cython >= {VERSION} (you have version {CYTHON_VERSION})') return False # if verbose: log.info('using Cython %s' % CYTHON_VERSION) return True def cython_run( source, target=None, depends=(), includes=(), workdir=None, force=False, VERSION='0.0', ): if target is None: target = os.path.splitext(source)[0] + '.c' cwd = os.getcwd() try: if workdir: os.chdir(workdir) alldeps = [source] for dep in depends: alldeps += glob.glob(dep) if not (force or modified.newer_group(alldeps, target)): log.debug("skipping '%s' -> '%s' (up-to-date)", source, target) return finally: os.chdir(cwd) require = 'Cython >= %s' % VERSION if setuptools and not cython_chk(VERSION, verbose=False): if sys.modules.get('Cython'): removed = getattr(sys.modules['Cython'], '__version__', '') log.info('removing Cython %s from sys.modules' % removed) pkgname = re.compile(r'cython(\.|$)', re.IGNORECASE) for modname in list(sys.modules.keys()): if pkgname.match(modname): del sys.modules[modname] try: install_setup_requires = setuptools._install_setup_requires with warnings.catch_warnings(): if hasattr(setuptools, 'SetuptoolsDeprecationWarning'): category = setuptools.SetuptoolsDeprecationWarning warnings.simplefilter('ignore', category) log.info("fetching build requirement '%s'" % require) install_setup_requires({'setup_requires': [require]}) except Exception: log.info("failed to fetch build requirement '%s'" % require) if not cython_chk(VERSION): raise DistutilsError("unsatisfied build requirement '%s'" % require) # log.info("cythonizing '%s' -> '%s'", source, target) from cythonize import cythonize args = [] if workdir: args += ['--working', workdir] args += [source] if target: args += ['--output-file', target] err = cythonize(args) if err: raise DistutilsError(f"Cython failure: '{source}' -> '{target}'") # -------------------------------------------------------------------- def fix_config_vars(names, values): values = list(values) if 'CONDA_BUILD' in os.environ: return values if sys.platform == 'darwin': if 'ARCHFLAGS' in os.environ: ARCHFLAGS = os.environ['ARCHFLAGS'] for i, flag in enumerate(list(values)): flag, count = re.subn(r'-arch\s+\w+', ' ', str(flag)) if count and ARCHFLAGS: flag = flag + ' ' + ARCHFLAGS values[i] = flag if 'SDKROOT' in os.environ: SDKROOT = os.environ['SDKROOT'] for i, flag in enumerate(list(values)): flag, count = re.subn(r'-isysroot [^ \t]*', ' ', str(flag)) if count and SDKROOT: flag = flag + ' ' + '-isysroot ' + SDKROOT values[i] = flag return values def get_config_vars(*names): # Core Python configuration values = sysconfig.get_config_vars(*names) # Do any distutils flags fixup right now return fix_config_vars(names, values) # -------------------------------------------------------------------- class PetscConfig: def __init__(self, petsc_dir, petsc_arch, dest_dir=None): if dest_dir is None: dest_dir = os.environ.get('DESTDIR') self.configdict = {} if not petsc_dir: raise DistutilsError('PETSc not found') if not os.path.isdir(petsc_dir): raise DistutilsError('invalid PETSC_DIR: %s' % petsc_dir) self.version = self._get_petsc_version(petsc_dir) self.configdict = self._get_petsc_config(petsc_dir, petsc_arch) self.PETSC_DIR = self['PETSC_DIR'] self.PETSC_ARCH = self['PETSC_ARCH'] self.DESTDIR = dest_dir language_map = {'CONLY': 'c', 'CXXONLY': 'c++'} self.language = language_map[self['PETSC_LANGUAGE']] def __getitem__(self, item): return self.configdict[item] def get(self, item, default=None): return self.configdict.get(item, default) def configure(self, extension, compiler=None): self.configure_extension(extension) if compiler is not None: self.configure_compiler(compiler) def _get_petsc_version(self, petsc_dir): import re version_re = { 'major': re.compile(r'#define\s+PETSC_VERSION_MAJOR\s+(\d+)'), 'minor': re.compile(r'#define\s+PETSC_VERSION_MINOR\s+(\d+)'), 'micro': re.compile(r'#define\s+PETSC_VERSION_SUBMINOR\s+(\d+)'), 'release': re.compile(r'#define\s+PETSC_VERSION_RELEASE\s+(-*\d+)'), } petscversion_h = os.path.join(petsc_dir, 'include', 'petscversion.h') with open(petscversion_h, 'rt') as f: data = f.read() major = int(version_re['major'].search(data).groups()[0]) minor = int(version_re['minor'].search(data).groups()[0]) micro = int(version_re['micro'].search(data).groups()[0]) release = int(version_re['release'].search(data).groups()[0]) return (major, minor, micro), (release == 1) def _get_petsc_config(self, petsc_dir, petsc_arch): from os.path import join, isdir, exists PETSC_DIR = petsc_dir PETSC_ARCH = petsc_arch # confdir = join('lib', 'petsc', 'conf') if not (PETSC_ARCH and isdir(join(PETSC_DIR, PETSC_ARCH))): petscvars = join(PETSC_DIR, confdir, 'petscvariables') PETSC_ARCH = makefile(open(petscvars, 'rt')).get('PETSC_ARCH') if not (PETSC_ARCH and isdir(join(PETSC_DIR, PETSC_ARCH))): PETSC_ARCH = '' # variables = join(PETSC_DIR, confdir, 'variables') if not exists(variables): variables = join(PETSC_DIR, PETSC_ARCH, confdir, 'variables') petscvariables = join(PETSC_DIR, PETSC_ARCH, confdir, 'petscvariables') # with open(variables) as f: contents = f.read() with open(petscvariables) as f: contents += f.read() # confstr = 'PETSC_DIR = %s\n' % PETSC_DIR confstr += 'PETSC_ARCH = %s\n' % PETSC_ARCH confstr += contents return makefile(StringIO(confstr)) def _configure_ext(self, ext, dct, append=False): extdict = ext.__dict__ for key, values in dct.items(): if key in extdict: for value in values: if value not in extdict[key]: if not append: extdict[key].insert(0, value) else: extdict[key].append(value) def configure_extension(self, extension): # includes and libraries # paths in PETSc config files point to final installation location, but # we might be building against PETSc in staging location (DESTDIR) when # DESTDIR is set, so append DESTDIR (if nonempty) to those paths petsc_inc = flaglist(prepend_to_flags(self.DESTDIR, self['PETSC_CC_INCLUDES'])) lib_flags = prepend_to_flags( self.DESTDIR, '-L{} {}'.format(self['PETSC_LIB_DIR'], self['PETSC_LIB_BASIC']), ) petsc_lib = flaglist(lib_flags) # runtime_library_dirs is not supported on Windows if sys.platform != 'win32': # if DESTDIR is set, then we're building against PETSc in a staging # directory, but rpath needs to point to final install directory. rpath = [strip_prefix(self.DESTDIR, self['PETSC_LIB_DIR'])] if sys.modules.get('petsc') is not None: if sys.platform == 'darwin': rpath = ['@loader_path/../../petsc/lib'] else: rpath = ['$ORIGIN/../../petsc/lib'] petsc_lib['runtime_library_dirs'].extend(rpath) # Link in extra libraries on static builds if self['BUILDSHAREDLIB'] != 'yes': petsc_ext_lib = split_quoted(self['PETSC_EXTERNAL_LIB_BASIC']) petsc_lib['extra_link_args'].extend(petsc_ext_lib) self._configure_ext(extension, petsc_inc, append=True) self._configure_ext(extension, petsc_lib) def configure_compiler(self, compiler): if compiler.compiler_type != 'unix': return getenv = os.environ.get # distutils C/C++ compiler (cc, cflags, ccshared, cxx) = get_config_vars('CC', 'CFLAGS', 'CCSHARED', 'CXX') ccshared = getenv('CCSHARED', ccshared or '') cflags = getenv('CFLAGS', cflags or '') cflags = cflags.replace('-Wstrict-prototypes', '') # distutils linker (ldflags, ldshared, so_ext) = get_config_vars('LDFLAGS', 'LDSHARED', 'SO') ld = cc ldshared = getenv('LDSHARED', ldshared) ldflags = getenv('LDFLAGS', cflags + ' ' + (ldflags or '')) ldcmd = split_quoted(ld) + split_quoted(ldflags) ldshared = [ flg for flg in split_quoted(ldshared) if flg not in ldcmd and (flg.find('/lib/spack/env') < 0) and (flg.find('/libexec/spack/') < 0) ] ldshared = str.join(' ', ldshared) # def get_flags(cmd): if not cmd: return '' cmd = split_quoted(cmd) if os.path.basename(cmd[0]) == 'xcrun': del cmd[0] while True: if cmd[0] == '-sdk': del cmd[0:2] continue if cmd[0] == '-log': del cmd[0] continue break return ' '.join(cmd[1:]) # PETSc C compiler PCC = self['PCC'] PCC_FLAGS = get_flags(cc) + ' ' + self['PCC_FLAGS'] PCC_FLAGS = PCC_FLAGS.replace('-fvisibility=hidden', '') PCC_FLAGS = PCC_FLAGS.replace('-Wpedantic', '-Wno-pedantic') PCC_FLAGS = PCC_FLAGS.replace('-Wextra-semi-stmt', '-Wno-extra-semi-stmt') PCC = getenv('PCC', PCC) + ' ' + getenv('PCCFLAGS', PCC_FLAGS) PCC_SHARED = str.join(' ', (PCC, ccshared, cflags)) # PETSc C++ compiler PCXX = PCC if self.language == 'c++' else self.get('CXX', cxx) # PETSc linker PLD = self['PCC_LINKER'] PLD_FLAGS = get_flags(ld) + ' ' + self['PCC_LINKER_FLAGS'] PLD_FLAGS = PLD_FLAGS.replace('-fvisibility=hidden', '') PLD = getenv('PLD', PLD) + ' ' + getenv('PLDFLAGS', PLD_FLAGS) PLD_SHARED = str.join(' ', (PLD, ldshared, ldflags)) # compiler.set_executables( compiler=PCC, compiler_cxx=PCXX, linker_exe=PLD, compiler_so=PCC_SHARED, linker_so=PLD_SHARED, ) compiler.shared_lib_extension = so_ext def log_info(self): PETSC_DIR = self['PETSC_DIR'] PETSC_ARCH = self['PETSC_ARCH'] version = '.'.join([str(i) for i in self.version[0]]) release = ('development', 'release')[self.version[1]] version_info = version + ' ' + release integer_size = '%s-bit' % self['PETSC_INDEX_SIZE'] scalar_type = self['PETSC_SCALAR'] precision = self['PETSC_PRECISION'] language = self['PETSC_LANGUAGE'] compiler = self['PCC'] linker = self['PCC_LINKER'] log.info('PETSC_DIR: %s' % PETSC_DIR) log.info('PETSC_ARCH: %s' % PETSC_ARCH) log.info('version: %s' % version_info) log.info('integer-size: %s' % integer_size) log.info('scalar-type: %s' % scalar_type) log.info('precision: %s' % precision) log.info('language: %s' % language) log.info('compiler: %s' % compiler) log.info('linker: %s' % linker) # -------------------------------------------------------------------- class Extension(_Extension): pass # -------------------------------------------------------------------- cmd_petsc_opts = [ ('petsc-dir=', None, 'define PETSC_DIR, overriding environmental variables'), ('petsc-arch=', None, 'define PETSC_ARCH, overriding environmental variables'), ] class config(_config): Configure = PetscConfig user_options = _config.user_options + cmd_petsc_opts def initialize_options(self): _config.initialize_options(self) self.petsc_dir = None self.petsc_arch = None def get_config_arch(self, arch): return config.Configure(self.petsc_dir, arch) def run(self): _config.run(self) self.petsc_dir = config.get_petsc_dir(self.petsc_dir) if self.petsc_dir is None: return petsc_arch = config.get_petsc_arch(self.petsc_dir, self.petsc_arch) log.info('-' * 70) log.info('PETSC_DIR: %s' % self.petsc_dir) arch_list = petsc_arch if not arch_list: arch_list = [None] for arch in arch_list: conf = self.get_config_arch(arch) archname = conf.PETSC_ARCH or conf['PETSC_ARCH'] scalar_type = conf['PETSC_SCALAR'] precision = conf['PETSC_PRECISION'] language = conf['PETSC_LANGUAGE'] compiler = conf['PCC'] linker = conf['PCC_LINKER'] log.info('-' * 70) log.info('PETSC_ARCH: %s' % archname) log.info(' * scalar-type: %s' % scalar_type) log.info(' * precision: %s' % precision) log.info(' * language: %s' % language) log.info(' * compiler: %s' % compiler) log.info(' * linker: %s' % linker) log.info('-' * 70) # @staticmethod def get_petsc_dir(petsc_dir): if not petsc_dir: return None petsc_dir = os.path.expandvars(petsc_dir) if not petsc_dir or '$PETSC_DIR' in petsc_dir: try: import petsc petsc_dir = petsc.get_petsc_dir() except ImportError: log.warn('PETSC_DIR not specified') return None petsc_dir = os.path.expanduser(petsc_dir) petsc_dir = os.path.abspath(petsc_dir) return config.chk_petsc_dir(petsc_dir) get_petsc_dir = staticmethod(get_petsc_dir) # @staticmethod def chk_petsc_dir(petsc_dir): if not os.path.isdir(petsc_dir): log.error('invalid PETSC_DIR: %s (ignored)' % petsc_dir) return None return petsc_dir chk_petsc_dir = staticmethod(chk_petsc_dir) # @staticmethod def get_petsc_arch(petsc_dir, petsc_arch): if not petsc_dir: return None petsc_arch = os.path.expandvars(petsc_arch) if not petsc_arch or '$PETSC_ARCH' in petsc_arch: petsc_arch = '' petsc_conf = os.path.join(petsc_dir, 'lib', 'petsc', 'conf') if os.path.isdir(petsc_conf): petscvariables = os.path.join(petsc_conf, 'petscvariables') if os.path.exists(petscvariables): conf = makefile(open(petscvariables, 'rt')) petsc_arch = conf.get('PETSC_ARCH', '') petsc_arch = petsc_arch.split(os.pathsep) petsc_arch = unique(petsc_arch) petsc_arch = [arch for arch in petsc_arch if arch] return config.chk_petsc_arch(petsc_dir, petsc_arch) get_petsc_arch = staticmethod(get_petsc_arch) # @staticmethod def chk_petsc_arch(petsc_dir, petsc_arch): valid_archs = [] for arch in petsc_arch: arch_path = os.path.join(petsc_dir, arch) if os.path.isdir(arch_path): valid_archs.append(arch) else: log.warn('invalid PETSC_ARCH: %s (ignored)' % arch) return valid_archs chk_petsc_arch = staticmethod(chk_petsc_arch) class build(_build): user_options = _build.user_options user_options += [ ( 'inplace', 'i', 'ignore build-lib and put compiled extensions into the source ' 'directory alongside your pure Python modules', ) ] user_options += cmd_petsc_opts boolean_options = _build.boolean_options boolean_options += ['inplace'] def initialize_options(self): _build.initialize_options(self) self.inplace = None self.petsc_dir = None self.petsc_arch = None def finalize_options(self): _build.finalize_options(self) if self.inplace is None: self.inplace = False self.set_undefined_options( 'config', ('petsc_dir', 'petsc_dir'), ('petsc_arch', 'petsc_arch') ) self.petsc_dir = config.get_petsc_dir(self.petsc_dir) self.petsc_arch = config.get_petsc_arch(self.petsc_dir, self.petsc_arch) sub_commands = [('build_src', lambda *args: True)] + _build.sub_commands class build_src(Command): description = 'build C sources from Cython files' user_options = [ ('force', 'f', 'forcibly build everything (ignore file timestamps)'), ] boolean_options = ['force'] def initialize_options(self): self.force = False def finalize_options(self): self.set_undefined_options( 'build', ('force', 'force'), ) def run(self): sources = getattr(self, 'sources', []) for source in sources: cython_run(force=self.force, VERSION=cython_req(), **source) class build_ext(_build_ext): user_options = _build_ext.user_options + cmd_petsc_opts def initialize_options(self): _build_ext.initialize_options(self) self.inplace = None self.petsc_dir = None self.petsc_arch = None self._outputs = [] def finalize_options(self): _build_ext.finalize_options(self) self.set_undefined_options('build', ('inplace', 'inplace')) self.set_undefined_options( 'build', ('petsc_dir', 'petsc_dir'), ('petsc_arch', 'petsc_arch') ) def _copy_ext(self, ext): extclass = ext.__class__ fullname = self.get_ext_fullname(ext.name) modpath = str.split(fullname, '.') pkgpath = os.path.join('', *modpath[0:-1]) name = modpath[-1] sources = list(ext.sources) newext = extclass(name, sources) newext.__dict__.update(copy.deepcopy(ext.__dict__)) newext.name = name return pkgpath, newext def _build_ext_arch(self, ext, pkgpath, arch): build_temp = self.build_temp build_lib = self.build_lib try: self.build_temp = os.path.join(build_temp, arch) self.build_lib = os.path.join(build_lib, pkgpath, arch) _build_ext.build_extension(self, ext) finally: self.build_temp = build_temp self.build_lib = build_lib def get_config_arch(self, arch): return config.Configure(self.petsc_dir, arch) def build_extension(self, ext): if not isinstance(ext, Extension): return _build_ext.build_extension(self, ext) petsc_arch = self.petsc_arch if not petsc_arch: petsc_arch = [None] for arch in petsc_arch: config = self.get_config_arch(arch) ARCH = arch or config['PETSC_ARCH'] if ARCH not in self.PETSC_ARCH_LIST: self.PETSC_ARCH_LIST.append(ARCH) self.DESTDIR = config.DESTDIR ext.language = config.language config.log_info() pkgpath, newext = self._copy_ext(ext) config.configure(newext, self.compiler) self._build_ext_arch(newext, pkgpath, ARCH) return None def run(self): self.build_sources() _build_ext.run(self) self.build_stubs() def build_sources(self): if 'build_src' in self.distribution.cmdclass: self.run_command('build_src') def build_stubs(self): pkgname = self.distribution.get_name() modname = self.extensions[0].name.split(".")[-1] srcdir = Path(__file__).parent.parent / 'src' / pkgname blddir = Path(self.build_lib) / pkgname alldeps = glob.glob(str(blddir / 'lib' / '*' / f'{modname}.*')) target = srcdir / f'{modname}.pyi' if not (self.force or modified.newer_group(alldeps, target)): log.debug(f"skipping '{modname}.*.so' -> '{target}' (up-to-date)") return env = os.environ.copy() python_path = env.get('PYTHONPATH', "") if python_path != "": python_path += ":" python_path += self.build_lib env['PYTHONPATH'] = python_path env.pop('PETSC_ARCH', None) stubgen = Path(__file__).parent / 'stubgen.py' rc = subprocess.call([sys.executable, stubgen], env=env) # noqa S603 if rc != 0: log.warn("Stubs could not be generated.") return self.copy_file( srcdir / f'{modname}.pyi', blddir / f'{modname}.pyi', level=self.verbose, ) def build_extensions(self, *args, **kargs): self.PETSC_ARCH_LIST = [] _build_ext.build_extensions(self, *args, **kargs) if not self.PETSC_ARCH_LIST: return self.build_configuration(self.PETSC_ARCH_LIST) def build_configuration(self, arch_list): # template, variables = self.get_config_data(arch_list) config_data = template % variables # build_lib = self.build_lib dist_name = self.distribution.get_name() config_file = os.path.join( build_lib, dist_name, 'lib', dist_name.replace('4py', '') + '.cfg' ) # def write_file(filename, data): with open(filename, 'w') as fh: fh.write(config_data) execute( write_file, (config_file, config_data), msg='writing %s' % config_file, verbose=self.verbose, ) def get_config_data(self, arch_list): DESTDIR = self.DESTDIR template = ( '\n'.join( [ 'PETSC_DIR = %(PETSC_DIR)s', 'PETSC_ARCH = %(PETSC_ARCH)s', ] ) + '\n' ) variables = { 'PETSC_DIR': strip_prefix(DESTDIR, self.petsc_dir), 'PETSC_ARCH': os.path.pathsep.join(arch_list), } return template, variables def copy_extensions_to_source(self): build_py = self.get_finalized_command('build_py') for ext in self.extensions: inp_file, reg_file = self._get_inplace_equivalent(build_py, ext) arch_list = [''] if isinstance(ext, Extension) and self.petsc_arch: arch_list = self.petsc_arch[:] file_pairs = [] inp_head, inp_tail = os.path.split(inp_file) reg_head, reg_tail = os.path.split(reg_file) for arch in arch_list: inp_file = os.path.join(inp_head, arch, inp_tail) reg_file = os.path.join(reg_head, arch, reg_tail) file_pairs.append((inp_file, reg_file)) for inp_file, reg_file in file_pairs: if os.path.exists(reg_file) or not ext.optional: dest_dir, _ = os.path.split(inp_file) self.mkpath(dest_dir) self.copy_file(reg_file, inp_file, level=self.verbose) def get_outputs(self): self.check_extensions_list(self.extensions) outputs = [] for ext in self.extensions: fullname = self.get_ext_fullname(ext.name) filename = self.get_ext_filename(fullname) if isinstance(ext, Extension) and self.petsc_arch: head, tail = os.path.split(filename) for arch in self.petsc_arch: outfile = os.path.join(self.build_lib, head, arch, tail) outputs.append(outfile) else: outfile = os.path.join(self.build_lib, filename) outputs.append(outfile) pkgname = self.distribution.get_name() modname = self.extensions[0].name.split(".")[-1] outputs.append(os.path.join(self.build_lib, pkgname, f"{modname}.pyi")) return list(set(outputs)) def get_source_files(self): orig = log.set_threshold(log.WARN) try: return super().get_source_files() finally: log.set_threshold(orig) class install(_install): def initialize_options(self): with warnings.catch_warnings(): if setuptools: if hasattr(setuptools, 'SetuptoolsDeprecationWarning'): category = setuptools.SetuptoolsDeprecationWarning warnings.simplefilter('ignore', category) _install.initialize_options(self) self.old_and_unmanageable = True cmdclass_list = [ config, build, build_src, build_ext, install, ] # -------------------------------------------------------------------- def setup(**attrs): cmdclass = attrs.setdefault('cmdclass', {}) for cmd in cmdclass_list: cmdclass.setdefault(cmd.__name__, cmd) build_src.sources = attrs.pop('cython_sources', None) use_setup_requires = False # handle Cython requirement ourselves if setuptools and build_src.sources and use_setup_requires: version = cython_req() if not cython_chk(version, verbose=False): reqs = attrs.setdefault('setup_requires', []) reqs += ['Cython>=' + version] return _setup(**attrs) # -------------------------------------------------------------------- if setuptools: try: from setuptools.command import egg_info as mod_egg_info _FileList = mod_egg_info.FileList class FileList(_FileList): def process_template_line(self, line): level = log.set_threshold(log.ERROR) try: _FileList.process_template_line(self, line) finally: log.set_threshold(level) mod_egg_info.FileList = FileList except (ImportError, AttributeError): pass # -------------------------------------------------------------------- def append(seq, item): if item not in seq: seq.append(item) def append_dict(conf, dct): for key, values in dct.items(): if key in conf: for value in values: if value not in conf[key]: conf[key].append(value) def unique(seq): res = [] for item in seq: if item not in res: res.append(item) return res def flaglist(flags): conf = { 'define_macros': [], 'undef_macros': [], 'include_dirs': [], 'libraries': [], 'library_dirs': [], 'runtime_library_dirs': [], 'extra_compile_args': [], 'extra_link_args': [], } if isinstance(flags, str): flags = flags.split() switch = '-Wl,' newflags = [] linkopts = [] for f in flags: if f.startswith(switch): if len(f) > 4: append(linkopts, f[4:]) else: append(newflags, f) if linkopts: newflags.append(switch + ','.join(linkopts)) flags = newflags append_next_word = None for word in flags: if append_next_word is not None: append(append_next_word, word) append_next_word = None continue switch, value = word[0:2], word[2:] if switch == '-I': append(conf['include_dirs'], value) elif switch == '-D': try: idx = value.index('=') macro = (value[:idx], value[idx + 1 :]) except ValueError: macro = (value, None) append(conf['define_macros'], macro) elif switch == '-U': append(conf['undef_macros'], value) elif switch == '-l': append(conf['libraries'], value) elif switch == '-L': append(conf['library_dirs'], value) elif switch == '-R': append(conf['runtime_library_dirs'], value) elif word.startswith('-Wl'): linkopts = word.split(',') append_dict(conf, flaglist(linkopts[1:])) elif word == '-rpath': append_next_word = conf['runtime_library_dirs'] elif word == '-Xlinker': append_next_word = conf['extra_link_args'] else: # log.warn("unrecognized flag '%s'" % word) pass return conf def prepend_to_flags(path, flags): """Prepend a path to compiler flags with absolute paths""" if not path: return flags def append_path(m): switch = m.group(1) open_quote = m.group(4) old_path = m.group(5) close_quote = m.group(6) if os.path.isabs(old_path): moded_path = os.path.normpath(path + os.path.sep + old_path) return switch + open_quote + moded_path + close_quote return m.group(0) return re.sub(r'((^|\s+)(-I|-L))(\s*["\']?)(\S+)(["\']?)', append_path, flags) def strip_prefix(prefix, string): if not prefix: return string return re.sub(r'^' + prefix, '', string) # -------------------------------------------------------------------- # Regexes needed for parsing Makefile-like syntaxes _variable_rx = re.compile(r'([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)') _findvar1_rx = re.compile(r'\$\(([A-Za-z][A-Za-z0-9_]*)\)') _findvar2_rx = re.compile(r'\${([A-Za-z][A-Za-z0-9_]*)}') def makefile(fileobj, dct=None): """Parse a Makefile-style file. A dictionary containing name/value pairs is returned. If an optional dictionary is passed in as the second argument, it is used instead of a new dictionary. """ fp = TextFile(file=fileobj, strip_comments=1, skip_blanks=1, join_lines=1) if dct is None: dct = {} done = {} notdone = {} while 1: line = fp.readline() if line is None: # eof break m = _variable_rx.match(line) if m: n, v = m.group(1, 2) v = str.strip(v) if '$' in v: notdone[n] = v else: try: v = int(v) except ValueError: pass done[n] = v try: del notdone[n] except KeyError: pass fp.close() # do variable interpolation here while notdone: for name in list(notdone.keys()): value = notdone[name] m = _findvar1_rx.search(value) or _findvar2_rx.search(value) if m: n = m.group(1) found = True if n in done: item = str(done[n]) elif n in notdone: # get it on a subsequent round found = False else: done[n] = item = '' if found: after = value[m.end() :] value = value[: m.start()] + item + after if '$' in after: notdone[name] = value else: try: value = int(value) except ValueError: done[name] = str.strip(value) else: done[name] = value del notdone[name] else: # bogus variable reference; # just drop it since we can't deal del notdone[name] # save the results in the global dictionary dct.update(done) return dct # --------------------------------------------------------------------