def _metadata_from(self, package, methods=(), recursive_methods=()) -> set: """Collect metadata whose requirements are implied by given function names. Args: package: The module name that must be imported in a source file to trigger the search. methods: Function names from **package** which take a distribution name as an argument and imply that metadata is required for that distribution. recursive_methods: Like **methods** but also implies that a distribution's dependencies' metadata must be collected too. Returns: Required metadata in hook data ``(source, dest)`` format as returned by :func:`PyInstaller.utils.hooks.copy_metadata()`. Scan all source code to be included for usage of particular *key* functions which imply that that code will require metadata for some distribution (which may not be its own) at runtime. In the case of a match, collect the required metadata. """ from PyInstaller.utils.hooks import copy_metadata from pkg_resources import DistributionNotFound # Generate sets of possible function names to search for. need_metadata = set() need_recursive_metadata = set() for method in methods: need_metadata.update(bytecode.any_alias(package + "." + method)) for method in recursive_methods: need_metadata.update(bytecode.any_alias(package + "." + method)) out = set() for (name, code) in self.get_code_using(package).items(): for calls in bytecode.recursive_function_calls(code).values(): for (function_name, args) in calls: # Only consider function calls taking one argument. if len(args) != 1: continue package, = args try: if function_name in need_metadata: out.update(copy_metadata(package)) elif function_name in need_recursive_metadata: out.update(copy_metadata(package, recursive=True)) except DistributionNotFound: # Currently, we'll opt to silently skip over missing # metadata. continue return out
def __recursively_scan_code_objects_for_ctypes(code: CodeType): """ Detects ctypes dependencies, using reasonable heuristics that should cover most common ctypes usages; returns a list containing names of binaries detected as dependencies. """ from PyInstaller.depend.bytecode import any_alias, search_recursively binaries = [] ctypes_dll_names = { *any_alias("ctypes.CDLL"), *any_alias("ctypes.cdll.LoadLibrary"), *any_alias("ctypes.WinDLL"), *any_alias("ctypes.windll.LoadLibrary"), *any_alias("ctypes.OleDLL"), *any_alias("ctypes.oledll.LoadLibrary"), *any_alias("ctypes.PyDLL"), *any_alias("ctypes.pydll.LoadLibrary"), } find_library_names = { *any_alias("ctypes.util.find_library"), } for calls in bytecode.recursive_function_calls(code).values(): for (name, args) in calls: if not len(args) == 1 or not isinstance(args[0], str): continue if name in ctypes_dll_names: # ctypes.*DLL() or ctypes.*dll.LoadLibrary() binaries.append(*args) elif name in find_library_names: # ctypes.util.find_library() needs to be handled separately, # because we need to resolve the library base name given # as the argument (without prefix and suffix, e.g. 'gs') # into corresponding full name (e.g., 'libgs.so.9'). libname = args[0] if libname: libname = ctypes.util.find_library(libname) if libname: # On Windows, `find_library` may return # a full pathname. See issue #1934 libname = os.path.basename(libname) binaries.append(libname) # The above handles any flavour of function/class call. # We still need to capture the (albeit rarely used) case of loading # libraries with ctypes.cdll's getattr. for i in search_recursively(_scan_code_for_ctypes_getattr, code).values(): binaries.extend(i) return binaries