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