xref: /petsc/src/binding/petsc4py/src/petsc4py/lib/__init__.py (revision 0f6b61ebcc3e6537ba558a641cd0ae0acf185194)
1# Author:  Lisandro Dalcin
2# Contact: dalcinl@gmail.com
3
4# --------------------------------------------------------------------
5
6"""
7Extension modules for different PETSc configurations.
8
9PETSc can be configured with different options (eg. debug/optimized,
10single/double precisionm, C/C++ compilers, external packages). Each
11configuration variant is associated to a name, frequently available as
12an environmental variable named ``PETSC_ARCH``.
13
14This package holds all the available variants of the PETSc
15extension module built against specific PETSc configurations. It also
16provides a convenience function using of the ``importlib`` module
17for easily importing any of the available extension modules depending
18on the value of a user-provided configuration name, the ``PETSC_ARCH``
19environmental variable, or a configuration file.
20
21Python implementations of the Python aware PETSc types are also
22exposed in submodules here.
23"""
24
25# --------------------------------------------------------------------
26
27
28def ImportPETSc(arch=None):
29    """
30    Import the PETSc extension module for a given configuration name.
31    """
32    path, arch = getPathArchPETSc(arch)
33    return Import('petsc4py', 'PETSc', path, arch)
34
35
36def getPathArchPETSc(arch=None):
37    """
38    Undocumented.
39    """
40    import os
41
42    path = os.path.abspath(os.path.dirname(__file__))
43    rcvar, rcfile = 'PETSC_ARCH', 'petsc.cfg'
44    path, arch = getPathArch(path, arch, rcvar, rcfile)
45    return (path, arch)
46
47
48# --------------------------------------------------------------------
49
50
51def Import(pkg, name, path, arch):
52    """
53    Import helper for PETSc-based extension modules.
54    """
55    import os
56    import sys
57    import warnings
58
59    try:
60        import importlib.machinery
61        import importlib.util
62    except ImportError:
63        importlib = None
64        import imp
65
66    def get_ext_suffix():
67        if importlib:
68            return importlib.machinery.EXTENSION_SUFFIXES[0]
69        return imp.get_suffixes()[0][0]
70
71    def import_module(pkg, name, path, arch):
72        fullname = f'{pkg}.{name}'
73        pathlist = [os.path.join(path, arch)]
74        if importlib:
75            finder = importlib.machinery.PathFinder()
76            spec = finder.find_spec(fullname, pathlist)
77            module = importlib.util.module_from_spec(spec)
78            sys.modules[fullname] = module
79            spec.loader.exec_module(module)
80            return module
81        f, fn, info = imp.find_module(name, pathlist)
82        with f:
83            return imp.load_module(fullname, f, fn, info)
84
85    # test if extension module was already imported
86    module = sys.modules.get(f'{pkg}.{name}')
87    filename = getattr(module, '__file__', '')
88    if filename.endswith(get_ext_suffix()):
89        # if 'arch' is None, do nothing; otherwise this
90        # call may be invalid if extension module for
91        # other 'arch' has been already imported.
92        if arch is not None and arch != module.__arch__:
93            raise ImportError('%s already imported' % module)
94        return module
95
96    # silence annoying Cython warning
97    warnings.filterwarnings('ignore', message='numpy.dtype size changed')
98    warnings.filterwarnings('ignore', message='numpy.ndarray size changed')
99    # import extension module from 'path/arch' directory
100    module = import_module(pkg, name, path, arch)
101    module.__arch__ = arch  # save arch value
102    setattr(sys.modules[pkg], name, module)
103    return module
104
105
106def getPathArch(path, arch, rcvar='PETSC_ARCH', rcfile='petsc.cfg'):
107    """
108    Undocumented.
109    """
110    import os
111    import warnings
112
113    # path
114    if not path:
115        path = '.'
116    elif os.path.isfile(path):
117        path = os.path.dirname(path)
118    elif not os.path.isdir(path):
119        raise ValueError("invalid path: '%s'" % path)
120    # arch
121    if arch is not None:
122        if not isinstance(arch, str):
123            raise TypeError('arch argument must be string')
124        if not os.path.isdir(os.path.join(path, arch)):
125            raise TypeError("invalid arch value: '%s'" % arch)
126        return (path, arch)
127
128    # helper function
129    def arch_list(arch):
130        arch = arch.strip().split(os.path.pathsep)
131        arch = [a.strip() for a in arch if a]
132        return [a for a in arch if a]
133
134    # try to get arch from the environment
135    arch_env = arch_list(os.environ.get(rcvar, ''))
136    for arch in arch_env:
137        if os.path.isdir(os.path.join(path, arch)):
138            return (path, arch)
139    # configuration file
140    if not os.path.isfile(rcfile):
141        rcfile = os.path.join(path, rcfile)
142        if not os.path.isfile(rcfile):
143            # now point to continue
144            return (path, '')
145
146    # helper function
147    def parse_rc(rcfile):
148        with open(rcfile) as f:
149            rcdata = f.read()
150        lines = [ln.strip() for ln in rcdata.splitlines()]
151        lines = [ln for ln in lines if not ln.startswith('#')]
152        entries = [ln.split('=') for ln in lines if ln]
153        entries = [(k.strip(), v.strip()) for k, v in entries]
154        return dict(entries)
155
156    # try to get arch from data in config file
157    configrc = parse_rc(rcfile)
158    arch_cfg = arch_list(configrc.get(rcvar, ''))
159    for arch in arch_cfg:
160        if arch.startswith('%(') and arch.endswith(')s'):
161            arch = arch % os.environ
162        if os.path.isdir(os.path.join(path, arch)):
163            if arch_env:
164                warnings.warn(
165                    f"ignored arch: '{os.path.pathsep.join(arch_env)}', using: '{arch}'",
166                    stacklevel=2,
167                )
168            return (path, arch)
169    # nothing good found
170    return (path, '')
171
172
173def getInitArgs(args):
174    """
175    Undocumented.
176    """
177    import shlex
178    import sys
179
180    if args is None:
181        args = []
182    elif isinstance(args, str):
183        args = shlex.split(args)
184    else:
185        args = [str(a) for a in args]
186        args = [a for a in args if a]
187    if args and args[0].startswith('-'):
188        sys_argv = getattr(sys, 'argv', None)
189        sys_exec = getattr(sys, 'executable', 'python')
190        if sys_argv and sys_argv[0] and sys_argv[0] != '-c':
191            prog_name = sys_argv[0]
192        else:
193            prog_name = sys_exec
194        args.insert(0, prog_name)
195    return args
196
197
198# --------------------------------------------------------------------
199