123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- #-----------------------------------------------------------------------------
- # Copyright (c) 2013-2021, PyInstaller Development Team.
- #
- # Distributed under the terms of the GNU General Public License (version 2
- # or later) with exception for distributing the bootloader.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #
- # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
- #-----------------------------------------------------------------------------
- """
- Manipulating with dynamic libraries.
- """
- import os.path
- from PyInstaller.utils.win32 import winutils
- __all__ = ['exclude_list', 'include_list', 'include_library']
- import os
- import re
- import PyInstaller.log as logging
- from PyInstaller import compat
- logger = logging.getLogger(__name__)
- _BOOTLOADER_FNAMES = {'run', 'run_d', 'runw', 'runw_d'}
- # Ignoring some system libraries speeds up packaging process
- _excludes = {
- # Ignore annoying warnings with Windows system DLLs.
- #
- # 'W: library kernel32.dll required via ctypes not found'
- # 'W: library coredll.dll required via ctypes not found'
- #
- # These these dlls has to be ignored for all operating systems because they might be resolved when scanning code for
- # ctypes dependencies.
- r'advapi32\.dll',
- r'ws2_32\.dll',
- r'gdi32\.dll',
- r'oleaut32\.dll',
- r'shell32\.dll',
- r'ole32\.dll',
- r'coredll\.dll',
- r'crypt32\.dll',
- r'kernel32',
- r'kernel32\.dll',
- r'msvcrt\.dll',
- r'rpcrt4\.dll',
- r'user32\.dll',
- # Some modules tries to import the Python library. e.g. pyreadline.console.console
- r'python\%s\%s',
- }
- # Regex includes - overrides excludes. Include list is used only to override specific libraries from exclude list.
- _includes = set()
- _win_includes = {
- # DLLs are from 'Microsoft Visual C++ 2010 Redistributable Package'
- # http://msdn.microsoft.com/en-us/library/8kche8ah(v=vs.100).aspx
- #
- # Python 3.3 and 3.4 use Visual Studio C++ 2010 for Windows builds; python33.dll depends on msvcr100.dll.
- #
- # Visual Studio C++ 2010 does not need Assembly manifests anymore and uses C++ runtime libraries the old way -
- # pointing to C:\Windows\System32. It is necessary to allow inclusion of these libraries from C:\Windows\System32.
- r'atl100\.dll',
- r'msvcr100\.dll',
- r'msvcp100\.dll',
- r'mfc100\.dll',
- r'mfc100u\.dll',
- r'mfcmifc80\.dll',
- r'mfcm100\.dll',
- r'mfcm100u\.dll',
- # Python 3.5 uses the Univeral C Runtime which consists of these DLLs:
- r'api-ms-win-core.*',
- r'api-ms-win-crt.*',
- r'ucrtbase\.dll',
- r'vcruntime140\.dll',
- # Additional DLLs from VC 2015/2017/2019 runtime. Allow these to be collected to avoid missing-DLL errors when the
- # target machine does not have the VC redistributable installed.
- r'msvcp140\.dll',
- r'msvcp140_1\.dll',
- r'msvcp140_2\.dll',
- r'vcruntime140_1\.dll',
- r'vcomp140\.dll',
- r'concrt140\.dll',
- # Allow pythonNN.dll, pythoncomNN.dll, pywintypesNN.dll
- r'py(?:thon(?:com(?:loader)?)?|wintypes)\d+\.dll',
- }
- _win_excludes = {
- # On Windows, only .dll files can be loaded.
- r'.*\.so',
- r'.*\.dylib',
- # MS assembly excludes
- r'Microsoft\.Windows\.Common-Controls',
- }
- _unix_excludes = {
- r'libc\.so(\..*)?',
- r'libdl\.so(\..*)?',
- r'libm\.so(\..*)?',
- r'libpthread\.so(\..*)?',
- r'librt\.so(\..*)?',
- r'libthread_db\.so(\..*)?',
- # glibc regex excludes.
- r'ld-linux\.so(\..*)?',
- r'libBrokenLocale\.so(\..*)?',
- r'libanl\.so(\..*)?',
- r'libcidn\.so(\..*)?',
- r'libcrypt\.so(\..*)?',
- r'libnsl\.so(\..*)?',
- r'libnss_compat.*\.so(\..*)?',
- r'libnss_dns.*\.so(\..*)?',
- r'libnss_files.*\.so(\..*)?',
- r'libnss_hesiod.*\.so(\..*)?',
- r'libnss_nis.*\.so(\..*)?',
- r'libnss_nisplus.*\.so(\..*)?',
- r'libresolv\.so(\..*)?',
- r'libutil\.so(\..*)?',
- # graphical interface libraries come with graphical stack (see libglvnd)
- r'libE?(Open)?GLX?(ESv1_CM|ESv2)?(dispatch)?\.so(\..*)?',
- r'libdrm\.so(\..*)?',
- # libxcb-dri changes ABI frequently (e.g.: between Ubuntu LTS releases) and is usually installed as dependency of
- # the graphics stack anyway. No need to bundle it.
- r'libxcb\.so(\..*)?',
- r'libxcb-dri.*\.so(\..*)?',
- }
- _aix_excludes = {
- r'libbz2\.a',
- r'libc\.a',
- r'libC\.a',
- r'libcrypt\.a',
- r'libdl\.a',
- r'libintl\.a',
- r'libpthreads\.a',
- r'librt\\.a',
- r'librtl\.a',
- r'libz\.a',
- }
- if compat.is_win:
- _includes |= _win_includes
- _excludes |= _win_excludes
- elif compat.is_aix:
- # The exclude list for AIX differs from other *nix platforms.
- _excludes |= _aix_excludes
- elif compat.is_unix:
- # Common excludes for *nix platforms -- except AIX.
- _excludes |= _unix_excludes
- class ExcludeList(object):
- def __init__(self):
- self.regex = re.compile('|'.join(_excludes), re.I)
- def search(self, libname):
- # Running re.search() on '' regex never returns None.
- if _excludes:
- return self.regex.match(os.path.basename(libname))
- else:
- return False
- class IncludeList(object):
- def __init__(self):
- self.regex = re.compile('|'.join(_includes), re.I)
- def search(self, libname):
- # Running re.search() on '' regex never returns None.
- if _includes:
- return self.regex.match(os.path.basename(libname))
- else:
- return False
- exclude_list = ExcludeList()
- include_list = IncludeList()
- if compat.is_darwin:
- # On Mac use macholib to decide if a binary is a system one.
- from macholib import util
- class MacExcludeList(object):
- def __init__(self, global_exclude_list):
- # Wraps the global 'exclude_list' before it is overridden by this class.
- self._exclude_list = global_exclude_list
- def search(self, libname):
- # First try global exclude list. If it matches, return its result; otherwise continue with other check.
- result = self._exclude_list.search(libname)
- if result:
- return result
- else:
- return util.in_system_path(libname)
- exclude_list = MacExcludeList(exclude_list)
- elif compat.is_win:
- class WinExcludeList(object):
- def __init__(self, global_exclude_list):
- self._exclude_list = global_exclude_list
- # use normpath because msys2 uses / instead of \
- self._windows_dir = os.path.normpath(winutils.get_windows_dir().lower())
- def search(self, libname):
- libname = libname.lower()
- result = self._exclude_list.search(libname)
- if result:
- return result
- else:
- # Exclude everything from the Windows directory by default.
- # .. sometimes realpath changes the case of libname, lower it
- # .. use normpath because msys2 uses / instead of \
- fn = os.path.normpath(os.path.realpath(libname).lower())
- return fn.startswith(self._windows_dir)
- exclude_list = WinExcludeList(exclude_list)
- def include_library(libname):
- """
- Check if the dynamic library should be included with application or not.
- """
- # For configuration phase we need to have exclude / include lists None so these checking is skipped and library gets
- # included.
- if exclude_list:
- if exclude_list.search(libname) and not include_list.search(libname):
- # Library is excluded and is not overriden by include list. It should be then excluded.
- return False
- else:
- # Include library.
- return True
- else:
- # By default include library.
- return True
- # Patterns for suppressing warnings about missing dynamically linked libraries
- _warning_suppressions = [
- # We fail to discover shiboken2 (PySide2) and shiboken6 (PySide6) shared libraries due to the way the packages set
- # up the search path to the library, which is located in a separate package. Suppress the harmless warnings to avoid
- # confusion.
- r'(lib)?shiboken.*',
- ]
- # On some systems (e.g., openwrt), libc.so might point to ldd. Suppress warnings about it.
- if compat.is_linux:
- _warning_suppressions.append(r'ldd')
- # Suppress false warnings on win 10 and UCRT (see issue #1566).
- if compat.is_win_10:
- _warning_suppressions.append(r'api-ms-win-crt.*')
- _warning_suppressions.append(r'api-ms-win-core.*')
- class MissingLibWarningSuppressionList:
- def __init__(self):
- self.regex = re.compile('|'.join(_warning_suppressions), re.I)
- def search(self, libname):
- # Running re.search() on '' regex never returns None.
- if _warning_suppressions:
- return self.regex.match(os.path.basename(libname))
- else:
- return False
- missing_lib_warning_suppression_list = MissingLibWarningSuppressionList()
- def warn_missing_lib(libname):
- """
- Check if a missing-library warning should be displayed for the given library name (or full path).
- """
- return not missing_lib_warning_suppression_list.search(libname)
- def mac_set_relative_dylib_deps(libname, distname):
- """
- On Mac OS set relative paths to dynamic library dependencies of `libname`.
- Relative paths allow to avoid using environment variable DYLD_LIBRARY_PATH. There are known some issues with
- DYLD_LIBRARY_PATH. Relative paths is more flexible mechanism.
- Current location of dependend libraries is derived from the location of the library path (paths start with
- '@loader_path').
- 'distname' path of the library relative to dist directory of frozen executable. We need this to determine the level
- of directory level for @loader_path of binaries not found in dist directory.
- For example, Qt5 plugins are not in the same directory as Qt*.dylib files. Without using
- '@loader_path/../..' for Qt plugins, Mac OS would not be able to resolve shared library dependencies,
- and Qt plugins will not be loaded.
- """
- from macholib import util
- from macholib.MachO import MachO
- # Ignore bootloader; otherwise PyInstaller fails with exception like
- # 'ValueError: total_size > low_offset (288 > 0)'
- if os.path.basename(libname) in _BOOTLOADER_FNAMES:
- return
- # Determine how many directories up ('../') is the directory with shared dynamic libraries.
- # E.g., ./qt4_plugins/images/ -> ./../../
- parent_dir = ''
- # Check if distname is not only base filename.
- if os.path.dirname(distname):
- parent_level = len(os.path.dirname(distname).split(os.sep))
- parent_dir = parent_level * (os.pardir + os.sep)
- def match_func(pth):
- """
- For system libraries is still used absolute path. It is unchanged.
- """
- # Leave system dynamic libraries unchanged.
- if util.in_system_path(pth):
- return None
- # The older python.org builds that use system Tcl/Tk framework have their _tkinter.cpython-*-darwin.so
- # library linked against /Library/Frameworks/Tcl.framework/Versions/8.5/Tcl and
- # /Library/Frameworks/Tk.framework/Versions/8.5/Tk, although the actual frameworks are located in
- # /System/Library/Frameworks. Therefore, they slip through the above in_system_path() check, and we need to
- # exempt them manually.
- _exemptions = [
- '/Library/Frameworks/Tcl.framework/',
- '/Library/Frameworks/Tk.framework/',
- ]
- if any([x in pth for x in _exemptions]):
- return None
- # Use relative path to dependent dynamic libraries based on the location of the executable.
- return os.path.join('@loader_path', parent_dir, os.path.basename(pth))
- # Rewrite mach headers with @loader_path.
- dll = MachO(libname)
- dll.rewriteLoadCommands(match_func)
- # Write changes into file. Write code is based on macholib example.
- try:
- with open(dll.filename, 'rb+') as f:
- for header in dll.headers:
- f.seek(0)
- dll.write(f)
- f.seek(0, 2)
- f.flush()
- except Exception:
- pass
|