xref: /petsc/src/binding/petsc4py/conf/confpetsc.py (revision 2ff79c18c26c94ed8cb599682f680f231dca6444)
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) and (flg.find('/libexec/spack/') < 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    def get_source_files(self):
757        orig = log.set_threshold(log.WARN)
758        try:
759            return super().get_source_files()
760        finally:
761            log.set_threshold(orig)
762
763
764class install(_install):
765    def initialize_options(self):
766        with warnings.catch_warnings():
767            if setuptools:
768                if hasattr(setuptools, 'SetuptoolsDeprecationWarning'):
769                    category = setuptools.SetuptoolsDeprecationWarning
770                    warnings.simplefilter('ignore', category)
771            _install.initialize_options(self)
772        self.old_and_unmanageable = True
773
774
775cmdclass_list = [
776    config,
777    build,
778    build_src,
779    build_ext,
780    install,
781]
782
783# --------------------------------------------------------------------
784
785
786def setup(**attrs):
787    cmdclass = attrs.setdefault('cmdclass', {})
788    for cmd in cmdclass_list:
789        cmdclass.setdefault(cmd.__name__, cmd)
790    build_src.sources = attrs.pop('cython_sources', None)
791    use_setup_requires = False  # handle Cython requirement ourselves
792    if setuptools and build_src.sources and use_setup_requires:
793        version = cython_req()
794        if not cython_chk(version, verbose=False):
795            reqs = attrs.setdefault('setup_requires', [])
796            reqs += ['Cython>=' + version]
797    return _setup(**attrs)
798
799
800# --------------------------------------------------------------------
801
802if setuptools:
803    try:
804        from setuptools.command import egg_info as mod_egg_info
805
806        _FileList = mod_egg_info.FileList
807
808        class FileList(_FileList):
809            def process_template_line(self, line):
810                level = log.set_threshold(log.ERROR)
811                try:
812                    _FileList.process_template_line(self, line)
813                finally:
814                    log.set_threshold(level)
815
816        mod_egg_info.FileList = FileList
817    except (ImportError, AttributeError):
818        pass
819
820# --------------------------------------------------------------------
821
822
823def append(seq, item):
824    if item not in seq:
825        seq.append(item)
826
827
828def append_dict(conf, dct):
829    for key, values in dct.items():
830        if key in conf:
831            for value in values:
832                if value not in conf[key]:
833                    conf[key].append(value)
834
835
836def unique(seq):
837    res = []
838    for item in seq:
839        if item not in res:
840            res.append(item)
841    return res
842
843
844def flaglist(flags):
845    conf = {
846        'define_macros': [],
847        'undef_macros': [],
848        'include_dirs': [],
849        'libraries': [],
850        'library_dirs': [],
851        'runtime_library_dirs': [],
852        'extra_compile_args': [],
853        'extra_link_args': [],
854    }
855
856    if isinstance(flags, str):
857        flags = flags.split()
858
859    switch = '-Wl,'
860    newflags = []
861    linkopts = []
862    for f in flags:
863        if f.startswith(switch):
864            if len(f) > 4:
865                append(linkopts, f[4:])
866        else:
867            append(newflags, f)
868    if linkopts:
869        newflags.append(switch + ','.join(linkopts))
870    flags = newflags
871
872    append_next_word = None
873
874    for word in flags:
875        if append_next_word is not None:
876            append(append_next_word, word)
877            append_next_word = None
878            continue
879
880        switch, value = word[0:2], word[2:]
881
882        if switch == '-I':
883            append(conf['include_dirs'], value)
884        elif switch == '-D':
885            try:
886                idx = value.index('=')
887                macro = (value[:idx], value[idx + 1 :])
888            except ValueError:
889                macro = (value, None)
890            append(conf['define_macros'], macro)
891        elif switch == '-U':
892            append(conf['undef_macros'], value)
893        elif switch == '-l':
894            append(conf['libraries'], value)
895        elif switch == '-L':
896            append(conf['library_dirs'], value)
897        elif switch == '-R':
898            append(conf['runtime_library_dirs'], value)
899        elif word.startswith('-Wl'):
900            linkopts = word.split(',')
901            append_dict(conf, flaglist(linkopts[1:]))
902        elif word == '-rpath':
903            append_next_word = conf['runtime_library_dirs']
904        elif word == '-Xlinker':
905            append_next_word = conf['extra_link_args']
906        else:
907            # log.warn("unrecognized flag '%s'" % word)
908            pass
909    return conf
910
911
912def prepend_to_flags(path, flags):
913    """Prepend a path to compiler flags with absolute paths"""
914    if not path:
915        return flags
916
917    def append_path(m):
918        switch = m.group(1)
919        open_quote = m.group(4)
920        old_path = m.group(5)
921        close_quote = m.group(6)
922        if os.path.isabs(old_path):
923            moded_path = os.path.normpath(path + os.path.sep + old_path)
924            return switch + open_quote + moded_path + close_quote
925        return m.group(0)
926
927    return re.sub(r'((^|\s+)(-I|-L))(\s*["\']?)(\S+)(["\']?)', append_path, flags)
928
929
930def strip_prefix(prefix, string):
931    if not prefix:
932        return string
933    return re.sub(r'^' + prefix, '', string)
934
935
936# --------------------------------------------------------------------
937
938# Regexes needed for parsing Makefile-like syntaxes
939_variable_rx = re.compile(r'([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)')
940_findvar1_rx = re.compile(r'\$\(([A-Za-z][A-Za-z0-9_]*)\)')
941_findvar2_rx = re.compile(r'\${([A-Za-z][A-Za-z0-9_]*)}')
942
943
944def makefile(fileobj, dct=None):
945    """Parse a Makefile-style file.
946
947    A dictionary containing name/value pairs is returned.  If an
948    optional dictionary is passed in as the second argument, it is
949    used instead of a new dictionary.
950    """
951    fp = TextFile(file=fileobj, strip_comments=1, skip_blanks=1, join_lines=1)
952
953    if dct is None:
954        dct = {}
955    done = {}
956    notdone = {}
957
958    while 1:
959        line = fp.readline()
960        if line is None:  # eof
961            break
962        m = _variable_rx.match(line)
963        if m:
964            n, v = m.group(1, 2)
965            v = str.strip(v)
966            if '$' in v:
967                notdone[n] = v
968            else:
969                try:
970                    v = int(v)
971                except ValueError:
972                    pass
973                done[n] = v
974                try:
975                    del notdone[n]
976                except KeyError:
977                    pass
978    fp.close()
979
980    # do variable interpolation here
981    while notdone:
982        for name in list(notdone.keys()):
983            value = notdone[name]
984            m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
985            if m:
986                n = m.group(1)
987                found = True
988                if n in done:
989                    item = str(done[n])
990                elif n in notdone:
991                    # get it on a subsequent round
992                    found = False
993                else:
994                    done[n] = item = ''
995                if found:
996                    after = value[m.end() :]
997                    value = value[: m.start()] + item + after
998                    if '$' in after:
999                        notdone[name] = value
1000                    else:
1001                        try:
1002                            value = int(value)
1003                        except ValueError:
1004                            done[name] = str.strip(value)
1005                        else:
1006                            done[name] = value
1007                        del notdone[name]
1008            else:
1009                # bogus variable reference;
1010                # just drop it since we can't deal
1011                del notdone[name]
1012    # save the results in the global dictionary
1013    dct.update(done)
1014    return dct
1015
1016
1017# --------------------------------------------------------------------
1018