utils.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2005-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. #--- functions for checking guts ---
  12. # NOTE: By GUTS it is meant intermediate files and data structures that
  13. # PyInstaller creates for bundling files and creating final executable.
  14. import fnmatch
  15. import glob
  16. import hashlib
  17. import os
  18. import os.path
  19. import pkgutil
  20. import platform
  21. import shutil
  22. import sys
  23. import struct
  24. from PyInstaller.config import CONF
  25. from PyInstaller import compat
  26. from PyInstaller.compat import is_darwin, is_win, EXTENSION_SUFFIXES, \
  27. is_py37, is_cygwin
  28. from PyInstaller.depend import dylib
  29. from PyInstaller.depend.bindepend import match_binding_redirect
  30. from PyInstaller.utils import misc
  31. from PyInstaller import log as logging
  32. if is_win:
  33. from PyInstaller.utils.win32 import winmanifest, winresource, versioninfo
  34. if is_darwin:
  35. import PyInstaller.utils.osx as osxutils
  36. logger = logging.getLogger(__name__)
  37. #-- Helpers for checking guts.
  38. #
  39. # NOTE: By _GUTS it is meant intermediate files and data structures that
  40. # PyInstaller creates for bundling files and creating final executable.
  41. def _check_guts_eq(attr, old, new, last_build):
  42. """
  43. rebuild is required if values differ
  44. """
  45. if old != new:
  46. logger.info("Building because %s changed", attr)
  47. return True
  48. return False
  49. def _check_guts_toc_mtime(attr, old, toc, last_build, pyc=0):
  50. """
  51. rebuild is required if mtimes of files listed in old toc are newer
  52. than last_build
  53. if pyc=1, check for .py files, too
  54. Use this for calculated/analysed values read from cache.
  55. """
  56. for (nm, fnm, typ) in old:
  57. if misc.mtime(fnm) > last_build:
  58. logger.info("Building because %s changed", fnm)
  59. return True
  60. elif pyc and misc.mtime(fnm[:-1]) > last_build:
  61. logger.info("Building because %s changed", fnm[:-1])
  62. return True
  63. return False
  64. def _check_guts_toc(attr, old, toc, last_build, pyc=0):
  65. """
  66. rebuild is required if either toc content changed or mtimes of
  67. files listed in old toc are newer than last_build
  68. if pyc=1, check for .py files, too
  69. Use this for input parameters.
  70. """
  71. return (_check_guts_eq(attr, old, toc, last_build)
  72. or _check_guts_toc_mtime(attr, old, toc, last_build, pyc=pyc))
  73. #---
  74. def add_suffix_to_extension(inm, fnm, typ):
  75. """
  76. Take a TOC entry (inm, fnm, typ) and adjust the inm for EXTENSION
  77. or DEPENDENCY to include the full library suffix.
  78. """
  79. if typ == 'EXTENSION':
  80. if fnm.endswith(inm):
  81. # If inm completely fits into end of the fnm, it has
  82. # already been processed
  83. return inm, fnm, typ
  84. # Change the dotted name into a relative path. This places C
  85. # extensions in the Python-standard location.
  86. inm = inm.replace('.', os.sep)
  87. # In some rare cases extension might already contain a suffix.
  88. # Skip it in this case.
  89. if os.path.splitext(inm)[1] not in EXTENSION_SUFFIXES:
  90. # Determine the base name of the file.
  91. base_name = os.path.basename(inm)
  92. assert '.' not in base_name
  93. # Use this file's existing extension. For extensions such as
  94. # ``libzmq.cp36-win_amd64.pyd``, we can't use
  95. # ``os.path.splitext``, which would give only the ```.pyd`` part
  96. # of the extension.
  97. inm = inm + os.path.basename(fnm)[len(base_name):]
  98. elif typ == 'DEPENDENCY':
  99. # Use the suffix from the filename.
  100. # TODO Verify what extensions are by DEPENDENCIES.
  101. binext = os.path.splitext(fnm)[1]
  102. if not os.path.splitext(inm)[1] == binext:
  103. inm = inm + binext
  104. return inm, fnm, typ
  105. def applyRedirects(manifest, redirects):
  106. """
  107. Apply the binding redirects specified by 'redirects' to the dependent assemblies
  108. of 'manifest'.
  109. :param manifest:
  110. :type manifest:
  111. :param redirects:
  112. :type redirects:
  113. :return:
  114. :rtype:
  115. """
  116. redirecting = False
  117. for binding in redirects:
  118. for dep in manifest.dependentAssemblies:
  119. if match_binding_redirect(dep, binding):
  120. logger.info("Redirecting %s version %s -> %s",
  121. binding.name, dep.version, binding.newVersion)
  122. dep.version = binding.newVersion
  123. redirecting = True
  124. return redirecting
  125. def checkCache(fnm, strip=False, upx=False, upx_exclude=None, dist_nm=None,
  126. target_arch=None, codesign_identity=None,
  127. entitlements_file=None):
  128. """
  129. Cache prevents preprocessing binary files again and again.
  130. 'dist_nm' Filename relative to dist directory. We need it on Mac
  131. to determine level of paths for @loader_path like
  132. '@loader_path/../../' for qt4 plugins.
  133. """
  134. from PyInstaller.config import CONF
  135. # On darwin a cache is required anyway to keep the libaries
  136. # with relative install names. Caching on darwin does not work
  137. # since we need to modify binary headers to use relative paths
  138. # to dll depencies and starting with '@loader_path'.
  139. if not strip and not upx and not is_darwin and not is_win:
  140. return fnm
  141. if dist_nm is not None and ":" in dist_nm:
  142. # A file embedded in another pyinstaller build via multipackage
  143. # No actual file exists to process
  144. return fnm
  145. if strip:
  146. strip = True
  147. else:
  148. strip = False
  149. upx_exclude = upx_exclude or []
  150. upx = (upx and (is_win or is_cygwin) and
  151. os.path.normcase(os.path.basename(fnm)) not in upx_exclude)
  152. # Load cache index
  153. # Make cachedir per Python major/minor version.
  154. # This allows parallel building of executables with different
  155. # Python versions as one user.
  156. pyver = ('py%d%s') % (sys.version_info[0], sys.version_info[1])
  157. arch = platform.architecture()[0]
  158. cachedir = os.path.join(CONF['cachedir'], 'bincache%d%d_%s_%s' % (strip, upx, pyver, arch))
  159. if target_arch:
  160. cachedir = os.path.join(cachedir, target_arch)
  161. if is_darwin:
  162. # Separate by codesign identity
  163. if codesign_identity:
  164. # Compute hex digest of codesign identity string to prevent
  165. # issues with invalid characters.
  166. csi_hash = hashlib.sha256(codesign_identity.encode('utf-8'))
  167. cachedir = os.path.join(cachedir, csi_hash.hexdigest())
  168. else:
  169. cachedir = os.path.join(cachedir, 'adhoc') # ad-hoc signing
  170. # Separate by entitlements
  171. if entitlements_file:
  172. # Compute hex digest of entitlements file contents
  173. with open(entitlements_file, 'rb') as fp:
  174. ef_hash = hashlib.sha256(fp.read())
  175. cachedir = os.path.join(cachedir, ef_hash.hexdigest())
  176. else:
  177. cachedir = os.path.join(cachedir, 'no-entitlements')
  178. if not os.path.exists(cachedir):
  179. os.makedirs(cachedir)
  180. cacheindexfn = os.path.join(cachedir, "index.dat")
  181. if os.path.exists(cacheindexfn):
  182. try:
  183. cache_index = misc.load_py_data_struct(cacheindexfn)
  184. except Exception as e:
  185. # tell the user they may want to fix their cache
  186. # .. however, don't delete it for them; if it keeps getting
  187. # corrupted, we'll never find out
  188. logger.warn("pyinstaller bincache may be corrupted; "
  189. "use pyinstaller --clean to fix")
  190. raise
  191. else:
  192. cache_index = {}
  193. # Verify if the file we're looking for is present in the cache.
  194. # Use the dist_mn if given to avoid different extension modules
  195. # sharing the same basename get corrupted.
  196. if dist_nm:
  197. basenm = os.path.normcase(dist_nm)
  198. else:
  199. basenm = os.path.normcase(os.path.basename(fnm))
  200. # Binding redirects should be taken into account to see if the file
  201. # needs to be reprocessed. The redirects may change if the versions of dependent
  202. # manifests change due to system updates.
  203. redirects = CONF.get('binding_redirects', [])
  204. digest = cacheDigest(fnm, redirects)
  205. cachedfile = os.path.join(cachedir, basenm)
  206. cmd = None
  207. if basenm in cache_index:
  208. if digest != cache_index[basenm]:
  209. os.remove(cachedfile)
  210. else:
  211. return cachedfile
  212. # Optionally change manifest and its deps to private assemblies
  213. if fnm.lower().endswith(".manifest"):
  214. manifest = winmanifest.Manifest()
  215. manifest.filename = fnm
  216. with open(fnm, "rb") as f:
  217. manifest.parse_string(f.read())
  218. if CONF.get('win_private_assemblies', False):
  219. if manifest.publicKeyToken:
  220. logger.info("Changing %s into private assembly", os.path.basename(fnm))
  221. manifest.publicKeyToken = None
  222. for dep in manifest.dependentAssemblies:
  223. # Exclude common-controls which is not bundled
  224. if dep.name != "Microsoft.Windows.Common-Controls":
  225. dep.publicKeyToken = None
  226. applyRedirects(manifest, redirects)
  227. manifest.writeprettyxml(cachedfile)
  228. return cachedfile
  229. if upx:
  230. if strip:
  231. fnm = checkCache(fnm, strip=True, upx=False, dist_nm=dist_nm,
  232. target_arch=target_arch,
  233. codesign_identity=codesign_identity,
  234. entitlements_file=entitlements_file)
  235. # We meed to avoid using UPX with Windows DLLs that have Control
  236. # Flow Guard enabled, as it breaks them.
  237. if is_win and versioninfo.pefile_check_control_flow_guard(fnm):
  238. logger.info('Disabling UPX for %s due to CFG!', fnm)
  239. elif misc.is_file_qt_plugin(fnm):
  240. logger.info('Disabling UPX for %s due to it being a Qt plugin!',
  241. fnm)
  242. else:
  243. bestopt = "--best"
  244. # FIXME: Linux builds of UPX do not seem to contain LZMA
  245. # (they assert out). A better configure-time check is due.
  246. if CONF["hasUPX"] >= (3,) and os.name == "nt":
  247. bestopt = "--lzma"
  248. upx_executable = "upx"
  249. if CONF.get('upx_dir'):
  250. upx_executable = os.path.join(CONF['upx_dir'], upx_executable)
  251. cmd = [upx_executable, bestopt, "-q", cachedfile]
  252. else:
  253. if strip:
  254. strip_options = []
  255. if is_darwin:
  256. # The default strip behaviour breaks some shared libraries
  257. # under Mac OSX.
  258. # -S = strip only debug symbols.
  259. strip_options = ["-S"]
  260. cmd = ["strip"] + strip_options + [cachedfile]
  261. if not os.path.exists(os.path.dirname(cachedfile)):
  262. os.makedirs(os.path.dirname(cachedfile))
  263. # There are known some issues with 'shutil.copy2' on Mac OS X 10.11
  264. # with copying st_flags. Issue #1650.
  265. # 'shutil.copy' copies also permission bits and it should be sufficient for
  266. # PyInstalle purposes.
  267. shutil.copy(fnm, cachedfile)
  268. # TODO find out if this is still necessary when no longer using shutil.copy2()
  269. if hasattr(os, 'chflags'):
  270. # Some libraries on FreeBSD have immunable flag (libthr.so.3, for example)
  271. # If flags still remains, os.chmod will failed with:
  272. # OSError: [Errno 1] Operation not permitted.
  273. try:
  274. os.chflags(cachedfile, 0)
  275. except OSError:
  276. pass
  277. os.chmod(cachedfile, 0o755)
  278. if os.path.splitext(fnm.lower())[1] in (".pyd", ".dll"):
  279. # When shared assemblies are bundled into the app, they may optionally be
  280. # changed into private assemblies.
  281. try:
  282. res = winmanifest.GetManifestResources(os.path.abspath(cachedfile))
  283. except winresource.pywintypes.error as e:
  284. if e.args[0] == winresource.ERROR_BAD_EXE_FORMAT:
  285. # Not a win32 PE file
  286. pass
  287. else:
  288. logger.error(os.path.abspath(cachedfile))
  289. raise
  290. else:
  291. if winmanifest.RT_MANIFEST in res and len(res[winmanifest.RT_MANIFEST]):
  292. for name in res[winmanifest.RT_MANIFEST]:
  293. for language in res[winmanifest.RT_MANIFEST][name]:
  294. try:
  295. manifest = winmanifest.Manifest()
  296. manifest.filename = ":".join([cachedfile,
  297. str(winmanifest.RT_MANIFEST),
  298. str(name),
  299. str(language)])
  300. manifest.parse_string(res[winmanifest.RT_MANIFEST][name][language],
  301. False)
  302. except Exception as exc:
  303. logger.error("Cannot parse manifest resource %s, "
  304. "%s", name, language)
  305. logger.error("From file %s", cachedfile, exc_info=1)
  306. else:
  307. # optionally change manifest to private assembly
  308. private = CONF.get('win_private_assemblies', False)
  309. if private:
  310. if manifest.publicKeyToken:
  311. logger.info("Changing %s into a private assembly",
  312. os.path.basename(fnm))
  313. manifest.publicKeyToken = None
  314. # Change dep to private assembly
  315. for dep in manifest.dependentAssemblies:
  316. # Exclude common-controls which is not bundled
  317. if dep.name != "Microsoft.Windows.Common-Controls":
  318. dep.publicKeyToken = None
  319. redirecting = applyRedirects(manifest, redirects)
  320. if redirecting or private:
  321. try:
  322. manifest.update_resources(os.path.abspath(cachedfile),
  323. [name],
  324. [language])
  325. except Exception as e:
  326. logger.error(os.path.abspath(cachedfile))
  327. raise
  328. if cmd:
  329. logger.info("Executing - " + ' '.join(cmd))
  330. # terminates if execution fails
  331. compat.exec_command(*cmd)
  332. # update cache index
  333. cache_index[basenm] = digest
  334. misc.save_py_data_struct(cacheindexfn, cache_index)
  335. # On Mac OS X we need relative paths to dll dependencies
  336. # starting with @executable_path. Modifying headers invalidates
  337. # signatures, so remove any existing signature and then re-add
  338. # it after paths are rewritten.
  339. if is_darwin:
  340. osxutils.binary_to_target_arch(cachedfile, target_arch,
  341. display_name=fnm)
  342. osxutils.remove_signature_from_binary(cachedfile)
  343. dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm)
  344. osxutils.sign_binary(cachedfile, codesign_identity, entitlements_file)
  345. return cachedfile
  346. def cacheDigest(fnm, redirects):
  347. hasher = hashlib.md5()
  348. with open(fnm, "rb") as f:
  349. for chunk in iter(lambda: f.read(16 * 1024), b""):
  350. hasher.update(chunk)
  351. if redirects:
  352. redirects = str(redirects).encode('utf-8')
  353. hasher.update(redirects)
  354. digest = bytearray(hasher.digest())
  355. return digest
  356. def _check_path_overlap(path):
  357. """
  358. Check that path does not overlap with WORKPATH or SPECPATH (i.e.
  359. WORKPATH and SPECPATH may not start with path, which could be
  360. caused by a faulty hand-edited specfile)
  361. Raise SystemExit if there is overlap, return True otherwise
  362. """
  363. from PyInstaller.config import CONF
  364. specerr = 0
  365. if CONF['workpath'].startswith(path):
  366. logger.error('Specfile error: The output path "%s" contains '
  367. 'WORKPATH (%s)', path, CONF['workpath'])
  368. specerr += 1
  369. if CONF['specpath'].startswith(path):
  370. logger.error('Specfile error: The output path "%s" contains '
  371. 'SPECPATH (%s)', path, CONF['specpath'])
  372. specerr += 1
  373. if specerr:
  374. raise SystemExit('Error: Please edit/recreate the specfile (%s) '
  375. 'and set a different output name (e.g. "dist").'
  376. % CONF['spec'])
  377. return True
  378. def _make_clean_directory(path):
  379. """
  380. Create a clean directory from the given directory name
  381. """
  382. if _check_path_overlap(path):
  383. if os.path.isdir(path) or os.path.isfile(path):
  384. try:
  385. os.remove(path)
  386. except OSError:
  387. _rmtree(path)
  388. os.makedirs(path, exist_ok=True)
  389. def _rmtree(path):
  390. """
  391. Remove directory and all its contents, but only after user confirmation,
  392. or if the -y option is set
  393. """
  394. from PyInstaller.config import CONF
  395. if CONF['noconfirm']:
  396. choice = 'y'
  397. elif sys.stdout.isatty():
  398. choice = compat.stdin_input('WARNING: The output directory "%s" and ALL ITS '
  399. 'CONTENTS will be REMOVED! Continue? (y/N)' % path)
  400. else:
  401. raise SystemExit('Error: The output directory "%s" is not empty. '
  402. 'Please remove all its contents or use the '
  403. '-y option (remove output directory without '
  404. 'confirmation).' % path)
  405. if choice.strip().lower() == 'y':
  406. if not CONF['noconfirm']:
  407. print("On your own risk, you can use the option `--noconfirm` "
  408. "to get rid of this question.")
  409. logger.info('Removing dir %s', path)
  410. shutil.rmtree(path)
  411. else:
  412. raise SystemExit('User aborted')
  413. # TODO Refactor to prohibit empty target directories. As the docstring
  414. #below documents, this function currently permits the second item of each
  415. #2-tuple in "hook.datas" to be the empty string, in which case the target
  416. #directory defaults to the source directory's basename. However, this
  417. #functionality is very fragile and hence bad. Instead:
  418. #
  419. #* An exception should be raised if such item is empty.
  420. #* All hooks currently passing the empty string for such item (e.g.,
  421. # "hooks/hook-babel.py", "hooks/hook-matplotlib.py") should be refactored
  422. # to instead pass such basename.
  423. def format_binaries_and_datas(binaries_or_datas, workingdir=None):
  424. """
  425. Convert the passed list of hook-style 2-tuples into a returned set of
  426. `TOC`-style 2-tuples.
  427. Elements of the passed list are 2-tuples `(source_dir_or_glob, target_dir)`.
  428. Elements of the returned set are 2-tuples `(target_file, source_file)`.
  429. For backwards compatibility, the order of elements in the former tuples are
  430. the reverse of the order of elements in the latter tuples!
  431. Parameters
  432. ----------
  433. binaries_or_datas : list
  434. List of hook-style 2-tuples (e.g., the top-level `binaries` and `datas`
  435. attributes defined by hooks) whose:
  436. * The first element is either:
  437. * A glob matching only the absolute or relative paths of source
  438. non-Python data files.
  439. * The absolute or relative path of a source directory containing only
  440. source non-Python data files.
  441. * The second element ist he relative path of the target directory
  442. into which these source files will be recursively copied.
  443. If the optional `workingdir` parameter is passed, source paths may be
  444. either absolute or relative; else, source paths _must_ be absolute.
  445. workingdir : str
  446. Optional absolute path of the directory to which all relative source
  447. paths in the `binaries_or_datas` parameter will be prepended by (and
  448. hence converted into absolute paths) _or_ `None` if these paths are to
  449. be preserved as relative. Defaults to `None`.
  450. Returns
  451. ----------
  452. set
  453. Set of `TOC`-style 2-tuples whose:
  454. * First element is the absolute or relative path of a target file.
  455. * Second element is the absolute or relative path of the corresponding
  456. source file to be copied to this target file.
  457. """
  458. toc_datas = set()
  459. for src_root_path_or_glob, trg_root_dir in binaries_or_datas:
  460. if not trg_root_dir:
  461. raise SystemExit("Empty DEST not allowed when adding binary "
  462. "and data files. "
  463. "Maybe you want to used %r.\nCaused by %r." %
  464. (os.curdir, src_root_path_or_glob))
  465. # Convert relative to absolute paths if required.
  466. if workingdir and not os.path.isabs(src_root_path_or_glob):
  467. src_root_path_or_glob = os.path.join(
  468. workingdir, src_root_path_or_glob)
  469. # Normalize paths.
  470. src_root_path_or_glob = os.path.normpath(src_root_path_or_glob)
  471. if os.path.isfile(src_root_path_or_glob):
  472. src_root_paths = [src_root_path_or_glob]
  473. else:
  474. # List of the absolute paths of all source paths matching the
  475. # current glob.
  476. src_root_paths = glob.glob(src_root_path_or_glob)
  477. if not src_root_paths:
  478. msg = 'Unable to find "%s" when adding binary and data files.' % (
  479. src_root_path_or_glob)
  480. # on Debian/Ubuntu, missing pyconfig.h files can be fixed with
  481. # installing python-dev
  482. if src_root_path_or_glob.endswith("pyconfig.h"):
  483. msg += """This would mean your Python installation doesn't
  484. come with proper library files. This usually happens by missing development
  485. package, or unsuitable build parameters of Python installation.
  486. * On Debian/Ubuntu, you would need to install Python development packages
  487. * apt-get install python3-dev
  488. * apt-get install python-dev
  489. * If you're building Python by yourself, please rebuild your Python with
  490. `--enable-shared` (or, `--enable-framework` on Darwin)
  491. """
  492. raise SystemExit(msg)
  493. for src_root_path in src_root_paths:
  494. if os.path.isfile(src_root_path):
  495. # Normalizing the result to remove redundant relative
  496. # paths (e.g., removing "./" from "trg/./file").
  497. toc_datas.add((
  498. os.path.normpath(os.path.join(
  499. trg_root_dir, os.path.basename(src_root_path))),
  500. os.path.normpath(src_root_path)))
  501. elif os.path.isdir(src_root_path):
  502. for src_dir, src_subdir_basenames, src_file_basenames in \
  503. os.walk(src_root_path):
  504. # Ensure the current source directory is a subdirectory
  505. # of the passed top-level source directory. Since
  506. # os.walk() does *NOT* follow symlinks by default, this
  507. # should be the case. (But let's make sure.)
  508. assert src_dir.startswith(src_root_path)
  509. # Relative path of the current target directory,
  510. # obtained by:
  511. #
  512. # * Stripping the top-level source directory from the
  513. # current source directory (e.g., removing "/top" from
  514. # "/top/dir").
  515. # * Normalizing the result to remove redundant relative
  516. # paths (e.g., removing "./" from "trg/./file").
  517. trg_dir = os.path.normpath(os.path.join(
  518. trg_root_dir,
  519. os.path.relpath(src_dir, src_root_path)))
  520. for src_file_basename in src_file_basenames:
  521. src_file = os.path.join(src_dir, src_file_basename)
  522. if os.path.isfile(src_file):
  523. # Normalize the result to remove redundant relative
  524. # paths (e.g., removing "./" from "trg/./file").
  525. toc_datas.add((
  526. os.path.normpath(
  527. os.path.join(trg_dir, src_file_basename)),
  528. os.path.normpath(src_file)))
  529. return toc_datas
  530. def _load_code(modname, filename):
  531. path_item = os.path.dirname(filename)
  532. if os.path.basename(filename).startswith('__init__.py'):
  533. # this is a package
  534. path_item = os.path.dirname(path_item)
  535. if os.path.basename(path_item) == '__pycache__':
  536. path_item = os.path.dirname(path_item)
  537. importer = pkgutil.get_importer(path_item)
  538. package, _, modname = modname.rpartition('.')
  539. if hasattr(importer, 'find_loader'):
  540. loader, portions = importer.find_loader(modname)
  541. else:
  542. loader = importer.find_module(modname)
  543. logger.debug('Compiling %s', filename)
  544. if loader and hasattr(loader, 'get_code'):
  545. return loader.get_code(modname)
  546. else:
  547. # Just as ``python foo.bar`` will read and execute statements in
  548. # ``foo.bar``, even though it lacks the ``.py`` extension, so
  549. # ``pyinstaller foo.bar`` should also work. However, Python's import
  550. # machinery doesn't load files without a ``.py`` extension. So, use
  551. # ``compile`` instead.
  552. #
  553. # On a side note, neither the Python 2 nor Python 3 calls to
  554. # ``pkgutil`` and ``find_module`` above handle modules ending in
  555. # ``.pyw``, even though ``imp.find_module`` and ``import <name>`` both
  556. # work. This code supports ``.pyw`` files.
  557. # Open the source file in binary mode and allow the `compile()` call to
  558. # detect the source encoding.
  559. with open(filename, 'rb') as f:
  560. source = f.read()
  561. return compile(source, filename, 'exec')
  562. def get_code_object(modname, filename):
  563. """
  564. Get the code-object for a module.
  565. This is a extra-simple version for compiling a module. It's
  566. not worth spending more effort here, as it is only used in the
  567. rare case if outXX-Analysis.toc exists, but outXX-PYZ.toc does
  568. not.
  569. """
  570. try:
  571. if filename in ('-', None):
  572. # This is a NamespacePackage, modulegraph marks them
  573. # by using the filename '-'. (But wants to use None,
  574. # so check for None, too, to be forward-compatible.)
  575. logger.debug('Compiling namespace package %s', modname)
  576. txt = '#\n'
  577. return compile(txt, filename, 'exec')
  578. else:
  579. logger.debug('Compiling %s', filename)
  580. co = _load_code(modname, filename)
  581. if not co:
  582. raise ValueError("Module file %s is missing" % filename)
  583. return co
  584. except SyntaxError as e:
  585. print("Syntax error in ", filename)
  586. print(e.args)
  587. raise
  588. def strip_paths_in_code(co, new_filename=None):
  589. # Paths to remove from filenames embedded in code objects
  590. replace_paths = sys.path + CONF['pathex']
  591. # Make sure paths end with os.sep and the longest paths are first
  592. replace_paths = sorted((os.path.join(f, '') for f in replace_paths),
  593. key=len, reverse=True)
  594. if new_filename is None:
  595. original_filename = os.path.normpath(co.co_filename)
  596. for f in replace_paths:
  597. if original_filename.startswith(f):
  598. new_filename = original_filename[len(f):]
  599. break
  600. else:
  601. return co
  602. code_func = type(co)
  603. consts = tuple(
  604. strip_paths_in_code(const_co, new_filename)
  605. if isinstance(const_co, code_func) else const_co
  606. for const_co in co.co_consts
  607. )
  608. if hasattr(co, 'replace'): # is_py38
  609. return co.replace(co_consts=consts, co_filename=new_filename)
  610. elif hasattr(co, 'co_kwonlyargcount'):
  611. # co_kwonlyargcount was added in some version of Python 3
  612. return code_func(co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize,
  613. co.co_flags, co.co_code, consts, co.co_names,
  614. co.co_varnames, new_filename, co.co_name,
  615. co.co_firstlineno, co.co_lnotab,
  616. co.co_freevars, co.co_cellvars)
  617. else:
  618. return code_func(co.co_argcount, co.co_nlocals, co.co_stacksize,
  619. co.co_flags, co.co_code, consts, co.co_names,
  620. co.co_varnames, new_filename, co.co_name,
  621. co.co_firstlineno, co.co_lnotab,
  622. co.co_freevars, co.co_cellvars)
  623. def fake_pyc_timestamp(buf):
  624. """
  625. Reset the timestamp from a .pyc-file header to a fixed value.
  626. This enables deterministic builds without having to set pyinstaller
  627. source metadata (mtime) since that changes the pyc-file contents.
  628. _buf_ must at least contain the full pyc-file header.
  629. """
  630. assert buf[:4] == compat.BYTECODE_MAGIC, \
  631. "Expected pyc magic {}, got {}".format(compat.BYTECODE_MAGIC, buf[:4])
  632. start, end = 4, 8
  633. if is_py37:
  634. # see https://www.python.org/dev/peps/pep-0552/
  635. (flags,) = struct.unpack_from(">I", buf, 4)
  636. if flags & 1:
  637. # We are in the future and hash-based pyc-files are used, so
  638. # clear "check_source" flag, since there is no source
  639. buf[4:8] = struct.pack(">I", flags ^ 2)
  640. return buf
  641. else:
  642. # no hash-based pyc-file, timestamp is the next field
  643. start, end = 8, 12
  644. ts = b'pyi0' # So people know where this comes from
  645. return buf[:start] + ts + buf[end:]
  646. def _should_include_system_binary(binary_tuple, exceptions):
  647. """
  648. Return True if the given binary_tuple describes a system binary that
  649. should be included. Exclude all system library binaries other than
  650. those with "lib-dynload" in the destination or "python" in the source,
  651. except for those matching the patterns in the exceptions list. Intended
  652. to only be used from the Analysis method exclude_system_libraries.
  653. """
  654. dest = binary_tuple[0]
  655. if dest.startswith('lib-dynload'):
  656. return True
  657. src = binary_tuple[1]
  658. if fnmatch.fnmatch(src, '*python*'):
  659. return True
  660. if not src.startswith('/lib') and not src.startswith('/usr/lib'):
  661. return True
  662. for exception in exceptions:
  663. if fnmatch.fnmatch(dest, exception):
  664. return True
  665. return False