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