Example #1
0
    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
Example #2
0
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