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
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
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)), ]
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