import ctypes import ctypes.util import os import sys import platform from typing import ( Optional, Dict, List ) _here = os.path.dirname(__file__) class ExternalLibraryError(Exception): pass architecture = platform.architecture()[0] _windows_styles = ["{}", "lib{}", "lib{}_dynamic", "{}_dynamic"] _other_styles = ["{}", "lib{}"] if architecture == "32bit": for arch_style in ["32bit", "32" "86", "win32", "x86", "_x86", "_32", "_win32", "_32bit"]: for style in ["{}", "lib{}"]: _windows_styles.append(style.format("{}"+arch_style)) elif architecture == "64bit": for arch_style in ["64bit", "64" "86_64", "amd64", "win_amd64", "x86_64", "_x86_64", "_64", "_amd64", "_64bit"]: for style in ["{}", "lib{}"]: _windows_styles.append(style.format("{}"+arch_style)) run_tests = lambda lib, tests: [f(lib) for f in tests] # Get the appropriate directory for the shared libraries depending # on the current platform and architecture platform_ = platform.system() lib_dir = None if platform_ == "Darwin": lib_dir = "libs/macos" elif platform_ == "Windows": if architecture == "32bit": lib_dir = "libs/win32" elif architecture == "64bit": lib_dir = "libs/win_amd64" class Library: @staticmethod def load(names: Dict[str, str], paths: Optional[List[str]] = None, tests = []) -> Optional[ctypes.CDLL]: lib = InternalLibrary.load(names, tests) if lib is None: lib = ExternalLibrary.load(names["external"], paths, tests) return lib class InternalLibrary: @staticmethod def load(names: Dict[str, str], tests) -> Optional[ctypes.CDLL]: # If we do not have a library directory, give up immediately if lib_dir is None: return None # Get the appropriate library filename given the platform try: name = names[platform_] except KeyError: return None # Attempt to load the library from here path = _here + "/" + lib_dir + "/" + name try: lib = ctypes.CDLL(path) except OSError as e: return None # Check that the library passes the tests if tests and all(run_tests(lib, tests)): return lib # Library failed tests return None # Cache of libraries that have already been loaded _loaded_libraries: Dict[str, ctypes.CDLL] = {} class ExternalLibrary: @staticmethod def load(name, paths = None, tests = []): if name in _loaded_libraries: return _loaded_libraries[name] if sys.platform == "win32": lib = ExternalLibrary.load_windows(name, paths, tests) _loaded_libraries[name] = lib return lib else: lib = ExternalLibrary.load_other(name, paths, tests) _loaded_libraries[name] = lib return lib @staticmethod def load_other(name, paths = None, tests = []): os.environ["PATH"] += ";" + ";".join((os.getcwd(), _here)) if paths: os.environ["PATH"] += ";" + ";".join(paths) for style in _other_styles: candidate = style.format(name) library = ctypes.util.find_library(candidate) if library: try: lib = ctypes.CDLL(library) if tests and all(run_tests(lib, tests)): return lib except: pass @staticmethod def load_windows(name, paths = None, tests = []): os.environ["PATH"] += ";" + ";".join((os.getcwd(), _here)) if paths: os.environ["PATH"] += ";" + ";".join(paths) not_supported = [] # libraries that were found, but are not supported for style in _windows_styles: candidate = style.format(name) library = ctypes.util.find_library(candidate) if library: try: lib = ctypes.CDLL(library) if tests and all(run_tests(lib, tests)): return lib not_supported.append(library) except WindowsError: pass except OSError: not_supported.append(library) if not_supported: raise ExternalLibraryError("library '{}' couldn't be loaded, because the following candidates were not supported:".format(name) + ("\n{}" * len(not_supported)).format(*not_supported)) raise ExternalLibraryError("library '{}' couldn't be loaded".format(name))