# -*- coding: utf-8 -*- # Copyright (c) 2015-1018, imageio contributors # Copyright (C) 2013, Zach Pincus, Almar Klein and others """ This module contains generic code to find and load a dynamic library. """ import os import sys import ctypes LOCALDIR = os.path.abspath(os.path.dirname(__file__)) # Flag that can be patched / set to True to disable loading non-system libs SYSTEM_LIBS_ONLY = False def looks_lib(fname): """ Returns True if the given filename looks like a dynamic library. Based on extension, but cross-platform and more flexible. """ fname = fname.lower() if sys.platform.startswith("win"): return fname.endswith(".dll") elif sys.platform.startswith("darwin"): return fname.endswith(".dylib") else: return fname.endswith(".so") or ".so." in fname def generate_candidate_libs(lib_names, lib_dirs=None): """ Generate a list of candidate filenames of what might be the dynamic library corresponding with the given list of names. Returns (lib_dirs, lib_paths) """ lib_dirs = lib_dirs or [] # Get system dirs to search sys_lib_dirs = [ "/lib", "/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", "/usr/local/lib", "/opt/local/lib", ] # Get Python dirs to search (shared if for Pyzo) py_sub_dirs = ["lib", "DLLs", "Library/bin", "shared"] py_lib_dirs = [os.path.join(sys.prefix, d) for d in py_sub_dirs] if hasattr(sys, "base_prefix"): py_lib_dirs += [os.path.join(sys.base_prefix, d) for d in py_sub_dirs] # Get user dirs to search (i.e. HOME) home_dir = os.path.expanduser("~") user_lib_dirs = [os.path.join(home_dir, d) for d in ["lib"]] # Select only the dirs for which a directory exists, and remove duplicates potential_lib_dirs = lib_dirs + sys_lib_dirs + py_lib_dirs + user_lib_dirs lib_dirs = [] for ld in potential_lib_dirs: if os.path.isdir(ld) and ld not in lib_dirs: lib_dirs.append(ld) # Now attempt to find libraries of that name in the given directory # (case-insensitive) lib_paths = [] for lib_dir in lib_dirs: # Get files, prefer short names, last version files = os.listdir(lib_dir) files = reversed(sorted(files)) files = sorted(files, key=len) for lib_name in lib_names: # Test all filenames for name and ext for fname in files: if fname.lower().startswith(lib_name) and looks_lib(fname): lib_paths.append(os.path.join(lib_dir, fname)) # Return (only the items which are files) lib_paths = [lp for lp in lib_paths if os.path.isfile(lp)] return lib_dirs, lib_paths def load_lib(exact_lib_names, lib_names, lib_dirs=None): """ load_lib(exact_lib_names, lib_names, lib_dirs=None) Load a dynamic library. This function first tries to load the library from the given exact names. When that fails, it tries to find the library in common locations. It searches for files that start with one of the names given in lib_names (case insensitive). The search is performed in the given lib_dirs and a set of common library dirs. Returns ``(ctypes_library, library_path)`` """ # Checks assert isinstance(exact_lib_names, list) assert isinstance(lib_names, list) if lib_dirs is not None: assert isinstance(lib_dirs, list) exact_lib_names = [n for n in exact_lib_names if n] lib_names = [n for n in lib_names if n] # Get reference name (for better messages) if lib_names: the_lib_name = lib_names[0] elif exact_lib_names: the_lib_name = exact_lib_names[0] else: raise ValueError("No library name given.") # Collect filenames of potential libraries # First try a few bare library names that ctypes might be able to find # in the default locations for each platform. if SYSTEM_LIBS_ONLY: lib_dirs, lib_paths = [], [] else: lib_dirs, lib_paths = generate_candidate_libs(lib_names, lib_dirs) lib_paths = exact_lib_names + lib_paths # Select loader if sys.platform.startswith("win"): loader = ctypes.windll else: loader = ctypes.cdll # Try to load until success the_lib = None errors = [] for fname in lib_paths: try: the_lib = loader.LoadLibrary(fname) break except Exception as err: # Don't record errors when it couldn't load the library from an # exact name -- this fails often, and doesn't provide any useful # debugging information anyway, beyond "couldn't find library..." if fname not in exact_lib_names: errors.append((fname, err)) # No success ... if the_lib is None: if errors: # No library loaded, and load-errors reported for some # candidate libs err_txt = ["%s:\n%s" % (l, str(e)) for l, e in errors] msg = ( "One or more %s libraries were found, but " + "could not be loaded due to the following errors:\n%s" ) raise OSError(msg % (the_lib_name, "\n\n".join(err_txt))) else: # No errors, because no potential libraries found at all! msg = "Could not find a %s library in any of:\n%s" raise OSError(msg % (the_lib_name, "\n".join(lib_dirs))) # Done return the_lib, fname