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