makespec.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  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. """
  12. Automatically build spec files containing a description of the project
  13. """
  14. import os
  15. import sys
  16. import argparse
  17. from PyInstaller import HOMEPATH, DEFAULT_SPECPATH
  18. from PyInstaller import log as logging
  19. from PyInstaller.compat import expand_path, is_darwin, is_win
  20. from PyInstaller.building.templates import onefiletmplt, onedirtmplt, \
  21. cipher_absent_template, cipher_init_template, bundleexetmplt, \
  22. bundletmplt, splashtmpl
  23. logger = logging.getLogger(__name__)
  24. add_command_sep = os.pathsep
  25. # This list gives valid choices for the ``--debug`` command-line option, except
  26. # for the ``all`` choice.
  27. DEBUG_ARGUMENT_CHOICES = ['imports', 'bootloader', 'noarchive']
  28. # This is the ``all`` choice.
  29. DEBUG_ALL_CHOICE = ['all']
  30. def quote_win_filepath(path):
  31. # quote all \ with another \ after using normpath to clean up the path
  32. return os.path.normpath(path).replace('\\', '\\\\')
  33. def make_path_spec_relative(filename, spec_dir):
  34. """
  35. Make the filename relative to the directory containing .spec file if filename
  36. is relative and not absolute. Otherwise keep filename untouched.
  37. """
  38. if os.path.isabs(filename):
  39. return filename
  40. else:
  41. filename = os.path.abspath(filename)
  42. # Make it relative.
  43. filename = os.path.relpath(filename, start=spec_dir)
  44. return filename
  45. # Support for trying to avoid hard-coded paths in the .spec files.
  46. # Eg, all files rooted in the Installer directory tree will be
  47. # written using "HOMEPATH", thus allowing this spec file to
  48. # be used with any Installer installation.
  49. # Same thing could be done for other paths too.
  50. path_conversions = (
  51. (HOMEPATH, "HOMEPATH"),
  52. )
  53. def add_data_or_binary(string):
  54. try:
  55. src, dest = string.split(add_command_sep)
  56. except ValueError as e:
  57. # Split into SRC and DEST failed, wrong syntax
  58. raise argparse.ArgumentError(
  59. "Wrong syntax, should be SRC{}DEST".format(add_command_sep)
  60. ) from e
  61. if not src or not dest:
  62. # Syntax was correct, but one or both of SRC and DEST was not given
  63. raise argparse.ArgumentError("You have to specify both SRC and DEST")
  64. # Return tuple containing SRC and SRC
  65. return (src, dest)
  66. def make_variable_path(filename, conversions=path_conversions):
  67. if not os.path.isabs(filename):
  68. # os.path.commonpath can not compare relative and absolute
  69. # paths, and if filename is not absolut, none of the
  70. # paths in conversions will match anyway.
  71. return None, filename
  72. for (from_path, to_name) in conversions:
  73. assert os.path.abspath(from_path) == from_path, (
  74. "path '%s' should already be absolute" % from_path)
  75. try:
  76. common_path = os.path.commonpath([filename, from_path])
  77. except ValueError:
  78. # Per https://docs.python.org/3/library/os.path.html#os.path.commonpath,
  79. # this raises ValueError in several cases which prevent computing
  80. # a common path.
  81. common_path = None
  82. if common_path == from_path:
  83. rest = filename[len(from_path):]
  84. if rest.startswith(('\\', '/')):
  85. rest = rest[1:]
  86. return to_name, rest
  87. return None, filename
  88. # An object used in place of a "path string" which knows how to repr()
  89. # itself using variable names instead of hard-coded paths.
  90. class Path:
  91. def __init__(self, *parts):
  92. self.path = os.path.join(*parts)
  93. self.variable_prefix = self.filename_suffix = None
  94. def __repr__(self):
  95. if self.filename_suffix is None:
  96. self.variable_prefix, self.filename_suffix = make_variable_path(self.path)
  97. if self.variable_prefix is None:
  98. return repr(self.path)
  99. return "os.path.join(" + self.variable_prefix + "," + repr(self.filename_suffix) + ")"
  100. # An object used to construct extra preamble for the spec file, in order
  101. # to accommodate extra collect_*() calls from the command-line
  102. class Preamble:
  103. def __init__(self, datas, binaries, hiddenimports, collect_data,
  104. collect_binaries, collect_submodules, collect_all,
  105. copy_metadata, recursive_copy_metadata):
  106. # Initialize with literal values - will be switched to preamble
  107. # variable name later, if necessary
  108. self.binaries = binaries or []
  109. self.hiddenimports = hiddenimports or []
  110. self.datas = datas or []
  111. # Preamble content
  112. self.content = []
  113. # Import statements
  114. if collect_data:
  115. self._add_hookutil_import('collect_data_files')
  116. if collect_binaries:
  117. self._add_hookutil_import('collect_dynamic_libs')
  118. if collect_submodules:
  119. self._add_hookutil_import('collect_submodules')
  120. if collect_all:
  121. self._add_hookutil_import('collect_all')
  122. if copy_metadata or recursive_copy_metadata:
  123. self._add_hookutil_import('copy_metadata')
  124. if self.content:
  125. self.content += [''] # empty line to separate the section
  126. # Variables
  127. if collect_data or copy_metadata or collect_all \
  128. or recursive_copy_metadata:
  129. self._add_var('datas', self.datas)
  130. self.datas = 'datas' # switch to variable
  131. if collect_binaries or collect_all:
  132. self._add_var('binaries', self.binaries)
  133. self.binaries = 'binaries' # switch to variable
  134. if collect_submodules or collect_all:
  135. self._add_var('hiddenimports', self.hiddenimports)
  136. self.hiddenimports = 'hiddenimports' # switch to variable
  137. # Content - collect_data_files
  138. for entry in collect_data:
  139. self._add_collect_data(entry)
  140. # Content - copy_metadata
  141. for entry in copy_metadata:
  142. self._add_copy_metadata(entry)
  143. # Content - copy_metadata(..., recursive=True)
  144. for entry in recursive_copy_metadata:
  145. self._add_recursive_copy_metadata(entry)
  146. # Content - collect_binaries
  147. for entry in collect_binaries:
  148. self._add_collect_binaries(entry)
  149. # Content - collect_submodules
  150. for entry in collect_submodules:
  151. self._add_collect_submodules(entry)
  152. # Content - collect_all
  153. for entry in collect_all:
  154. self._add_collect_all(entry)
  155. # Merge
  156. if self.content and self.content[-1] != '':
  157. self.content += [''] # empty line
  158. self.content = '\n'.join(self.content)
  159. def _add_hookutil_import(self, name):
  160. self.content += [
  161. 'from PyInstaller.utils.hooks import {0}'.format(name)
  162. ]
  163. def _add_var(self, name, initial_value):
  164. self.content += [
  165. '{0} = {1}'.format(name, initial_value)
  166. ]
  167. def _add_collect_data(self, name):
  168. self.content += [
  169. 'datas += collect_data_files(\'{0}\')'.format(name)
  170. ]
  171. def _add_copy_metadata(self, name):
  172. self.content += [
  173. 'datas += copy_metadata(\'{0}\')'.format(name)
  174. ]
  175. def _add_recursive_copy_metadata(self, name):
  176. self.content += [
  177. 'datas += copy_metadata(\'{0}\', recursive=True)'.format(name)
  178. ]
  179. def _add_collect_binaries(self, name):
  180. self.content += [
  181. 'binaries += collect_dynamic_libs(\'{0}\')'.format(name)
  182. ]
  183. def _add_collect_submodules(self, name):
  184. self.content += [
  185. 'hiddenimports += collect_submodules(\'{0}\')'.format(name)
  186. ]
  187. def _add_collect_all(self, name):
  188. self.content += [
  189. 'tmp_ret = collect_all(\'{0}\')'.format(name),
  190. 'datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]' # noqa: E501
  191. ]
  192. def __add_options(parser):
  193. """
  194. Add the `Makespec` options to a option-parser instance or a
  195. option group.
  196. """
  197. g = parser.add_argument_group('What to generate')
  198. g.add_argument("-D", "--onedir", dest="onefile",
  199. action="store_false", default=False,
  200. help="Create a one-folder bundle containing an executable (default)")
  201. g.add_argument("-F", "--onefile", dest="onefile",
  202. action="store_true", default=False,
  203. help="Create a one-file bundled executable.")
  204. g.add_argument("--specpath", metavar="DIR",
  205. help="Folder to store the generated spec file "
  206. "(default: current directory)")
  207. g.add_argument("-n", "--name",
  208. help="Name to assign to the bundled app and spec file "
  209. "(default: first script's basename)")
  210. g = parser.add_argument_group('What to bundle, where to search')
  211. g.add_argument('--add-data',
  212. action='append', default=[], type=add_data_or_binary,
  213. metavar='<SRC;DEST or SRC:DEST>', dest='datas',
  214. help='Additional non-binary files or folders to be added '
  215. 'to the executable. The path separator is platform '
  216. 'specific, ``os.pathsep`` (which is ``;`` on Windows '
  217. 'and ``:`` on most unix systems) is used. This option '
  218. 'can be used multiple times.')
  219. g.add_argument('--add-binary',
  220. action='append', default=[], type=add_data_or_binary,
  221. metavar='<SRC;DEST or SRC:DEST>', dest="binaries",
  222. help='Additional binary files to be added to the executable. '
  223. 'See the ``--add-data`` option for more details. '
  224. 'This option can be used multiple times.')
  225. g.add_argument("-p", "--paths", dest="pathex",
  226. metavar="DIR", action="append", default=[],
  227. help="A path to search for imports (like using PYTHONPATH). "
  228. "Multiple paths are allowed, separated "
  229. "by ``%s``, or use this option multiple times. "
  230. "Equivalent to supplying the ``pathex`` argument in "
  231. "the spec file."
  232. % repr(os.pathsep))
  233. g.add_argument('--hidden-import', '--hiddenimport',
  234. action='append', default=[],
  235. metavar="MODULENAME", dest='hiddenimports',
  236. help='Name an import not visible in the code of the script(s). '
  237. 'This option can be used multiple times.')
  238. g.add_argument('--collect-submodules', action="append", default=[],
  239. metavar="MODULENAME", dest='collect_submodules',
  240. help='Collect all submodules from the specified package '
  241. 'or module. This option can be used multiple times.')
  242. g.add_argument('--collect-data', '--collect-datas', action="append",
  243. default=[], metavar="MODULENAME", dest='collect_data',
  244. help='Collect all data from the specified package or '
  245. ' module. This option can be used multiple times.')
  246. g.add_argument('--collect-binaries', action="append", default=[],
  247. metavar="MODULENAME", dest='collect_binaries',
  248. help='Collect all binaries from the specified package or '
  249. ' module. This option can be used multiple times.')
  250. g.add_argument('--collect-all', action="append", default=[],
  251. metavar="MODULENAME", dest='collect_all',
  252. help='Collect all submodules, data files, and binaries '
  253. 'from the specified package or module. This option can '
  254. 'be used multiple times.')
  255. g.add_argument('--copy-metadata', action="append", default=[],
  256. metavar="PACKAGENAME", dest='copy_metadata',
  257. help='Copy metadata for the specified package. '
  258. 'This option can be used multiple times.')
  259. g.add_argument('--recursive-copy-metadata', action="append", default=[],
  260. metavar="PACKAGENAME", dest='recursive_copy_metadata',
  261. help='Copy metadata for the specified package and all its '
  262. 'dependencies. This option can be used multiple times.')
  263. g.add_argument("--additional-hooks-dir", action="append", dest="hookspath",
  264. default=[],
  265. help="An additional path to search for hooks. "
  266. "This option can be used multiple times.")
  267. g.add_argument('--runtime-hook', action='append', dest='runtime_hooks',
  268. default=[],
  269. help='Path to a custom runtime hook file. A runtime hook '
  270. 'is code that is bundled with the executable and '
  271. 'is executed before any other code or module '
  272. 'to set up special features of the runtime environment. '
  273. 'This option can be used multiple times.')
  274. g.add_argument('--exclude-module', dest='excludes', action='append',
  275. default=[],
  276. help='Optional module or package (the Python name, '
  277. 'not the path name) that will be ignored (as though '
  278. 'it was not found). '
  279. 'This option can be used multiple times.')
  280. g.add_argument('--key', dest='key',
  281. help='The key used to encrypt Python bytecode.')
  282. g.add_argument('--splash',
  283. dest='splash', metavar="IMAGE_FILE",
  284. help="(EXPERIMENTAL) Add an splash screen with the image"
  285. " IMAGE_FILE to the application. The splash screen"
  286. " can show progress updates while unpacking.")
  287. g = parser.add_argument_group('How to generate')
  288. g.add_argument("-d", "--debug",
  289. # If this option is not specified, then its default value is
  290. # an empty list (no debug options selected).
  291. default=[],
  292. # Note that ``nargs`` is omitted. This produces a single item
  293. # not stored in a list, as opposed to list containing one
  294. # item, per `nargs <https://docs.python.org/3/library/argparse.html#nargs>`_.
  295. nargs=None,
  296. # The options specified must come from this list.
  297. choices=DEBUG_ALL_CHOICE + DEBUG_ARGUMENT_CHOICES,
  298. # Append choice, rather than storing them (which would
  299. # overwrite any previous selections).
  300. action='append',
  301. # Allow newlines in the help text; see the
  302. # ``_SmartFormatter`` in ``__main__.py``.
  303. help=("R|Provide assistance with debugging a frozen\n"
  304. "application. This argument may be provided multiple\n"
  305. "times to select several of the following options.\n"
  306. "\n"
  307. "- all: All three of the following options.\n"
  308. "\n"
  309. "- imports: specify the -v option to the underlying\n"
  310. " Python interpreter, causing it to print a message\n"
  311. " each time a module is initialized, showing the\n"
  312. " place (filename or built-in module) from which it\n"
  313. " is loaded. See\n"
  314. " https://docs.python.org/3/using/cmdline.html#id4.\n"
  315. "\n"
  316. "- bootloader: tell the bootloader to issue progress\n"
  317. " messages while initializing and starting the\n"
  318. " bundled app. Used to diagnose problems with\n"
  319. " missing imports.\n"
  320. "\n"
  321. "- noarchive: instead of storing all frozen Python\n"
  322. " source files as an archive inside the resulting\n"
  323. " executable, store them as files in the resulting\n"
  324. " output directory.\n"
  325. "\n"))
  326. g.add_argument("-s", "--strip", action="store_true",
  327. help="Apply a symbol-table strip to the executable and shared libs "
  328. "(not recommended for Windows)")
  329. g.add_argument("--noupx", action="store_true", default=False,
  330. help="Do not use UPX even if it is available "
  331. "(works differently between Windows and *nix)")
  332. g.add_argument("--upx-exclude", dest="upx_exclude", metavar="FILE",
  333. action="append",
  334. help="Prevent a binary from being compressed when using "
  335. "upx. This is typically used if upx corrupts certain "
  336. "binaries during compression. "
  337. "FILE is the filename of the binary without path. "
  338. "This option can be used multiple times.")
  339. g = parser.add_argument_group('Windows and Mac OS X specific options')
  340. g.add_argument("-c", "--console", "--nowindowed", dest="console",
  341. action="store_true", default=True,
  342. help="Open a console window for standard i/o (default). "
  343. "On Windows this option will have no effect if the "
  344. "first script is a '.pyw' file.")
  345. g.add_argument("-w", "--windowed", "--noconsole", dest="console",
  346. action="store_false",
  347. help="Windows and Mac OS X: do not provide a console window "
  348. "for standard i/o. "
  349. "On Mac OS X this also triggers building an OS X .app bundle. "
  350. "On Windows this option will be set if the first "
  351. "script is a '.pyw' file. "
  352. "This option is ignored in *NIX systems.")
  353. g.add_argument("-i", "--icon", dest="icon_file",
  354. metavar='<FILE.ico or FILE.exe,ID or FILE.icns or "NONE">',
  355. help="FILE.ico: apply that icon to a Windows executable. "
  356. "FILE.exe,ID, extract the icon with ID from an exe. "
  357. "FILE.icns: apply the icon to the "
  358. ".app bundle on Mac OS X. "
  359. 'Use "NONE" to not apply any icon, '
  360. "thereby making the OS to show some default "
  361. "(default: apply PyInstaller's icon)")
  362. g.add_argument("--disable-windowed-traceback",
  363. dest="disable_windowed_traceback", action="store_true",
  364. default=False,
  365. help="Disable traceback dump of unhandled exception in "
  366. "windowed (noconsole) mode (Windows and macOS only), "
  367. "and instead display a message that this feature is "
  368. "disabled.")
  369. g = parser.add_argument_group('Windows specific options')
  370. g.add_argument("--version-file",
  371. dest="version_file", metavar="FILE",
  372. help="add a version resource from FILE to the exe")
  373. g.add_argument("-m", "--manifest", metavar="<FILE or XML>",
  374. help="add manifest FILE or XML to the exe")
  375. g.add_argument("-r", "--resource", dest="resources",
  376. metavar="RESOURCE", action="append",
  377. default=[],
  378. help="Add or update a resource to a Windows executable. "
  379. "The RESOURCE is one to four items, "
  380. "FILE[,TYPE[,NAME[,LANGUAGE]]]. "
  381. "FILE can be a "
  382. "data file or an exe/dll. For data files, at least "
  383. "TYPE and NAME must be specified. LANGUAGE defaults "
  384. "to 0 or may be specified as wildcard * to update all "
  385. "resources of the given TYPE and NAME. For exe/dll "
  386. "files, all resources from FILE will be added/updated "
  387. "to the final executable if TYPE, NAME and LANGUAGE "
  388. "are omitted or specified as wildcard *."
  389. "This option can be used multiple times.")
  390. g.add_argument('--uac-admin', dest='uac_admin', action="store_true", default=False,
  391. help='Using this option creates a Manifest '
  392. 'which will request elevation upon application restart.')
  393. g.add_argument('--uac-uiaccess', dest='uac_uiaccess', action="store_true", default=False,
  394. help='Using this option allows an elevated application to '
  395. 'work with Remote Desktop.')
  396. g = parser.add_argument_group('Windows Side-by-side Assembly searching options (advanced)')
  397. g.add_argument("--win-private-assemblies", dest="win_private_assemblies",
  398. action="store_true",
  399. help="Any Shared Assemblies bundled into the application "
  400. "will be changed into Private Assemblies. This means "
  401. "the exact versions of these assemblies will always "
  402. "be used, and any newer versions installed on user "
  403. "machines at the system level will be ignored.")
  404. g.add_argument("--win-no-prefer-redirects", dest="win_no_prefer_redirects",
  405. action="store_true",
  406. help="While searching for Shared or Private Assemblies to "
  407. "bundle into the application, PyInstaller will prefer "
  408. "not to follow policies that redirect to newer versions, "
  409. "and will try to bundle the exact versions of the assembly.")
  410. g = parser.add_argument_group('Mac OS X specific options')
  411. g.add_argument('--osx-bundle-identifier', dest='bundle_identifier',
  412. help='Mac OS X .app bundle identifier is used as the default unique program '
  413. 'name for code signing purposes. The usual form is a hierarchical name '
  414. 'in reverse DNS notation. For example: com.mycompany.department.appname '
  415. "(default: first script's basename)")
  416. g.add_argument('--target-architecture', '--target-arch',
  417. dest='target_arch', metavar='ARCH', default=None,
  418. help="Target architecture (macOS only; valid values: "
  419. "x86_64, arm64, universal2). Enables switching "
  420. "between universal2 and single-arch version of "
  421. "frozen application (provided python installation "
  422. "supports the target architecture). If not target "
  423. "architecture is not specified, the current running "
  424. "architecture is targeted.")
  425. g.add_argument('--codesign-identity', dest='codesign_identity',
  426. metavar='IDENTITY', default=None,
  427. help="Code signing identity (macOS only). Use the provided "
  428. "identity to sign collected binaries and generated "
  429. "executable. If signing identity is not provided, "
  430. "ad-hoc signing is performed instead.")
  431. g.add_argument('--osx-entitlements-file', dest='entitlements_file',
  432. metavar='FILENAME', default=None,
  433. help="Entitlements file to use when code-signing the "
  434. "collected binaries (macOS only).")
  435. g = parser.add_argument_group('Rarely used special options')
  436. g.add_argument("--runtime-tmpdir", dest="runtime_tmpdir", metavar="PATH",
  437. help="Where to extract libraries and support files in "
  438. "`onefile`-mode. "
  439. "If this option is given, the bootloader will ignore "
  440. "any temp-folder location defined by the run-time OS. "
  441. "The ``_MEIxxxxxx``-folder will be created here. "
  442. "Please use this option only if you know what you "
  443. "are doing.")
  444. g.add_argument("--bootloader-ignore-signals", action="store_true",
  445. default=False,
  446. help=("Tell the bootloader to ignore signals rather "
  447. "than forwarding them to the child process. "
  448. "Useful in situations where e.g. a supervisor "
  449. "process signals both the bootloader and child "
  450. "(e.g. via a process group) to avoid signalling "
  451. "the child twice."))
  452. def main(scripts, name=None, onefile=None,
  453. console=True, debug=None, strip=False, noupx=False, upx_exclude=None,
  454. runtime_tmpdir=None, pathex=None, version_file=None, specpath=None,
  455. bootloader_ignore_signals=False, disable_windowed_traceback=False,
  456. datas=None, binaries=None, icon_file=None, manifest=None, resources=None, bundle_identifier=None,
  457. hiddenimports=None, hookspath=None, key=None, runtime_hooks=None,
  458. excludes=None, uac_admin=False, uac_uiaccess=False,
  459. win_no_prefer_redirects=False, win_private_assemblies=False,
  460. collect_submodules=None, collect_binaries=None, collect_data=None,
  461. collect_all=None, copy_metadata=None, splash=None,
  462. recursive_copy_metadata=None, target_arch=None,
  463. codesign_identity=None, entitlements_file=None, **kwargs):
  464. # If appname is not specified - use the basename of the main script as name.
  465. if name is None:
  466. name = os.path.splitext(os.path.basename(scripts[0]))[0]
  467. # If specpath not specified - use default value - current working directory.
  468. if specpath is None:
  469. specpath = DEFAULT_SPECPATH
  470. else:
  471. # Expand tilde to user's home directory.
  472. specpath = expand_path(specpath)
  473. # If cwd is the root directory of PyInstaller then generate .spec file
  474. # subdirectory ./appname/.
  475. if specpath == HOMEPATH:
  476. specpath = os.path.join(HOMEPATH, name)
  477. # Create directory tree if missing.
  478. if not os.path.exists(specpath):
  479. os.makedirs(specpath)
  480. # Append specpath to PYTHONPATH - where to look for additional Python modules.
  481. pathex = pathex or []
  482. pathex = pathex[:]
  483. pathex.append(specpath)
  484. # Handle additional EXE options.
  485. exe_options = ''
  486. if version_file:
  487. exe_options = "%s, version='%s'" % (exe_options, quote_win_filepath(version_file))
  488. if uac_admin:
  489. exe_options = "%s, uac_admin=%s" % (exe_options, 'True')
  490. if uac_uiaccess:
  491. exe_options = "%s, uac_uiaccess=%s" % (exe_options, 'True')
  492. if icon_file:
  493. # Icon file for Windows.
  494. # On Windows default icon is embedded in the bootloader executable.
  495. exe_options = "%s, icon='%s'" % (exe_options, quote_win_filepath(icon_file))
  496. # Icon file for OSX.
  497. # We need to encapsulate it into apostrofes.
  498. icon_file = "'%s'" % icon_file
  499. else:
  500. # On OSX default icon has to be copied into the .app bundle.
  501. # The the text value 'None' means - use default icon.
  502. icon_file = 'None'
  503. if bundle_identifier:
  504. # We need to encapsulate it into apostrofes.
  505. bundle_identifier = "'%s'" % bundle_identifier
  506. if manifest:
  507. if "<" in manifest:
  508. # Assume XML string
  509. exe_options = "%s, manifest='%s'" % (exe_options, manifest.replace("'", "\\'"))
  510. else:
  511. # Assume filename
  512. exe_options = "%s, manifest='%s'" % (exe_options, quote_win_filepath(manifest))
  513. if resources:
  514. resources = list(map(quote_win_filepath, resources))
  515. exe_options = "%s, resources=%s" % (exe_options, repr(resources))
  516. hiddenimports = hiddenimports or []
  517. upx_exclude = upx_exclude or []
  518. # If file extension of the first script is '.pyw', force --windowed option.
  519. if is_win and os.path.splitext(scripts[0])[-1] == '.pyw':
  520. console = False
  521. # If script paths are relative, make them relative to the directory containing .spec file.
  522. scripts = [make_path_spec_relative(x, specpath) for x in scripts]
  523. # With absolute paths replace prefix with variable HOMEPATH.
  524. scripts = list(map(Path, scripts))
  525. if key:
  526. # Tries to import tinyaes since we need it for bytecode obfuscation.
  527. try:
  528. import tinyaes # noqa: F401 (test import)
  529. except ImportError:
  530. logger.error('We need tinyaes to use byte-code obfuscation but we '
  531. 'could not')
  532. logger.error('find it. You can install it with pip by running:')
  533. logger.error(' pip install tinyaes')
  534. sys.exit(1)
  535. cipher_init = cipher_init_template % {'key': key}
  536. else:
  537. cipher_init = cipher_absent_template
  538. # Translate the default of ``debug=None`` to an empty list.
  539. if debug is None:
  540. debug = []
  541. # Translate the ``all`` option.
  542. if DEBUG_ALL_CHOICE[0] in debug:
  543. debug = DEBUG_ARGUMENT_CHOICES
  544. # Create preamble (for collect_*() calls)
  545. preamble = Preamble(
  546. datas, binaries, hiddenimports, collect_data, collect_binaries,
  547. collect_submodules, collect_all, copy_metadata, recursive_copy_metadata
  548. )
  549. if splash:
  550. splash_init = splashtmpl % {'splash_image': splash}
  551. splash_binaries = ("\n"
  552. + " " * (10 if onefile else 15) # noqa: W503
  553. + "splash.binaries,") # noqa: W503
  554. splash_target = "\n" + " " * 10 + "splash,"
  555. else:
  556. splash_init = splash_binaries = splash_target = ""
  557. d = {
  558. 'scripts': scripts,
  559. 'pathex': pathex,
  560. 'binaries': preamble.binaries,
  561. 'datas': preamble.datas,
  562. 'hiddenimports': preamble.hiddenimports,
  563. 'preamble': preamble.content,
  564. 'name': name,
  565. 'noarchive': 'noarchive' in debug,
  566. 'options': [('v', None, 'OPTION')] if 'imports' in debug else [],
  567. 'debug_bootloader': 'bootloader' in debug,
  568. 'bootloader_ignore_signals': bootloader_ignore_signals,
  569. 'strip': strip,
  570. 'upx': not noupx,
  571. 'upx_exclude': upx_exclude,
  572. 'runtime_tmpdir': runtime_tmpdir,
  573. 'exe_options': exe_options,
  574. 'cipher_init': cipher_init,
  575. # Directory with additional custom import hooks.
  576. 'hookspath': hookspath,
  577. # List with custom runtime hook files.
  578. 'runtime_hooks': runtime_hooks or [],
  579. # List of modules/pakages to ignore.
  580. 'excludes': excludes or [],
  581. # only Windows and Mac OS X distinguish windowed and console apps
  582. 'console': console,
  583. 'disable_windowed_traceback': disable_windowed_traceback,
  584. # Icon filename. Only OSX uses this item.
  585. 'icon': icon_file,
  586. # .app bundle identifier. Only OSX uses this item.
  587. 'bundle_identifier': bundle_identifier,
  588. # Target architecture (macOS only)
  589. 'target_arch': target_arch,
  590. # Code signing identity (macOS only)
  591. 'codesign_identity': codesign_identity,
  592. # Entitlements file (macOS only)
  593. 'entitlements_file': entitlements_file,
  594. # Windows assembly searching options
  595. 'win_no_prefer_redirects': win_no_prefer_redirects,
  596. 'win_private_assemblies': win_private_assemblies,
  597. # splash screen
  598. 'splash_init': splash_init,
  599. 'splash_target': splash_target,
  600. 'splash_binaries': splash_binaries,
  601. }
  602. # Write down .spec file to filesystem.
  603. specfnm = os.path.join(specpath, name + '.spec')
  604. with open(specfnm, 'w', encoding='utf-8') as specfile:
  605. if onefile:
  606. specfile.write(onefiletmplt % d)
  607. # For OSX create .app bundle.
  608. if is_darwin and not console:
  609. specfile.write(bundleexetmplt % d)
  610. else:
  611. specfile.write(onedirtmplt % d)
  612. # For OSX create .app bundle.
  613. if is_darwin and not console:
  614. specfile.write(bundletmplt % d)
  615. return specfnm