Пример #1
0
def _find_tcl_tk(hook_api):
    """
    Get a platform-specific 2-tuple of the absolute paths of the top-level
    external data directories for both Tcl and Tk, respectively.

    Returns
    -------
    list
        2-tuple whose first element is the value of `${TCL_LIBRARY}` and whose
        second element is the value of `${TK_LIBRARY}`.
    """
    bins = selectImports(hook_api.__file__)

    if is_darwin:
        # _tkinter depends on system Tcl/Tk frameworks.
        # For example this is the case of Python from homebrew.
        if not bins:
            # 'hook_api.binaries' can't be used because on Mac OS X _tkinter.so
            # might depend on system Tcl/Tk frameworks and these are not
            # included in 'hook_api.binaries'.
            bins = getImports(hook_api.__file__)
            # Reformat data structure from
            #     set(['lib1', 'lib2', 'lib3'])
            # to
            #     [('Tcl', '/path/to/Tcl'), ('Tk', '/path/to/Tk')]
            mapping = {}
            for l in bins:
                mapping[os.path.basename(l)] = l
            bins = [
                ('Tcl', mapping['Tcl']),
                ('Tk', mapping['Tk']),
            ]

        # _tkinter depends on Tcl/Tk compiled as frameworks.
        path_to_tcl = bins[0][1]
        if 'Library/Frameworks' in path_to_tcl:
            tcl_tk = _find_tcl_tk_darwin_frameworks(bins)
        # Tcl/Tk compiled as on Linux other Unixes.
        # For example this is the case of Tcl/Tk from macports.
        else:
            tcl_tk = _find_tcl_tk_dir()

    else:
        tcl_tk = _find_tcl_tk_dir()

    return tcl_tk
Пример #2
0
def _find_tcl_tk(hook_api):
    """
    Get a platform-specific 2-tuple of the absolute paths of the top-level
    external data directories for both Tcl and Tk, respectively.

    Returns
    -------
    list
        2-tuple whose first element is the value of `${TCL_LIBRARY}` and whose
        second element is the value of `${TK_LIBRARY}`.
    """
    bins = selectImports(hook_api.__file__)

    if is_darwin:
        # _tkinter depends on system Tcl/Tk frameworks.
        # For example this is the case of Python from homebrew.
        if not bins:
            # 'hook_api.binaries' can't be used because on Mac OS X _tkinter.so
            # might depend on system Tcl/Tk frameworks and these are not
            # included in 'hook_api.binaries'.
            bins = getImports(hook_api.__file__)
            # Reformat data structure from
            #     set(['lib1', 'lib2', 'lib3'])
            # to
            #     [('Tcl', '/path/to/Tcl'), ('Tk', '/path/to/Tk')]
            mapping = {}
            for l in bins:
                mapping[os.path.basename(l)] = l
            bins = [
                ('Tcl', mapping['Tcl']),
                ('Tk', mapping['Tk']),
            ]

        # _tkinter depends on Tcl/Tk compiled as frameworks.
        path_to_tcl = bins[0][1]
        if 'Library/Frameworks' in path_to_tcl and 'Python' not in path_to_tcl:  # Edited: https://github.com/pyinstaller/pyinstaller/issues/3753#issuecomment-432464838
            tcl_tk = _find_tcl_tk_darwin_frameworks(bins)
        # Tcl/Tk compiled as on Linux other Unixes.
        # For example this is the case of Tcl/Tk from macports.
        else:
            tcl_tk = _find_tcl_tk_dir()

    else:
        tcl_tk = _find_tcl_tk_dir()

    return tcl_tk
