"""OpenMP wrapper using a libgomp dynamically loaded library.""" from ctypes.util import find_library from subprocess import check_output, CalledProcessError, DEVNULL import ctypes import os import sys import sysconfig try: # there may be an environ modification when loading config from pythran.config import compiler except ImportError: def compiler(): return os.environ.get('CXX', 'c++') cxx = compiler() # This function and the `msvc_runtime_*` ones below are taken over from # numpy.distutils def get_shared_lib_extension(is_python_ext=False): """Return the correct file extension for shared libraries. Parameters ---------- is_python_ext : bool, optional Whether the shared library is a Python extension. Default is False. Returns ------- so_ext : str The shared library extension. Notes ----- For Python shared libs, `so_ext` will typically be '.so' on Linux and OS X, and '.pyd' on Windows. For Python >= 3.2 `so_ext` has a tag prepended on POSIX systems according to PEP 3149. """ confvars = sysconfig.get_config_vars() so_ext = confvars.get('EXT_SUFFIX', '') if not is_python_ext: # hardcode some known values to avoid some old distutils bugs if (sys.platform.startswith('linux') or sys.platform.startswith('gnukfreebsd')): so_ext = '.so' elif sys.platform.startswith('darwin'): so_ext = '.dylib' elif sys.platform.startswith('win'): so_ext = '.dll' else: # don't use long extension (e.g., .cpython-310-x64_64-linux-gnu.so', # see PEP 3149), but subtract that Python-specific part here so_ext = so_ext.replace('.' + confvars.get('SOABI'), '', 1) return so_ext def msvc_runtime_version(): "Return version of MSVC runtime library, as defined by __MSC_VER__ macro" msc_pos = sys.version.find('MSC v.') if msc_pos != -1: msc_ver = int(sys.version[msc_pos+6:msc_pos+10]) else: msc_ver = None return msc_ver def msvc_runtime_major(): "Return major version of MSVC runtime coded like get_build_msvc_version" major = {1300: 70, # MSVC 7.0 1310: 71, # MSVC 7.1 1400: 80, # MSVC 8 1500: 90, # MSVC 9 (aka 2008) 1600: 100, # MSVC 10 (aka 2010) 1900: 140, # MSVC 14 (aka 2015) 1910: 141, # MSVC 141 (aka 2017) 1920: 142, # MSVC 142 (aka 2019) }.get(msvc_runtime_version(), None) return major class OpenMP(object): """ Internal representation of the OpenMP module. Custom class is used to dynamically add omp runtime function to this library when function is called. """ def __init__(self): # FIXME: this is broken, for at least two reasons: # (1) checking how Python was built is not a good way to determine if # MSVC is being used right now, # (2) the `msvc_runtime_major` function above came from # `numpy.distutils`, where it did not have entries for 1910/1920 # and returned None instead ver = msvc_runtime_major() if ver is None: self.init_not_msvc() else: self.init_msvc(ver) def init_msvc(self, ver): vcomp_path = find_library('vcomp%d.dll' % ver) if not vcomp_path: raise ImportError("I can't find a shared library for vcomp.") else: # Load the library (shouldn't fail with an absolute path right?) self.libomp = ctypes.CDLL(vcomp_path) self.version = 20 def get_libomp_names(self): """Return list of OpenMP libraries to try""" return ['omp', 'gomp', 'iomp5'] def init_not_msvc(self): """ Find OpenMP library and try to load if using ctype interface. """ # find_library() does not automatically search LD_LIBRARY_PATH # until Python 3.6+, so we explicitly add it. # LD_LIBRARY_PATH is used on Linux, while macOS uses DYLD_LIBRARY_PATH # and DYLD_FALLBACK_LIBRARY_PATH. env_vars = [] if sys.platform == 'darwin': env_vars = ['DYLD_LIBRARY_PATH', 'DYLD_FALLBACK_LIBRARY_PATH'] else: env_vars = ['LD_LIBRARY_PATH'] paths = [] for env_var in env_vars: env_paths = os.environ.get(env_var, '') if env_paths: paths.extend(env_paths.split(os.pathsep)) libomp_names = self.get_libomp_names() if cxx is not None: for libomp_name in libomp_names: cmd = [cxx, '-print-file-name=lib{}{}'.format( libomp_name, get_shared_lib_extension())] # The subprocess can fail in various ways, including because it # doesn't support '-print-file-name'. In that case just give up. try: output = check_output(cmd, stderr=DEVNULL) path = os.path.dirname(output.decode().strip()) if path: paths.append(path) except (OSError, CalledProcessError): pass for libomp_name in libomp_names: # Try to load find libomp shared library using loader search dirs libomp_path = find_library(libomp_name) # Try to use custom paths if lookup failed if not libomp_path: for path in paths: candidate_path = os.path.join( path, 'lib{}{}'.format(libomp_name, get_shared_lib_extension())) if os.path.isfile(candidate_path): libomp_path = candidate_path break # Load the library if libomp_path: try: self.libomp = ctypes.CDLL(libomp_path) except OSError: raise ImportError("found openMP library '{}' but couldn't load it. " "This may happen if you are cross-compiling.".format(libomp_path)) self.version = 45 return raise ImportError("I can't find a shared library for libomp, you may need to install it " "or adjust the {} environment variable.".format(env_vars[0])) def __getattr__(self, name): """ Get correct function name from libgomp ready to be use. __getattr__ is call only `name != libomp` as libomp is a real attribute. """ if name == 'VERSION': return self.version return getattr(self.libomp, 'omp_' + name) # see http://mail.python.org/pipermail/python-ideas/2012-May/014969.html sys.modules[__name__] = OpenMP()