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