Пример #3
0
def find_tcl_tk_shared_libs(tkinter_ext_file):
    """
    Find Tcl and Tk shared libraries against which the
    _tkinter module is linked.

    Returns
    -------
    list
        list containing two tuples, one for Tcl and one for Tk library,
        where each tuple contains library name and its full path, i.e.,
        [(tcl_lib, tcl_libpath), (tk_lib, tk_libpath)]. If a library is
        not found, the corresponding tuple elements are set to None.
    """
    tcl_lib = None
    tcl_libpath = None
    tk_lib = None
    tk_libpath = None

    # Do not use bindepend.selectImports, as it ignores libraries seen
    # during previous invocations.
    _tkinter_imports = bindepend.getImports(tkinter_ext_file)

    def _get_library_path(lib):
        if not compat.is_win and not compat.is_cygwin:
            # non-Windows systems return the path of the library
            path = lib
        else:
            # We need to find the library
            path = bindepend.getfullnameof(lib)
        return path

    for lib in _tkinter_imports:
        # On some platforms, full path to the shared librars is returned.
        # So check only basename to prevent false positive matches due
        # to words tcl or tk being contained in the path.
        lib_name = os.path.basename(lib)
        lib_name_lower = lib_name.lower()  # lower-case for comparisons

        if 'tcl' in lib_name_lower:
            tcl_lib = lib_name
            tcl_libpath = _get_library_path(lib)
        elif 'tk' in lib_name_lower:
            tk_lib = lib_name
            tk_libpath = _get_library_path(lib)

    return [(tcl_lib, tcl_libpath), (tk_lib, tk_libpath)]
