dylib.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2013-2021, PyInstaller Development Team.
  3. #
  4. # Distributed under the terms of the GNU General Public License (version 2
  5. # or later) with exception for distributing the bootloader.
  6. #
  7. # The full license is in the file COPYING.txt, distributed with this software.
  8. #
  9. # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
  10. #-----------------------------------------------------------------------------
  11. """
  12. Manipulating with dynamic libraries.
  13. """
  14. import os.path
  15. from PyInstaller.utils.win32 import winutils
  16. __all__ = ['exclude_list', 'include_list', 'include_library']
  17. import os
  18. import re
  19. import PyInstaller.log as logging
  20. from PyInstaller import compat
  21. logger = logging.getLogger(__name__)
  22. _BOOTLOADER_FNAMES = {'run', 'run_d', 'runw', 'runw_d'}
  23. # Ignoring some system libraries speeds up packaging process
  24. _excludes = {
  25. # Ignore annoying warnings with Windows system DLLs.
  26. #
  27. # 'W: library kernel32.dll required via ctypes not found'
  28. # 'W: library coredll.dll required via ctypes not found'
  29. #
  30. # These these dlls has to be ignored for all operating systems because they might be resolved when scanning code for
  31. # ctypes dependencies.
  32. r'advapi32\.dll',
  33. r'ws2_32\.dll',
  34. r'gdi32\.dll',
  35. r'oleaut32\.dll',
  36. r'shell32\.dll',
  37. r'ole32\.dll',
  38. r'coredll\.dll',
  39. r'crypt32\.dll',
  40. r'kernel32',
  41. r'kernel32\.dll',
  42. r'msvcrt\.dll',
  43. r'rpcrt4\.dll',
  44. r'user32\.dll',
  45. # Some modules tries to import the Python library. e.g. pyreadline.console.console
  46. r'python\%s\%s',
  47. }
  48. # Regex includes - overrides excludes. Include list is used only to override specific libraries from exclude list.
  49. _includes = set()
  50. _win_includes = {
  51. # DLLs are from 'Microsoft Visual C++ 2010 Redistributable Package'
  52. # http://msdn.microsoft.com/en-us/library/8kche8ah(v=vs.100).aspx
  53. #
  54. # Python 3.3 and 3.4 use Visual Studio C++ 2010 for Windows builds; python33.dll depends on msvcr100.dll.
  55. #
  56. # Visual Studio C++ 2010 does not need Assembly manifests anymore and uses C++ runtime libraries the old way -
  57. # pointing to C:\Windows\System32. It is necessary to allow inclusion of these libraries from C:\Windows\System32.
  58. r'atl100\.dll',
  59. r'msvcr100\.dll',
  60. r'msvcp100\.dll',
  61. r'mfc100\.dll',
  62. r'mfc100u\.dll',
  63. r'mfcmifc80\.dll',
  64. r'mfcm100\.dll',
  65. r'mfcm100u\.dll',
  66. # Python 3.5 uses the Univeral C Runtime which consists of these DLLs:
  67. r'api-ms-win-core.*',
  68. r'api-ms-win-crt.*',
  69. r'ucrtbase\.dll',
  70. r'vcruntime140\.dll',
  71. # Additional DLLs from VC 2015/2017/2019 runtime. Allow these to be collected to avoid missing-DLL errors when the
  72. # target machine does not have the VC redistributable installed.
  73. r'msvcp140\.dll',
  74. r'msvcp140_1\.dll',
  75. r'msvcp140_2\.dll',
  76. r'vcruntime140_1\.dll',
  77. r'vcomp140\.dll',
  78. r'concrt140\.dll',
  79. # Allow pythonNN.dll, pythoncomNN.dll, pywintypesNN.dll
  80. r'py(?:thon(?:com(?:loader)?)?|wintypes)\d+\.dll',
  81. }
  82. _win_excludes = {
  83. # On Windows, only .dll files can be loaded.
  84. r'.*\.so',
  85. r'.*\.dylib',
  86. # MS assembly excludes
  87. r'Microsoft\.Windows\.Common-Controls',
  88. }
  89. _unix_excludes = {
  90. r'libc\.so(\..*)?',
  91. r'libdl\.so(\..*)?',
  92. r'libm\.so(\..*)?',
  93. r'libpthread\.so(\..*)?',
  94. r'librt\.so(\..*)?',
  95. r'libthread_db\.so(\..*)?',
  96. # glibc regex excludes.
  97. r'ld-linux\.so(\..*)?',
  98. r'libBrokenLocale\.so(\..*)?',
  99. r'libanl\.so(\..*)?',
  100. r'libcidn\.so(\..*)?',
  101. r'libcrypt\.so(\..*)?',
  102. r'libnsl\.so(\..*)?',
  103. r'libnss_compat.*\.so(\..*)?',
  104. r'libnss_dns.*\.so(\..*)?',
  105. r'libnss_files.*\.so(\..*)?',
  106. r'libnss_hesiod.*\.so(\..*)?',
  107. r'libnss_nis.*\.so(\..*)?',
  108. r'libnss_nisplus.*\.so(\..*)?',
  109. r'libresolv\.so(\..*)?',
  110. r'libutil\.so(\..*)?',
  111. # graphical interface libraries come with graphical stack (see libglvnd)
  112. r'libE?(Open)?GLX?(ESv1_CM|ESv2)?(dispatch)?\.so(\..*)?',
  113. r'libdrm\.so(\..*)?',
  114. # libxcb-dri changes ABI frequently (e.g.: between Ubuntu LTS releases) and is usually installed as dependency of
  115. # the graphics stack anyway. No need to bundle it.
  116. r'libxcb\.so(\..*)?',
  117. r'libxcb-dri.*\.so(\..*)?',
  118. }
  119. _aix_excludes = {
  120. r'libbz2\.a',
  121. r'libc\.a',
  122. r'libC\.a',
  123. r'libcrypt\.a',
  124. r'libdl\.a',
  125. r'libintl\.a',
  126. r'libpthreads\.a',
  127. r'librt\\.a',
  128. r'librtl\.a',
  129. r'libz\.a',
  130. }
  131. if compat.is_win:
  132. _includes |= _win_includes
  133. _excludes |= _win_excludes
  134. elif compat.is_aix:
  135. # The exclude list for AIX differs from other *nix platforms.
  136. _excludes |= _aix_excludes
  137. elif compat.is_unix:
  138. # Common excludes for *nix platforms -- except AIX.
  139. _excludes |= _unix_excludes
  140. class ExcludeList(object):
  141. def __init__(self):
  142. self.regex = re.compile('|'.join(_excludes), re.I)
  143. def search(self, libname):
  144. # Running re.search() on '' regex never returns None.
  145. if _excludes:
  146. return self.regex.match(os.path.basename(libname))
  147. else:
  148. return False
  149. class IncludeList(object):
  150. def __init__(self):
  151. self.regex = re.compile('|'.join(_includes), re.I)
  152. def search(self, libname):
  153. # Running re.search() on '' regex never returns None.
  154. if _includes:
  155. return self.regex.match(os.path.basename(libname))
  156. else:
  157. return False
  158. exclude_list = ExcludeList()
  159. include_list = IncludeList()
  160. if compat.is_darwin:
  161. # On Mac use macholib to decide if a binary is a system one.
  162. from macholib import util
  163. class MacExcludeList(object):
  164. def __init__(self, global_exclude_list):
  165. # Wraps the global 'exclude_list' before it is overridden by this class.
  166. self._exclude_list = global_exclude_list
  167. def search(self, libname):
  168. # First try global exclude list. If it matches, return its result; otherwise continue with other check.
  169. result = self._exclude_list.search(libname)
  170. if result:
  171. return result
  172. else:
  173. return util.in_system_path(libname)
  174. exclude_list = MacExcludeList(exclude_list)
  175. elif compat.is_win:
  176. class WinExcludeList(object):
  177. def __init__(self, global_exclude_list):
  178. self._exclude_list = global_exclude_list
  179. # use normpath because msys2 uses / instead of \
  180. self._windows_dir = os.path.normpath(winutils.get_windows_dir().lower())
  181. def search(self, libname):
  182. libname = libname.lower()
  183. result = self._exclude_list.search(libname)
  184. if result:
  185. return result
  186. else:
  187. # Exclude everything from the Windows directory by default.
  188. # .. sometimes realpath changes the case of libname, lower it
  189. # .. use normpath because msys2 uses / instead of \
  190. fn = os.path.normpath(os.path.realpath(libname).lower())
  191. return fn.startswith(self._windows_dir)
  192. exclude_list = WinExcludeList(exclude_list)
  193. def include_library(libname):
  194. """
  195. Check if the dynamic library should be included with application or not.
  196. """
  197. # For configuration phase we need to have exclude / include lists None so these checking is skipped and library gets
  198. # included.
  199. if exclude_list:
  200. if exclude_list.search(libname) and not include_list.search(libname):
  201. # Library is excluded and is not overriden by include list. It should be then excluded.
  202. return False
  203. else:
  204. # Include library.
  205. return True
  206. else:
  207. # By default include library.
  208. return True
  209. # Patterns for suppressing warnings about missing dynamically linked libraries
  210. _warning_suppressions = [
  211. # We fail to discover shiboken2 (PySide2) and shiboken6 (PySide6) shared libraries due to the way the packages set
  212. # up the search path to the library, which is located in a separate package. Suppress the harmless warnings to avoid
  213. # confusion.
  214. r'(lib)?shiboken.*',
  215. ]
  216. # On some systems (e.g., openwrt), libc.so might point to ldd. Suppress warnings about it.
  217. if compat.is_linux:
  218. _warning_suppressions.append(r'ldd')
  219. # Suppress false warnings on win 10 and UCRT (see issue #1566).
  220. if compat.is_win_10:
  221. _warning_suppressions.append(r'api-ms-win-crt.*')
  222. _warning_suppressions.append(r'api-ms-win-core.*')
  223. class MissingLibWarningSuppressionList:
  224. def __init__(self):
  225. self.regex = re.compile('|'.join(_warning_suppressions), re.I)
  226. def search(self, libname):
  227. # Running re.search() on '' regex never returns None.
  228. if _warning_suppressions:
  229. return self.regex.match(os.path.basename(libname))
  230. else:
  231. return False
  232. missing_lib_warning_suppression_list = MissingLibWarningSuppressionList()
  233. def warn_missing_lib(libname):
  234. """
  235. Check if a missing-library warning should be displayed for the given library name (or full path).
  236. """
  237. return not missing_lib_warning_suppression_list.search(libname)
  238. def mac_set_relative_dylib_deps(libname, distname):
  239. """
  240. On Mac OS set relative paths to dynamic library dependencies of `libname`.
  241. Relative paths allow to avoid using environment variable DYLD_LIBRARY_PATH. There are known some issues with
  242. DYLD_LIBRARY_PATH. Relative paths is more flexible mechanism.
  243. Current location of dependend libraries is derived from the location of the library path (paths start with
  244. '@loader_path').
  245. 'distname' path of the library relative to dist directory of frozen executable. We need this to determine the level
  246. of directory level for @loader_path of binaries not found in dist directory.
  247. For example, Qt5 plugins are not in the same directory as Qt*.dylib files. Without using
  248. '@loader_path/../..' for Qt plugins, Mac OS would not be able to resolve shared library dependencies,
  249. and Qt plugins will not be loaded.
  250. """
  251. from macholib import util
  252. from macholib.MachO import MachO
  253. # Ignore bootloader; otherwise PyInstaller fails with exception like
  254. # 'ValueError: total_size > low_offset (288 > 0)'
  255. if os.path.basename(libname) in _BOOTLOADER_FNAMES:
  256. return
  257. # Determine how many directories up ('../') is the directory with shared dynamic libraries.
  258. # E.g., ./qt4_plugins/images/ -> ./../../
  259. parent_dir = ''
  260. # Check if distname is not only base filename.
  261. if os.path.dirname(distname):
  262. parent_level = len(os.path.dirname(distname).split(os.sep))
  263. parent_dir = parent_level * (os.pardir + os.sep)
  264. def match_func(pth):
  265. """
  266. For system libraries is still used absolute path. It is unchanged.
  267. """
  268. # Leave system dynamic libraries unchanged.
  269. if util.in_system_path(pth):
  270. return None
  271. # The older python.org builds that use system Tcl/Tk framework have their _tkinter.cpython-*-darwin.so
  272. # library linked against /Library/Frameworks/Tcl.framework/Versions/8.5/Tcl and
  273. # /Library/Frameworks/Tk.framework/Versions/8.5/Tk, although the actual frameworks are located in
  274. # /System/Library/Frameworks. Therefore, they slip through the above in_system_path() check, and we need to
  275. # exempt them manually.
  276. _exemptions = [
  277. '/Library/Frameworks/Tcl.framework/',
  278. '/Library/Frameworks/Tk.framework/',
  279. ]
  280. if any([x in pth for x in _exemptions]):
  281. return None
  282. # Use relative path to dependent dynamic libraries based on the location of the executable.
  283. return os.path.join('@loader_path', parent_dir, os.path.basename(pth))
  284. # Rewrite mach headers with @loader_path.
  285. dll = MachO(libname)
  286. dll.rewriteLoadCommands(match_func)
  287. # Write changes into file. Write code is based on macholib example.
  288. try:
  289. with open(dll.filename, 'rb+') as f:
  290. for header in dll.headers:
  291. f.seek(0)
  292. dll.write(f)
  293. f.seek(0, 2)
  294. f.flush()
  295. except Exception:
  296. pass