1# -------------------------------------------------------------------- 2 3import re 4import os 5import sys 6import glob 7import copy 8import warnings 9from distutils import log 10from distutils import sysconfig 11from distutils.util import execute 12from distutils.util import split_quoted 13from distutils.errors import DistutilsError 14from distutils.text_file import TextFile 15 16 17try: 18 from cStringIO import StringIO 19except ImportError: 20 from io import StringIO 21 22try: 23 import setuptools 24except ImportError: 25 setuptools = None 26 27if setuptools: 28 from setuptools import setup as _setup 29 from setuptools import Extension as _Extension 30 from setuptools import Command 31else: 32 from distutils.core import setup as _setup 33 from distutils.core import Extension as _Extension 34 from distutils.core import Command 35 36 37def import_command(cmd): 38 try: 39 from importlib import import_module 40 except ImportError: 41 42 def import_module(n): 43 return __import__(n, fromlist=[None]) 44 45 try: 46 if not setuptools: 47 raise ImportError 48 mod = import_module('setuptools.command.' + cmd) 49 return getattr(mod, cmd) 50 except ImportError: 51 mod = import_module('distutils.command.' + cmd) 52 return getattr(mod, cmd) 53 54 55_config = import_command('config') 56_build = import_command('build') 57_build_ext = import_command('build_ext') 58_install = import_command('install') 59 60try: 61 from setuptools import modified 62except ImportError: 63 try: 64 from setuptools import dep_util as modified 65 except ImportError: 66 from distutils import dep_util as modified 67 68try: 69 from packaging.version import Version 70except ImportError: 71 try: 72 from setuptools.extern.packaging.version import Version 73 except ImportError: 74 from distutils.version import StrictVersion as Version 75 76# -------------------------------------------------------------------- 77 78# Cython 79 80CYTHON = '3.0.0' 81 82 83def cython_req(): 84 return CYTHON 85 86 87def cython_chk(VERSION, verbose=True): 88 # 89 def warn(message): 90 if not verbose: 91 return 92 ruler, ws, nl = '*' * 80, ' ', '\n' 93 pyexe = sys.executable 94 advise = '$ %s -m pip install --upgrade cython' % pyexe 95 96 def printer(*s): 97 sys.stderr.write(' '.join(s) + '\n') 98 99 printer(ruler, nl) 100 printer(ws, message, nl) 101 printer(ws, ws, advise, nl) 102 printer(ruler) 103 104 # 105 try: 106 import Cython 107 except ImportError: 108 warn('You need Cython to generate C source files.') 109 return False 110 # 111 CYTHON_VERSION = Cython.__version__ 112 m = re.match(r'(\d+\.\d+(?:\.\d+)?).*', CYTHON_VERSION) 113 if not m: 114 warn(f'Cannot parse Cython version string {CYTHON_VERSION!r}') 115 return False 116 REQUIRED = Version(VERSION) 117 PROVIDED = Version(m.groups()[0]) 118 if PROVIDED < REQUIRED: 119 warn(f'You need Cython >= {VERSION} (you have version {CYTHON_VERSION})') 120 return False 121 # 122 if verbose: 123 log.info('using Cython %s' % CYTHON_VERSION) 124 return True 125 126 127def cython_run( 128 source, 129 target=None, 130 depends=(), 131 includes=(), 132 workdir=None, 133 force=False, 134 VERSION='0.0', 135): 136 if target is None: 137 target = os.path.splitext(source)[0] + '.c' 138 cwd = os.getcwd() 139 try: 140 if workdir: 141 os.chdir(workdir) 142 alldeps = [source] 143 for dep in depends: 144 alldeps += glob.glob(dep) 145 if not (force or modified.newer_group(alldeps, target)): 146 log.debug("skipping '%s' -> '%s' (up-to-date)", source, target) 147 return 148 finally: 149 os.chdir(cwd) 150 require = 'Cython >= %s' % VERSION 151 if setuptools and not cython_chk(VERSION, verbose=False): 152 if sys.modules.get('Cython'): 153 removed = getattr(sys.modules['Cython'], '__version__', '') 154 log.info('removing Cython %s from sys.modules' % removed) 155 pkgname = re.compile(r'cython(\.|$)', re.IGNORECASE) 156 for modname in list(sys.modules.keys()): 157 if pkgname.match(modname): 158 del sys.modules[modname] 159 try: 160 install_setup_requires = setuptools._install_setup_requires 161 with warnings.catch_warnings(): 162 if hasattr(setuptools, 'SetuptoolsDeprecationWarning'): 163 category = setuptools.SetuptoolsDeprecationWarning 164 warnings.simplefilter('ignore', category) 165 log.info("fetching build requirement '%s'" % require) 166 install_setup_requires({'setup_requires': [require]}) 167 except Exception: 168 log.info("failed to fetch build requirement '%s'" % require) 169 if not cython_chk(VERSION): 170 raise DistutilsError("unsatisfied build requirement '%s'" % require) 171 # 172 log.info("cythonizing '%s' -> '%s'", source, target) 173 from cythonize import cythonize 174 175 args = [] 176 if workdir: 177 args += ['--working', workdir] 178 args += [source] 179 if target: 180 args += ['--output-file', target] 181 err = cythonize(args) 182 if err: 183 raise DistutilsError(f"Cython failure: '{source}' -> '{target}'") 184 185 186# -------------------------------------------------------------------- 187 188 189def fix_config_vars(names, values): 190 values = list(values) 191 if 'CONDA_BUILD' in os.environ: 192 return values 193 if sys.platform == 'darwin': 194 if 'ARCHFLAGS' in os.environ: 195 ARCHFLAGS = os.environ['ARCHFLAGS'] 196 for i, flag in enumerate(list(values)): 197 flag, count = re.subn(r'-arch\s+\w+', ' ', str(flag)) 198 if count and ARCHFLAGS: 199 flag = flag + ' ' + ARCHFLAGS 200 values[i] = flag 201 if 'SDKROOT' in os.environ: 202 SDKROOT = os.environ['SDKROOT'] 203 for i, flag in enumerate(list(values)): 204 flag, count = re.subn(r'-isysroot [^ \t]*', ' ', str(flag)) 205 if count and SDKROOT: 206 flag = flag + ' ' + '-isysroot ' + SDKROOT 207 values[i] = flag 208 return values 209 210 211def get_config_vars(*names): 212 # Core Python configuration 213 values = sysconfig.get_config_vars(*names) 214 # Do any distutils flags fixup right now 215 return fix_config_vars(names, values) 216 217 218# -------------------------------------------------------------------- 219 220 221class PetscConfig: 222 def __init__(self, petsc_dir, petsc_arch, dest_dir=None): 223 if dest_dir is None: 224 dest_dir = os.environ.get('DESTDIR') 225 self.configdict = {} 226 if not petsc_dir: 227 raise DistutilsError('PETSc not found') 228 if not os.path.isdir(petsc_dir): 229 raise DistutilsError('invalid PETSC_DIR: %s' % petsc_dir) 230 self.version = self._get_petsc_version(petsc_dir) 231 self.configdict = self._get_petsc_config(petsc_dir, petsc_arch) 232 self.PETSC_DIR = self['PETSC_DIR'] 233 self.PETSC_ARCH = self['PETSC_ARCH'] 234 self.DESTDIR = dest_dir 235 language_map = {'CONLY': 'c', 'CXXONLY': 'c++'} 236 self.language = language_map[self['PETSC_LANGUAGE']] 237 238 def __getitem__(self, item): 239 return self.configdict[item] 240 241 def get(self, item, default=None): 242 return self.configdict.get(item, default) 243 244 def configure(self, extension, compiler=None): 245 self.configure_extension(extension) 246 if compiler is not None: 247 self.configure_compiler(compiler) 248 249 def _get_petsc_version(self, petsc_dir): 250 import re 251 252 version_re = { 253 'major': re.compile(r'#define\s+PETSC_VERSION_MAJOR\s+(\d+)'), 254 'minor': re.compile(r'#define\s+PETSC_VERSION_MINOR\s+(\d+)'), 255 'micro': re.compile(r'#define\s+PETSC_VERSION_SUBMINOR\s+(\d+)'), 256 'release': re.compile(r'#define\s+PETSC_VERSION_RELEASE\s+(-*\d+)'), 257 } 258 petscversion_h = os.path.join(petsc_dir, 'include', 'petscversion.h') 259 with open(petscversion_h, 'rt') as f: 260 data = f.read() 261 major = int(version_re['major'].search(data).groups()[0]) 262 minor = int(version_re['minor'].search(data).groups()[0]) 263 micro = int(version_re['micro'].search(data).groups()[0]) 264 release = int(version_re['release'].search(data).groups()[0]) 265 return (major, minor, micro), (release == 1) 266 267 def _get_petsc_config(self, petsc_dir, petsc_arch): 268 from os.path import join, isdir, exists 269 270 PETSC_DIR = petsc_dir 271 PETSC_ARCH = petsc_arch 272 # 273 confdir = join('lib', 'petsc', 'conf') 274 if not (PETSC_ARCH and isdir(join(PETSC_DIR, PETSC_ARCH))): 275 petscvars = join(PETSC_DIR, confdir, 'petscvariables') 276 PETSC_ARCH = makefile(open(petscvars, 'rt')).get('PETSC_ARCH') 277 if not (PETSC_ARCH and isdir(join(PETSC_DIR, PETSC_ARCH))): 278 PETSC_ARCH = '' 279 # 280 variables = join(PETSC_DIR, confdir, 'variables') 281 if not exists(variables): 282 variables = join(PETSC_DIR, PETSC_ARCH, confdir, 'variables') 283 petscvariables = join(PETSC_DIR, PETSC_ARCH, confdir, 'petscvariables') 284 # 285 with open(variables) as f: 286 contents = f.read() 287 with open(petscvariables) as f: 288 contents += f.read() 289 # 290 confstr = 'PETSC_DIR = %s\n' % PETSC_DIR 291 confstr += 'PETSC_ARCH = %s\n' % PETSC_ARCH 292 confstr += contents 293 return makefile(StringIO(confstr)) 294 295 def _configure_ext(self, ext, dct, append=False): 296 extdict = ext.__dict__ 297 for key, values in dct.items(): 298 if key in extdict: 299 for value in values: 300 if value not in extdict[key]: 301 if not append: 302 extdict[key].insert(0, value) 303 else: 304 extdict[key].append(value) 305 306 def configure_extension(self, extension): 307 # includes and libraries 308 # paths in PETSc config files point to final installation location, but 309 # we might be building against PETSc in staging location (DESTDIR) when 310 # DESTDIR is set, so append DESTDIR (if nonempty) to those paths 311 petsc_inc = flaglist(prepend_to_flags(self.DESTDIR, self['PETSC_CC_INCLUDES'])) 312 lib_flags = prepend_to_flags( 313 self.DESTDIR, 314 '-L{} {}'.format(self['PETSC_LIB_DIR'], self['PETSC_LIB_BASIC']), 315 ) 316 petsc_lib = flaglist(lib_flags) 317 # runtime_library_dirs is not supported on Windows 318 if sys.platform != 'win32': 319 # if DESTDIR is set, then we're building against PETSc in a staging 320 # directory, but rpath needs to point to final install directory. 321 rpath = [strip_prefix(self.DESTDIR, self['PETSC_LIB_DIR'])] 322 if sys.modules.get('petsc') is not None: 323 if sys.platform == 'darwin': 324 rpath = ['@loader_path/../../petsc/lib'] 325 else: 326 rpath = ['$ORIGIN/../../petsc/lib'] 327 petsc_lib['runtime_library_dirs'].extend(rpath) 328 # Link in extra libraries on static builds 329 if self['BUILDSHAREDLIB'] != 'yes': 330 petsc_ext_lib = split_quoted(self['PETSC_EXTERNAL_LIB_BASIC']) 331 petsc_lib['extra_link_args'].extend(petsc_ext_lib) 332 self._configure_ext(extension, petsc_inc, append=True) 333 self._configure_ext(extension, petsc_lib) 334 335 def configure_compiler(self, compiler): 336 if compiler.compiler_type != 'unix': 337 return 338 getenv = os.environ.get 339 # distutils C/C++ compiler 340 (cc, cflags, ccshared, cxx) = get_config_vars('CC', 'CFLAGS', 'CCSHARED', 'CXX') 341 ccshared = getenv('CCSHARED', ccshared or '') 342 cflags = getenv('CFLAGS', cflags or '') 343 cflags = cflags.replace('-Wstrict-prototypes', '') 344 # distutils linker 345 (ldflags, ldshared, so_ext) = get_config_vars('LDFLAGS', 'LDSHARED', 'SO') 346 ld = cc 347 ldshared = getenv('LDSHARED', ldshared) 348 ldflags = getenv('LDFLAGS', cflags + ' ' + (ldflags or '')) 349 ldcmd = split_quoted(ld) + split_quoted(ldflags) 350 ldshared = [ 351 flg 352 for flg in split_quoted(ldshared) 353 if flg not in ldcmd and (flg.find('/lib/spack/env') < 0) 354 ] 355 ldshared = str.join(' ', ldshared) 356 357 # 358 def get_flags(cmd): 359 if not cmd: 360 return '' 361 cmd = split_quoted(cmd) 362 if os.path.basename(cmd[0]) == 'xcrun': 363 del cmd[0] 364 while True: 365 if cmd[0] == '-sdk': 366 del cmd[0:2] 367 continue 368 if cmd[0] == '-log': 369 del cmd[0] 370 continue 371 break 372 return ' '.join(cmd[1:]) 373 374 # PETSc C compiler 375 PCC = self['PCC'] 376 PCC_FLAGS = get_flags(cc) + ' ' + self['PCC_FLAGS'] 377 PCC_FLAGS = PCC_FLAGS.replace('-fvisibility=hidden', '') 378 PCC_FLAGS = PCC_FLAGS.replace('-Wpedantic', '-Wno-pedantic') 379 PCC_FLAGS = PCC_FLAGS.replace('-Wextra-semi-stmt', '-Wno-extra-semi-stmt') 380 PCC = getenv('PCC', PCC) + ' ' + getenv('PCCFLAGS', PCC_FLAGS) 381 PCC_SHARED = str.join(' ', (PCC, ccshared, cflags)) 382 # PETSc C++ compiler 383 PCXX = PCC if self.language == 'c++' else self.get('CXX', cxx) 384 # PETSc linker 385 PLD = self['PCC_LINKER'] 386 PLD_FLAGS = get_flags(ld) + ' ' + self['PCC_LINKER_FLAGS'] 387 PLD_FLAGS = PLD_FLAGS.replace('-fvisibility=hidden', '') 388 PLD = getenv('PLD', PLD) + ' ' + getenv('PLDFLAGS', PLD_FLAGS) 389 PLD_SHARED = str.join(' ', (PLD, ldshared, ldflags)) 390 # 391 compiler.set_executables( 392 compiler=PCC, 393 compiler_cxx=PCXX, 394 linker_exe=PLD, 395 compiler_so=PCC_SHARED, 396 linker_so=PLD_SHARED, 397 ) 398 compiler.shared_lib_extension = so_ext 399 400 def log_info(self): 401 PETSC_DIR = self['PETSC_DIR'] 402 PETSC_ARCH = self['PETSC_ARCH'] 403 version = '.'.join([str(i) for i in self.version[0]]) 404 release = ('development', 'release')[self.version[1]] 405 version_info = version + ' ' + release 406 integer_size = '%s-bit' % self['PETSC_INDEX_SIZE'] 407 scalar_type = self['PETSC_SCALAR'] 408 precision = self['PETSC_PRECISION'] 409 language = self['PETSC_LANGUAGE'] 410 compiler = self['PCC'] 411 linker = self['PCC_LINKER'] 412 log.info('PETSC_DIR: %s' % PETSC_DIR) 413 log.info('PETSC_ARCH: %s' % PETSC_ARCH) 414 log.info('version: %s' % version_info) 415 log.info('integer-size: %s' % integer_size) 416 log.info('scalar-type: %s' % scalar_type) 417 log.info('precision: %s' % precision) 418 log.info('language: %s' % language) 419 log.info('compiler: %s' % compiler) 420 log.info('linker: %s' % linker) 421 422 423# -------------------------------------------------------------------- 424 425 426class Extension(_Extension): 427 pass 428 429 430# -------------------------------------------------------------------- 431 432cmd_petsc_opts = [ 433 ('petsc-dir=', None, 'define PETSC_DIR, overriding environmental variables'), 434 ('petsc-arch=', None, 'define PETSC_ARCH, overriding environmental variables'), 435] 436 437 438class config(_config): 439 Configure = PetscConfig 440 441 user_options = _config.user_options + cmd_petsc_opts 442 443 def initialize_options(self): 444 _config.initialize_options(self) 445 self.petsc_dir = None 446 self.petsc_arch = None 447 448 def get_config_arch(self, arch): 449 return config.Configure(self.petsc_dir, arch) 450 451 def run(self): 452 _config.run(self) 453 self.petsc_dir = config.get_petsc_dir(self.petsc_dir) 454 if self.petsc_dir is None: 455 return 456 petsc_arch = config.get_petsc_arch(self.petsc_dir, self.petsc_arch) 457 log.info('-' * 70) 458 log.info('PETSC_DIR: %s' % self.petsc_dir) 459 arch_list = petsc_arch 460 if not arch_list: 461 arch_list = [None] 462 for arch in arch_list: 463 conf = self.get_config_arch(arch) 464 archname = conf.PETSC_ARCH or conf['PETSC_ARCH'] 465 scalar_type = conf['PETSC_SCALAR'] 466 precision = conf['PETSC_PRECISION'] 467 language = conf['PETSC_LANGUAGE'] 468 compiler = conf['PCC'] 469 linker = conf['PCC_LINKER'] 470 log.info('-' * 70) 471 log.info('PETSC_ARCH: %s' % archname) 472 log.info(' * scalar-type: %s' % scalar_type) 473 log.info(' * precision: %s' % precision) 474 log.info(' * language: %s' % language) 475 log.info(' * compiler: %s' % compiler) 476 log.info(' * linker: %s' % linker) 477 log.info('-' * 70) 478 479 # @staticmethod 480 def get_petsc_dir(petsc_dir): 481 if not petsc_dir: 482 return None 483 petsc_dir = os.path.expandvars(petsc_dir) 484 if not petsc_dir or '$PETSC_DIR' in petsc_dir: 485 try: 486 import petsc 487 488 petsc_dir = petsc.get_petsc_dir() 489 except ImportError: 490 log.warn('PETSC_DIR not specified') 491 return None 492 petsc_dir = os.path.expanduser(petsc_dir) 493 petsc_dir = os.path.abspath(petsc_dir) 494 return config.chk_petsc_dir(petsc_dir) 495 496 get_petsc_dir = staticmethod(get_petsc_dir) 497 498 # @staticmethod 499 def chk_petsc_dir(petsc_dir): 500 if not os.path.isdir(petsc_dir): 501 log.error('invalid PETSC_DIR: %s (ignored)' % petsc_dir) 502 return None 503 return petsc_dir 504 505 chk_petsc_dir = staticmethod(chk_petsc_dir) 506 507 # @staticmethod 508 def get_petsc_arch(petsc_dir, petsc_arch): 509 if not petsc_dir: 510 return None 511 petsc_arch = os.path.expandvars(petsc_arch) 512 if not petsc_arch or '$PETSC_ARCH' in petsc_arch: 513 petsc_arch = '' 514 petsc_conf = os.path.join(petsc_dir, 'lib', 'petsc', 'conf') 515 if os.path.isdir(petsc_conf): 516 petscvariables = os.path.join(petsc_conf, 'petscvariables') 517 if os.path.exists(petscvariables): 518 conf = makefile(open(petscvariables, 'rt')) 519 petsc_arch = conf.get('PETSC_ARCH', '') 520 petsc_arch = petsc_arch.split(os.pathsep) 521 petsc_arch = unique(petsc_arch) 522 petsc_arch = [arch for arch in petsc_arch if arch] 523 return config.chk_petsc_arch(petsc_dir, petsc_arch) 524 525 get_petsc_arch = staticmethod(get_petsc_arch) 526 527 # @staticmethod 528 def chk_petsc_arch(petsc_dir, petsc_arch): 529 valid_archs = [] 530 for arch in petsc_arch: 531 arch_path = os.path.join(petsc_dir, arch) 532 if os.path.isdir(arch_path): 533 valid_archs.append(arch) 534 else: 535 log.warn('invalid PETSC_ARCH: %s (ignored)' % arch) 536 return valid_archs 537 538 chk_petsc_arch = staticmethod(chk_petsc_arch) 539 540 541class build(_build): 542 user_options = _build.user_options 543 user_options += [ 544 ( 545 'inplace', 546 'i', 547 'ignore build-lib and put compiled extensions into the source ' 548 'directory alongside your pure Python modules', 549 ) 550 ] 551 user_options += cmd_petsc_opts 552 553 boolean_options = _build.boolean_options 554 boolean_options += ['inplace'] 555 556 def initialize_options(self): 557 _build.initialize_options(self) 558 self.inplace = None 559 self.petsc_dir = None 560 self.petsc_arch = None 561 562 def finalize_options(self): 563 _build.finalize_options(self) 564 if self.inplace is None: 565 self.inplace = False 566 self.set_undefined_options( 567 'config', ('petsc_dir', 'petsc_dir'), ('petsc_arch', 'petsc_arch') 568 ) 569 self.petsc_dir = config.get_petsc_dir(self.petsc_dir) 570 self.petsc_arch = config.get_petsc_arch(self.petsc_dir, self.petsc_arch) 571 572 sub_commands = [('build_src', lambda *args: True)] + _build.sub_commands 573 574 575class build_src(Command): 576 description = 'build C sources from Cython files' 577 578 user_options = [ 579 ('force', 'f', 'forcibly build everything (ignore file timestamps)'), 580 ] 581 582 boolean_options = ['force'] 583 584 def initialize_options(self): 585 self.force = False 586 587 def finalize_options(self): 588 self.set_undefined_options( 589 'build', 590 ('force', 'force'), 591 ) 592 593 def run(self): 594 sources = getattr(self, 'sources', []) 595 for source in sources: 596 cython_run(force=self.force, VERSION=cython_req(), **source) 597 598 599class build_ext(_build_ext): 600 user_options = _build_ext.user_options + cmd_petsc_opts 601 602 def initialize_options(self): 603 _build_ext.initialize_options(self) 604 self.inplace = None 605 self.petsc_dir = None 606 self.petsc_arch = None 607 self._outputs = [] 608 609 def finalize_options(self): 610 _build_ext.finalize_options(self) 611 self.set_undefined_options('build', ('inplace', 'inplace')) 612 self.set_undefined_options( 613 'build', ('petsc_dir', 'petsc_dir'), ('petsc_arch', 'petsc_arch') 614 ) 615 616 def _copy_ext(self, ext): 617 extclass = ext.__class__ 618 fullname = self.get_ext_fullname(ext.name) 619 modpath = str.split(fullname, '.') 620 pkgpath = os.path.join('', *modpath[0:-1]) 621 name = modpath[-1] 622 sources = list(ext.sources) 623 newext = extclass(name, sources) 624 newext.__dict__.update(copy.deepcopy(ext.__dict__)) 625 newext.name = name 626 return pkgpath, newext 627 628 def _build_ext_arch(self, ext, pkgpath, arch): 629 build_temp = self.build_temp 630 build_lib = self.build_lib 631 try: 632 self.build_temp = os.path.join(build_temp, arch) 633 self.build_lib = os.path.join(build_lib, pkgpath, arch) 634 _build_ext.build_extension(self, ext) 635 finally: 636 self.build_temp = build_temp 637 self.build_lib = build_lib 638 639 def get_config_arch(self, arch): 640 return config.Configure(self.petsc_dir, arch) 641 642 def build_extension(self, ext): 643 if not isinstance(ext, Extension): 644 return _build_ext.build_extension(self, ext) 645 petsc_arch = self.petsc_arch 646 if not petsc_arch: 647 petsc_arch = [None] 648 for arch in petsc_arch: 649 config = self.get_config_arch(arch) 650 ARCH = arch or config['PETSC_ARCH'] 651 if ARCH not in self.PETSC_ARCH_LIST: 652 self.PETSC_ARCH_LIST.append(ARCH) 653 self.DESTDIR = config.DESTDIR 654 ext.language = config.language 655 config.log_info() 656 pkgpath, newext = self._copy_ext(ext) 657 config.configure(newext, self.compiler) 658 self._build_ext_arch(newext, pkgpath, ARCH) 659 return None 660 661 def run(self): 662 self.build_sources() 663 _build_ext.run(self) 664 665 def build_sources(self): 666 if 'build_src' in self.distribution.cmdclass: 667 self.run_command('build_src') 668 669 def build_extensions(self, *args, **kargs): 670 self.PETSC_ARCH_LIST = [] 671 _build_ext.build_extensions(self, *args, **kargs) 672 if not self.PETSC_ARCH_LIST: 673 return 674 self.build_configuration(self.PETSC_ARCH_LIST) 675 676 def build_configuration(self, arch_list): 677 # 678 template, variables = self.get_config_data(arch_list) 679 config_data = template % variables 680 # 681 build_lib = self.build_lib 682 dist_name = self.distribution.get_name() 683 config_file = os.path.join( 684 build_lib, dist_name, 'lib', dist_name.replace('4py', '') + '.cfg' 685 ) 686 687 # 688 def write_file(filename, data): 689 with open(filename, 'w') as fh: 690 fh.write(config_data) 691 692 execute( 693 write_file, 694 (config_file, config_data), 695 msg='writing %s' % config_file, 696 verbose=self.verbose, 697 dry_run=self.dry_run, 698 ) 699 700 def get_config_data(self, arch_list): 701 DESTDIR = self.DESTDIR 702 template = ( 703 '\n'.join( 704 [ 705 'PETSC_DIR = %(PETSC_DIR)s', 706 'PETSC_ARCH = %(PETSC_ARCH)s', 707 ] 708 ) 709 + '\n' 710 ) 711 variables = { 712 'PETSC_DIR': strip_prefix(DESTDIR, self.petsc_dir), 713 'PETSC_ARCH': os.path.pathsep.join(arch_list), 714 } 715 return template, variables 716 717 def copy_extensions_to_source(self): 718 build_py = self.get_finalized_command('build_py') 719 for ext in self.extensions: 720 inp_file, reg_file = self._get_inplace_equivalent(build_py, ext) 721 722 arch_list = [''] 723 if isinstance(ext, Extension) and self.petsc_arch: 724 arch_list = self.petsc_arch[:] 725 726 file_pairs = [] 727 inp_head, inp_tail = os.path.split(inp_file) 728 reg_head, reg_tail = os.path.split(reg_file) 729 for arch in arch_list: 730 inp_file = os.path.join(inp_head, arch, inp_tail) 731 reg_file = os.path.join(reg_head, arch, reg_tail) 732 file_pairs.append((inp_file, reg_file)) 733 734 for inp_file, reg_file in file_pairs: 735 if os.path.exists(reg_file) or not ext.optional: 736 dest_dir, _ = os.path.split(inp_file) 737 self.mkpath(dest_dir) 738 self.copy_file(reg_file, inp_file, level=self.verbose) 739 740 def get_outputs(self): 741 self.check_extensions_list(self.extensions) 742 outputs = [] 743 for ext in self.extensions: 744 fullname = self.get_ext_fullname(ext.name) 745 filename = self.get_ext_filename(fullname) 746 if isinstance(ext, Extension) and self.petsc_arch: 747 head, tail = os.path.split(filename) 748 for arch in self.petsc_arch: 749 outfile = os.path.join(self.build_lib, head, arch, tail) 750 outputs.append(outfile) 751 else: 752 outfile = os.path.join(self.build_lib, filename) 753 outputs.append(outfile) 754 return list(set(outputs)) 755 756 757class install(_install): 758 def initialize_options(self): 759 with warnings.catch_warnings(): 760 if setuptools: 761 if hasattr(setuptools, 'SetuptoolsDeprecationWarning'): 762 category = setuptools.SetuptoolsDeprecationWarning 763 warnings.simplefilter('ignore', category) 764 _install.initialize_options(self) 765 self.old_and_unmanageable = True 766 767 768cmdclass_list = [ 769 config, 770 build, 771 build_src, 772 build_ext, 773 install, 774] 775 776# -------------------------------------------------------------------- 777 778 779def setup(**attrs): 780 cmdclass = attrs.setdefault('cmdclass', {}) 781 for cmd in cmdclass_list: 782 cmdclass.setdefault(cmd.__name__, cmd) 783 build_src.sources = attrs.pop('cython_sources', None) 784 use_setup_requires = False # handle Cython requirement ourselves 785 if setuptools and build_src.sources and use_setup_requires: 786 version = cython_req() 787 if not cython_chk(version, verbose=False): 788 reqs = attrs.setdefault('setup_requires', []) 789 reqs += ['Cython>=' + version] 790 return _setup(**attrs) 791 792 793# -------------------------------------------------------------------- 794 795if setuptools: 796 try: 797 from setuptools.command import egg_info as mod_egg_info 798 799 _FileList = mod_egg_info.FileList 800 801 class FileList(_FileList): 802 def process_template_line(self, line): 803 level = log.set_threshold(log.ERROR) 804 try: 805 _FileList.process_template_line(self, line) 806 finally: 807 log.set_threshold(level) 808 809 mod_egg_info.FileList = FileList 810 except (ImportError, AttributeError): 811 pass 812 813# -------------------------------------------------------------------- 814 815 816def append(seq, item): 817 if item not in seq: 818 seq.append(item) 819 820 821def append_dict(conf, dct): 822 for key, values in dct.items(): 823 if key in conf: 824 for value in values: 825 if value not in conf[key]: 826 conf[key].append(value) 827 828 829def unique(seq): 830 res = [] 831 for item in seq: 832 if item not in res: 833 res.append(item) 834 return res 835 836 837def flaglist(flags): 838 conf = { 839 'define_macros': [], 840 'undef_macros': [], 841 'include_dirs': [], 842 'libraries': [], 843 'library_dirs': [], 844 'runtime_library_dirs': [], 845 'extra_compile_args': [], 846 'extra_link_args': [], 847 } 848 849 if isinstance(flags, str): 850 flags = flags.split() 851 852 switch = '-Wl,' 853 newflags = [] 854 linkopts = [] 855 for f in flags: 856 if f.startswith(switch): 857 if len(f) > 4: 858 append(linkopts, f[4:]) 859 else: 860 append(newflags, f) 861 if linkopts: 862 newflags.append(switch + ','.join(linkopts)) 863 flags = newflags 864 865 append_next_word = None 866 867 for word in flags: 868 if append_next_word is not None: 869 append(append_next_word, word) 870 append_next_word = None 871 continue 872 873 switch, value = word[0:2], word[2:] 874 875 if switch == '-I': 876 append(conf['include_dirs'], value) 877 elif switch == '-D': 878 try: 879 idx = value.index('=') 880 macro = (value[:idx], value[idx + 1 :]) 881 except ValueError: 882 macro = (value, None) 883 append(conf['define_macros'], macro) 884 elif switch == '-U': 885 append(conf['undef_macros'], value) 886 elif switch == '-l': 887 append(conf['libraries'], value) 888 elif switch == '-L': 889 append(conf['library_dirs'], value) 890 elif switch == '-R': 891 append(conf['runtime_library_dirs'], value) 892 elif word.startswith('-Wl'): 893 linkopts = word.split(',') 894 append_dict(conf, flaglist(linkopts[1:])) 895 elif word == '-rpath': 896 append_next_word = conf['runtime_library_dirs'] 897 elif word == '-Xlinker': 898 append_next_word = conf['extra_link_args'] 899 else: 900 # log.warn("unrecognized flag '%s'" % word) 901 pass 902 return conf 903 904 905def prepend_to_flags(path, flags): 906 """Prepend a path to compiler flags with absolute paths""" 907 if not path: 908 return flags 909 910 def append_path(m): 911 switch = m.group(1) 912 open_quote = m.group(4) 913 old_path = m.group(5) 914 close_quote = m.group(6) 915 if os.path.isabs(old_path): 916 moded_path = os.path.normpath(path + os.path.sep + old_path) 917 return switch + open_quote + moded_path + close_quote 918 return m.group(0) 919 920 return re.sub(r'((^|\s+)(-I|-L))(\s*["\']?)(\S+)(["\']?)', append_path, flags) 921 922 923def strip_prefix(prefix, string): 924 if not prefix: 925 return string 926 return re.sub(r'^' + prefix, '', string) 927 928 929# -------------------------------------------------------------------- 930 931# Regexes needed for parsing Makefile-like syntaxes 932_variable_rx = re.compile(r'([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)') 933_findvar1_rx = re.compile(r'\$\(([A-Za-z][A-Za-z0-9_]*)\)') 934_findvar2_rx = re.compile(r'\${([A-Za-z][A-Za-z0-9_]*)}') 935 936 937def makefile(fileobj, dct=None): 938 """Parse a Makefile-style file. 939 940 A dictionary containing name/value pairs is returned. If an 941 optional dictionary is passed in as the second argument, it is 942 used instead of a new dictionary. 943 """ 944 fp = TextFile(file=fileobj, strip_comments=1, skip_blanks=1, join_lines=1) 945 946 if dct is None: 947 dct = {} 948 done = {} 949 notdone = {} 950 951 while 1: 952 line = fp.readline() 953 if line is None: # eof 954 break 955 m = _variable_rx.match(line) 956 if m: 957 n, v = m.group(1, 2) 958 v = str.strip(v) 959 if '$' in v: 960 notdone[n] = v 961 else: 962 try: 963 v = int(v) 964 except ValueError: 965 pass 966 done[n] = v 967 try: 968 del notdone[n] 969 except KeyError: 970 pass 971 fp.close() 972 973 # do variable interpolation here 974 while notdone: 975 for name in list(notdone.keys()): 976 value = notdone[name] 977 m = _findvar1_rx.search(value) or _findvar2_rx.search(value) 978 if m: 979 n = m.group(1) 980 found = True 981 if n in done: 982 item = str(done[n]) 983 elif n in notdone: 984 # get it on a subsequent round 985 found = False 986 else: 987 done[n] = item = '' 988 if found: 989 after = value[m.end() :] 990 value = value[: m.start()] + item + after 991 if '$' in after: 992 notdone[name] = value 993 else: 994 try: 995 value = int(value) 996 except ValueError: 997 done[name] = str.strip(value) 998 else: 999 done[name] = value 1000 del notdone[name] 1001 else: 1002 # bogus variable reference; 1003 # just drop it since we can't deal 1004 del notdone[name] 1005 # save the results in the global dictionary 1006 dct.update(done) 1007 return dct 1008 1009 1010# -------------------------------------------------------------------- 1011