Пример #4
0
def add_qt5_dependencies(hook_file):
    # Accumulate all dependencies in a set to avoid duplicates.
    hiddenimports = set()
    translations_base = set()
    plugins = set()

    # Find the module underlying this Qt hook: change
    # ``/path/to/hook-PyQt5.blah.py`` to ``PyQt5.blah``.
    hook_name, hook_ext = os.path.splitext(os.path.basename(hook_file))
    assert hook_ext.startswith('.py')
    assert hook_name.startswith('hook-')
    module_name = hook_name[5:]
    namespace = module_name.split('.')[0]
    if namespace not in ('PyQt5', 'PySide2'):
        raise Exception('Invalid namespace: {0}'.format(namespace))
    is_PyQt5 = namespace == 'PyQt5'

    # Exit if the requested library can't be imported.
    if ((is_PyQt5 and not pyqt5_library_info.version)
            or (not is_PyQt5 and not pyside2_library_info.version)):
        return [], [], []

    # Look up the module returned by this import.
    module = get_module_file_attribute(module_name)
    logger.debug('add_qt5_dependencies: Examining %s, based on hook of %s.',
                 module, hook_file)

    # Walk through all the static dependencies of a dynamically-linked library
    # (``.so``/``.dll``/``.dylib``).
    imports = set(getImports(module))
    while imports:
        imp = imports.pop()

        # On Windows, find this library; other platforms already provide the
        # full path.
        if is_win:
            imp = getfullnameof(
                imp,
                # First, look for Qt binaries in the local Qt install.
                pyqt5_library_info.location['BinariesPath']
                if is_PyQt5 else pyside2_library_info.location['BinariesPath'])

        # Strip off the extension and ``lib`` prefix (Linux/Mac) to give the raw
        # name. Lowercase (since Windows always normalized names to lowercase).
        lib_name = os.path.splitext(os.path.basename(imp))[0].lower()
        # Linux libraries sometimes have a dotted version number --
        # ``libfoo.so.3``. It's now ''libfoo.so``, but the ``.so`` must also be
        # removed.
        if is_linux and os.path.splitext(lib_name)[1] == '.so':
            lib_name = os.path.splitext(lib_name)[0]
        if lib_name.startswith('lib'):
            lib_name = lib_name[3:]
        # Mac: rename from ``qt`` to ``qt5`` to match names in Windows/Linux.
        if is_darwin and lib_name.startswith('qt'):
            lib_name = 'qt5' + lib_name[2:]

        # match libs with QT_LIBINFIX set to '_conda', i.e. conda-forge builds
        if lib_name.endswith('_conda'):
            lib_name = lib_name[:-6]

        logger.debug('add_qt5_dependencies: raw lib %s -> parsed lib %s', imp,
                     lib_name)

        # Follow only Qt dependencies.
        if lib_name in _qt_dynamic_dependencies_dict:
            # Follow these to find additional dependencies.
            logger.debug('add_qt5_dependencies: Import of %s.', imp)
            imports.update(getImports(imp))
            # Look up which plugins and translations are needed.
            dd = _qt_dynamic_dependencies_dict[lib_name]
            lib_name_hiddenimports, lib_name_translations_base = dd[:2]
            lib_name_plugins = dd[2:]
            # Add them in.
            if lib_name_hiddenimports:
                hiddenimports.update([namespace + lib_name_hiddenimports])
            plugins.update(lib_name_plugins)
            if lib_name_translations_base:
                translations_base.update([lib_name_translations_base])

    # Change plugins into binaries.
    binaries = []
    for plugin in plugins:
        more_binaries = qt_plugins_binaries(plugin, namespace=namespace)
        binaries.extend(more_binaries)
    # Change translation_base to datas.
    tp = (pyqt5_library_info.location['TranslationsPath']
          if is_PyQt5 else pyside2_library_info.location['TranslationsPath'])
    datas = []
    for tb in translations_base:
        src = os.path.join(tp, tb + '_*.qm')
        # Not all PyQt5 installations include translations. See
        # https://github.com/pyinstaller/pyinstaller/pull/3229#issuecomment-359479893
        # and
        # https://github.com/pyinstaller/pyinstaller/issues/2857#issuecomment-368744341.
        if glob.glob(src):
            datas.append((
                src,
                os.path.join(
                    # The PySide2 Windows wheels place translations in a
                    # different location.
                    namespace,
                    '' if not is_PyQt5 and is_win else 'Qt',
                    'translations')))
        else:
            logger.warning(
                'Unable to find Qt5 translations %s. These '
                'translations were not packaged.', src)
    # Change hiddenimports to a list.
    hiddenimports = list(hiddenimports)

    logger.debug(
        'add_qt5_dependencies: imports from %s:\n'
        '  hiddenimports = %s\n'
        '  binaries = %s\n'
        '  datas = %s', hook_name, hiddenimports, binaries, datas)
    return hiddenimports, binaries, datas
                     pyside2_library_info.location['LibraryExecutablesPath'],
                     pyside2_library_info.location['PrefixPath'] + '/')
             ])))
        ]
        if compat.is_win:
            datas += [
                x for x in collect_system_data_files(
                    pyside2_library_info.location['PrefixPath'], '.')
                if os.path.basename(x[0]) == 'qt.conf'
            ]

    # Add Linux-specific libraries.
    if compat.is_linux:
        # The automatic library detection fails for `NSS
        # <https://packages.ubuntu.com/search?keywords=libnss3>`_, which is used by
        # QtWebEngine. In some distributions, the ``libnss`` supporting libraries
        # are stored in a subdirectory ``nss``. Since ``libnss`` is not statically
        # linked to these, but dynamically loads them, we need to search for and add
        # them.
        #
        # First, get all libraries linked to ``PyQt5.QtWebEngineWidgets``.
        for imp in getImports(
                get_module_file_attribute('PySide2.QtWebEngineWidgets')):
            # Look for ``libnss3.so``.
            if os.path.basename(imp).startswith('libnss3.so'):
                # Find the location of NSS: given a ``/path/to/libnss.so``,
                # add ``/path/to/nss/*.so`` to get the missing NSS libraries.
                nss_subdir = os.path.join(os.path.dirname(imp), 'nss')
                if os.path.exists(nss_subdir):
                    binaries.append((os.path.join(nss_subdir, '*.so'), 'nss'))
