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
Exemple #2
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
Exemple #3
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
#-----------------------------------------------------------------------------
# Copyright (c) 2013-2019, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
import os.path

from PyInstaller.utils.hooks import add_qt5_dependencies, eval_statement
from PyInstaller.compat import is_win
from PyInstaller.depend.bindepend import getfullnameof

hiddenimports, binaries, datas = add_qt5_dependencies(__file__)

# Add libraries needed for SSL if these are available. See issue #3520, #4048.
if (is_win and eval_statement("""
    from PyQt5.QtNetwork import QSslSocket
    print(QSslSocket.supportsSsl())""")):

    rel_data_path = ['PyQt5', 'Qt', 'bin']
    binaries += [
        # Per http://doc.qt.io/qt-5/ssl.html#enabling-and-disabling-ssl-support,
        # the SSL libraries are dynamically loaded, implying they exist in
        # the system path. Include these.
        (getfullnameof('libeay32.dll'), os.path.join(*rel_data_path)),
        (getfullnameof('ssleay32.dll'), os.path.join(*rel_data_path)),
    ]
Exemple #5
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