Пример #6
0
def get_qt_webengine_binaries_and_data_files(qt_library_info):
    binaries = []
    datas = []

    # Output directory (varies between PyQt and PySide and among OSes; the difference is abstracted by
    # qt_library_info.qt_rel_dir)
    rel_data_path = qt_library_info.qt_rel_dir

    if compat.is_darwin:
        # On macOS, Qt shared libraries are provided in form of .framework bundles. However, PyInstaller collects shared
        # library from the bundle into top-level application directory, breaking the bundle structure.
        #
        # QtWebEngine and its underlying Chromium engine, however, have very strict data file layout requirements due to
        # sandboxing, and does not work if the helper process executable does not load the shared library from
        # QtWebEngineCore.framework (which also needs to contain all resources).
        #
        # Therefore, we collect the QtWebEngineCore.framework manually, in order to obtain a working QtWebEngineProcess
        # helper executable. But because that bypasses our dependency scanner, we need to collect the dependent
        # .framework bundles as well. And we need to override QTWEBENGINEPROCESS_PATH in rthook, because the
        # QtWebEngineWidgets python extension actually loads up the copy of shared library that is located in
        # sys._MEIPASS (as opposed to the manually-copied one in .framework bundle). Furthermore, because the extension
        # modules use Qt shared libraries in sys._MEIPASS, we also copy all contents of
        # QtWebEngineCore.framework/Resources into sys._MEIPASS to make resource loading in the main process work.
        #
        # Besides being ugly, this approach has three main ramifications:
        # 1. we bundle two copies of each Qt shared library involved: the copy used by main process, picked up by
        #    dependency scanner; and a copy in manually-collected .framework bundle that is used by the helper process.
        # 2. the trick with copying contents of Resource directory of QtWebEngineCore.framework does not work in onefile
        #    mode, and consequently QtWebEngine does not work in onefile mode.
        # 3. copying contents of QtWebEngineCore.framework/Resource means that its Info.plist ends up in sys._MEIPASS,
        #    causing the main process in onedir mode to be mis-identified as "QtWebEngineProcess".
        #
        # In the near future, this quagmire will hopefully be properly sorted out, but in the mean time, we have to live
        # with what we have been given.
        data_path = qt_library_info.location['DataPath']
        libraries = [
            'QtCore', 'QtWebEngineCore', 'QtQuick', 'QtQml', 'QtQmlModels',
            'QtNetwork', 'QtGui', 'QtWebChannel', 'QtPositioning'
        ]
        for i in libraries:
            framework_dir = i + '.framework'
            datas += hooks.collect_system_data_files(
                os.path.join(data_path, 'lib', framework_dir),
                os.path.join(rel_data_path, 'lib', framework_dir), True)
        datas += [(os.path.join(data_path, 'lib', 'QtWebEngineCore.framework',
                                'Resources'), os.curdir)]
    else:
        # Windows and linux
        locales = 'qtwebengine_locales'
        resources = 'resources'

        # Translations
        datas.append((
            os.path.join(qt_library_info.location['TranslationsPath'],
                         locales),
            os.path.join(rel_data_path, 'translations', locales),
        ))

        # Resources; ``DataPath`` is the base directory for ``resources``, as per the
        # `docs <https://doc.qt.io/qt-5.10/qtwebengine-deploying.html#deploying-resources>`_.
        datas.append(
            (os.path.join(qt_library_info.location['DataPath'], resources),
             os.path.join(rel_data_path, resources)), )

        # Helper process executable (QtWebEngineProcess), located in ``LibraryExecutablesPath``.
        dest = os.path.join(
            rel_data_path,
            os.path.relpath(qt_library_info.location['LibraryExecutablesPath'],
                            qt_library_info.location['PrefixPath']))
        datas.append(
            (os.path.join(qt_library_info.location['LibraryExecutablesPath'],
                          'QtWebEngineProcess*'), dest))

    # Add Linux-specific libraries.
    if compat.is_linux:
        # The automatic library detection fails for `NSS <https://packages.ubuntu.com/search?keywords=libnss3>`_, which
        # is used by QtWebEngine. In some distributions, the ``libnss`` supporting libraries are stored in a
        # subdirectory ``nss``. Since ``libnss`` is not statically linked to these, but dynamically loads them, we need
        # to search for and add them.

        # First, get all libraries linked to ``QtWebEngineWidgets`` extension module.
        module_file = hooks.get_module_file_attribute(
            qt_library_info.namespace + '.QtWebEngineWidgets')
        module_imports = bindepend.getImports(module_file)
        for imp in module_imports:
            # Look for ``libnss3.so``.
            if os.path.basename(imp).startswith('libnss3.so'):
                # Find the location of NSS: given a ``/path/to/libnss.so``, add ``/path/to/nss/*.so`` to get the
                # missing NSS libraries.
                nss_glob = os.path.join(os.path.dirname(imp), 'nss', '*.so')
                if glob.glob(nss_glob):
                    binaries.append((nss_glob, 'nss'))

    return binaries, datas
Пример #7
0
def add_qt_dependencies(hook_file):
    # Accumulate all dependencies in a set to avoid duplicates.
    hiddenimports = set()
    translations_base = set()
    plugins = set()

    # Find the module underlying this Qt hook: change ``/path/to/hook-PyQt5.blah.py`` to ``PyQt5.blah``.
    hook_name, hook_ext = os.path.splitext(os.path.basename(hook_file))
    assert hook_ext.startswith('.py')
    assert hook_name.startswith('hook-')
    module_name = hook_name[5:]
    namespace = module_name.split('.')[0]
    # Retrieve Qt library info structure.
    qt_info = get_qt_library_info(namespace)

    # Exit if the requested library cannot be imported.
    # NOTE: qt_info.version can be empty list on older Qt5 versions (#5381).
    if qt_info.version is None:
        return [], [], []

    # Look up the module returned by this import.
    module = hooks.get_module_file_attribute(module_name)
    logger.debug('add_qt%d_dependencies: Examining %s, based on hook of %s.',
                 qt_info.qt_major, module, hook_file)

    # Walk through all the static dependencies of a dynamically-linked library (``.so``/``.dll``/``.dylib``).
    imports = set(bindepend.getImports(module))
    while imports:
        imp = imports.pop()

        # On Windows, find this library; other platforms already provide the full path.
        if compat.is_win:
            # First, look for Qt binaries in the local Qt install.
            imp = bindepend.getfullnameof(imp,
                                          qt_info.location['BinariesPath'])

        # Strip off the extension and ``lib`` prefix (Linux/Mac) to give the raw name.
        # Lowercase (since Windows always normalizes names to lowercase).
        lib_name = os.path.splitext(os.path.basename(imp))[0].lower()
        # Linux libraries sometimes have a dotted version number -- ``libfoo.so.3``. It is now ''libfoo.so``,
        # but the ``.so`` must also be removed.
        if compat.is_linux and os.path.splitext(lib_name)[1] == '.so':
            lib_name = os.path.splitext(lib_name)[0]
        if lib_name.startswith('lib'):
            lib_name = lib_name[3:]
        # Mac OS: handle different naming schemes. PyPI wheels ship framework-enabled Qt builds, where shared libraries
        # are part of .framework bundles (e.g., ``PyQt5/Qt5/lib/QtCore.framework/Versions/5/QtCore``). In Anaconda
        # (Py)Qt installations, the shared libraries are installed in environment's library directory, and contain
        # versioned extensions, e.g., ``libQt5Core.5.dylib``.
        if compat.is_darwin:
            if lib_name.startswith('qt') and not lib_name.startswith(
                    'qt' + str(qt_info.qt_major)):
                # Qt library from a framework bundle (e.g., ``QtCore``); change prefix from ``qt`` to ``qt5`` or ``qt6``
                # to match names in Windows/Linux.
                lib_name = 'qt' + str(qt_info.qt_major) + lib_name[2:]
            if lib_name.endswith('.' + str(qt_info.qt_major)):
                # Qt library from Anaconda, which originally had versioned extension, e.g., ``libfoo.5.dynlib``.
                # The above processing turned it into ``foo.5``, so we need to remove the last two characters.
                lib_name = lib_name[:-2]

        # Match libs with QT_LIBINFIX set to '_conda', i.e. conda-forge builds.
        if lib_name.endswith('_conda'):
            lib_name = lib_name[:-6]

        logger.debug('add_qt%d_dependencies: raw lib %s -> parsed lib %s',
                     qt_info.qt_major, imp, lib_name)

        # Follow only Qt dependencies.
        _qt_dynamic_dependencies_dict = (_qt5_dynamic_dependencies_dict
                                         if qt_info.qt_major == 5 else
                                         _qt6_dynamic_dependencies_dict)
        if lib_name in _qt_dynamic_dependencies_dict:
            # Follow these to find additional dependencies.
            logger.debug('add_qt%d_dependencies: Import of %s.',
                         qt_info.qt_major, imp)
            imports.update(bindepend.getImports(imp))
            # Look up which plugins and translations are needed.
            dd = _qt_dynamic_dependencies_dict[lib_name]
            lib_name_hiddenimports, lib_name_translations_base = dd[:2]
            lib_name_plugins = dd[2:]
            # Add them in.
            if lib_name_hiddenimports:
                hiddenimports.update([namespace + lib_name_hiddenimports])
            plugins.update(lib_name_plugins)
            if lib_name_translations_base:
                translations_base.update([lib_name_translations_base])

    # Change plugins into binaries.
    binaries = []
    for plugin in plugins:
        more_binaries = qt_plugins_binaries(plugin, namespace=namespace)
        binaries.extend(more_binaries)
    # Change translation_base to datas.
    tp = qt_info.location['TranslationsPath']
    tp_dst = os.path.join(qt_info.qt_rel_dir, 'translations')
    datas = []
    for tb in translations_base:
        src = os.path.join(tp, tb + '_*.qm')
        # Not all PyQt5 installations include translations. See
        # https://github.com/pyinstaller/pyinstaller/pull/3229#issuecomment-359479893
        # and
        # https://github.com/pyinstaller/pyinstaller/issues/2857#issuecomment-368744341.
        if glob.glob(src):
            datas.append((src, tp_dst))
        else:
            logger.warning(
                'Unable to find Qt%d translations %s. These translations were not packaged.',
                qt_info.qt_major, src)
    # Change hiddenimports to a list.
    hiddenimports = list(hiddenimports)

    logger.debug(
        'add_qt%d_dependencies: imports from %s:\n'
        '  hiddenimports = %s\n'
        '  binaries = %s\n'
        '  datas = %s', qt_info.qt_major, hook_name, hiddenimports, binaries,
        datas)
    return hiddenimports, binaries, datas
def _find_tcl_tk(hook_api):
    """
    Get a platform-specific 2-tuple of the absolute paths of the top-level
    external data directories for both Tcl and Tk, respectively.

    Returns
    -------
    list
        2-tuple whose first element is the value of `${TCL_LIBRARY}` and whose
        second element is the value of `${TK_LIBRARY}`.
    """
    bins = selectImports(hook_api.__file__)

    if is_darwin:
        # _tkinter depends on system Tcl/Tk frameworks.
        # For example this is the case of Python from homebrew.
        if not bins:
            # 'hook_api.binaries' can't be used because on Mac OS X _tkinter.so
            # might depend on system Tcl/Tk frameworks and these are not
            # included in 'hook_api.binaries'.
            bins = getImports(hook_api.__file__)

            if bins:
                # Reformat data structure from
                #     set(['lib1', 'lib2', 'lib3'])
                # to
                #     [('Tcl', '/path/to/Tcl'), ('Tk', '/path/to/Tk')]
                mapping = {}
                for lib in bins:
                    mapping[os.path.basename(lib)] = lib
                bins = [
                    ('Tcl', mapping['Tcl']),
                    ('Tk', mapping['Tk']),
                ]
            else:
                # Starting with macOS 11, system libraries are hidden.
                # Until we adjust library discovery accordingly, bins
                # will end up empty. But this implicitly indicates that
                # the system framework is used, so return None, None
                # to inform the caller.
                return None, None

        # _tkinter depends on Tcl/Tk compiled as frameworks.
        path_to_tcl = bins[0][1]
        # OS X system installation of Tcl/Tk.
        # [/System]/Library/Frameworks/Tcl.framework/Resources/Scripts/Tcl
        if 'Library/Frameworks/Tcl.framework' in path_to_tcl:
            #tcl_tk = _find_tcl_tk_darwin_system_frameworks(bins)
            tcl_tk = None, None  # Do not gather system framework's data

        # Tcl/Tk compiled as on Linux other Unixes.
        # This is the case of Tcl/Tk from macports and Tck/Tk built into
        # python.org OS X python distributions.
        # python.org built-in tcl/tk is located at
        # /Library/Frameworks/Python.framework/Versions/3.x/lib/libtcl8.6.dylib
        else:
            tcl_tk = _find_tcl_tk_dir()

    else:
        tcl_tk = _find_tcl_tk_dir()

    return tcl_tk
Пример #9
0
def add_qt5_dependencies(hook_file):
    # Accumulate all dependencies in a set to avoid duplicates.
    hiddenimports = set()
    translations_base = set()
    plugins = set()

    # Find the module underlying this Qt hook: change
    # ``/path/to/hook-PyQt5.blah.py`` to ``PyQt5.blah``.
    hook_name, hook_ext = os.path.splitext(os.path.basename(hook_file))
    assert hook_ext.startswith('.py')
    assert hook_name.startswith('hook-')
    module_name = hook_name[5:]
    namespace = module_name.split('.')[0]
    if namespace != 'PyQt5':
        raise Exception('Invalid namespace: {0}'.format(namespace))

    # Look up the module returned by this import.
    module = get_module_file_attribute(module_name)
    logger.debug('add_qt5_dependencies: Examining %s, based on hook of %s.',
                 module, hook_file)

    # Walk through all the static dependencies of a dynamically-linked library
    # (``.so``/``.dll``/``.dylib``).
    imports = set(getImports(module))
    while imports:
        imp = imports.pop()

        # On Windows, find this library; other platforms already provide the
        # full path.
        if is_win:
            imp = getfullnameof(imp)

        # Strip off the extension and ``lib`` prefix (Linux/Mac) to give the raw
        # name. Lowercase (since Windows always normalized names to lowercase).
        lib_name = os.path.splitext(os.path.basename(imp))[0].lower()
        # Linux libraries sometimes have a dotted version number --
        # ``libfoo.so.3``. It's now ''libfoo.so``, but the ``.so`` must also be
        # removed.
        if is_linux and os.path.splitext(lib_name)[1] == '.so':
            lib_name = os.path.splitext(lib_name)[0]
        if lib_name.startswith('lib'):
            lib_name = lib_name[3:]
        # Mac: rename from ``qt`` to ``qt5`` to match names in Windows/Linux.
        if is_darwin and lib_name.startswith('qt'):
            lib_name = 'qt5' + lib_name[2:]
        logger.debug('add_qt5_dependencies: raw lib %s -> parsed lib %s',
                     imp, lib_name)

        # Follow only Qt dependencies.
        if lib_name in _qt_dynamic_dependencies_dict:
            # Follow these to find additional dependencies.
            logger.debug('add_qt5_dependencies: Import of %s.', imp)
            imports.update(getImports(imp))
            # Look up which plugins and translations are needed. Avoid Python
            # 3-only syntax, since the Python 2.7 parser will raise an
            # exception. The original statment was:
            ## (lib_name_hiddenimports, lib_name_translations_base,
            ## *lib_name_plugins) = _qt_dynamic_dependencies_dict[lib_name]
            dd = _qt_dynamic_dependencies_dict[lib_name]
            lib_name_hiddenimports, lib_name_translations_base = dd[:2]
            lib_name_plugins = dd[2:]
            # Add them in.
            if lib_name_hiddenimports:
                hiddenimports.update([lib_name_hiddenimports])
            plugins.update(lib_name_plugins)
            if lib_name_translations_base:
                translations_base.update([lib_name_translations_base])

    # Change plugins into binaries.
    binaries = []
    for plugin in plugins:
        more_binaries = qt_plugins_binaries(plugin, namespace=namespace)
        binaries.extend(more_binaries)
    # Change translation_base to datas.
    tp = pyqt5_library_info.location['TranslationsPath']
    datas = []
    for tb in translations_base:
        src = os.path.join(tp, tb + '_*.qm')
        # Not all PyQt5 installations include translations. See
        # https://github.com/pyinstaller/pyinstaller/pull/3229#issuecomment-359479893
        # and
        # https://github.com/pyinstaller/pyinstaller/issues/2857#issuecomment-368744341.
        if glob.glob(src):
            datas.append( (src, os.path.join(namespace, 'Qt', 'translations')) )
        else:
            logger.warning('Unable to find Qt5 translations %s. These '
                           'translations were not packaged.', src)
    # Change hiddenimports to a list.
    hiddenimports = list(hiddenimports)

    logger.debug('add_qt5_dependencies: imports from %s:\n'
                 '  hiddenimports = %s\n'
                 '  binaries = %s\n'
                 '  datas = %s',
                 hook_name, hiddenimports, binaries, datas)
    return hiddenimports, binaries, datas