makespec.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2005-2023, 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 argparse
  15. import os
  16. import re
  17. import sys
  18. from PyInstaller import DEFAULT_SPECPATH, HOMEPATH
  19. from PyInstaller import log as logging
  20. from PyInstaller.building.templates import bundleexetmplt, bundletmplt, onedirtmplt, onefiletmplt, splashtmpl
  21. from PyInstaller.compat import is_darwin, is_win
  22. logger = logging.getLogger(__name__)
  23. # This list gives valid choices for the ``--debug`` command-line option, except for the ``all`` choice.
  24. DEBUG_ARGUMENT_CHOICES = ['imports', 'bootloader', 'noarchive']
  25. # This is the ``all`` choice.
  26. DEBUG_ALL_CHOICE = ['all']
  27. def escape_win_filepath(path):
  28. # escape all \ with another \ after using normpath to clean up the path
  29. return os.path.normpath(path).replace('\\', '\\\\')
  30. def make_path_spec_relative(filename, spec_dir):
  31. """
  32. Make the filename relative to the directory containing .spec file if filename is relative and not absolute.
  33. Otherwise keep filename untouched.
  34. """
  35. if os.path.isabs(filename):
  36. return filename
  37. else:
  38. filename = os.path.abspath(filename)
  39. # Make it relative.
  40. filename = os.path.relpath(filename, start=spec_dir)
  41. return filename
  42. # Support for trying to avoid hard-coded paths in the .spec files. Eg, all files rooted in the Installer directory tree
  43. # will be written using "HOMEPATH", thus allowing this spec file to be used with any Installer installation. Same thing
  44. # could be done for other paths too.
  45. path_conversions = ((HOMEPATH, "HOMEPATH"),)
  46. class SourceDestAction(argparse.Action):
  47. """
  48. A command line option which takes multiple source:dest pairs.
  49. """
  50. def __init__(self, *args, default=None, metavar=None, **kwargs):
  51. super().__init__(*args, default=[], metavar='SOURCE:DEST', **kwargs)
  52. def __call__(self, parser, namespace, value, option_string=None):
  53. try:
  54. # Find the only separator that isn't a Windows drive.
  55. separator, = (m for m in re.finditer(rf"(^\w:[/\\])|[:{os.pathsep}]", value) if not m[1])
  56. except ValueError:
  57. # Split into SRC and DEST failed, wrong syntax
  58. raise argparse.ArgumentError(self, f'Wrong syntax, should be {self.option_strings[0]}=SOURCE:DEST')
  59. src = value[:separator.start()]
  60. dest = value[separator.end():]
  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(self, "You have to specify both SOURCE and DEST")
  64. # argparse is not particularly smart with copy by reference typed defaults. If the current list is the default,
  65. # replace it before modifying it to avoid changing the default.
  66. if getattr(namespace, self.dest) is self.default:
  67. setattr(namespace, self.dest, [])
  68. getattr(namespace, self.dest).append((src, dest))
  69. def make_variable_path(filename, conversions=path_conversions):
  70. if not os.path.isabs(filename):
  71. # os.path.commonpath can not compare relative and absolute paths, and if filename is not absolute, none of the
  72. # paths in conversions will match anyway.
  73. return None, filename
  74. for (from_path, to_name) in conversions:
  75. assert os.path.abspath(from_path) == from_path, ("path '%s' should already be absolute" % from_path)
  76. try:
  77. common_path = os.path.commonpath([filename, from_path])
  78. except ValueError:
  79. # Per https://docs.python.org/3/library/os.path.html#os.path.commonpath, this raises ValueError in several
  80. # cases which prevent computing 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. def removed_key_option(x):
  89. from PyInstaller.exceptions import RemovedCipherFeatureError
  90. raise RemovedCipherFeatureError("Please remove your --key=xxx argument.")
  91. class _RemovedFlagAction(argparse.Action):
  92. def __init__(self, *args, **kwargs):
  93. kwargs["help"] = argparse.SUPPRESS
  94. kwargs["nargs"] = 0
  95. super().__init__(*args, **kwargs)
  96. class _RemovedNoEmbedManifestAction(_RemovedFlagAction):
  97. def __call__(self, *args, **kwargs):
  98. from PyInstaller.exceptions import RemovedExternalManifestError
  99. raise RemovedExternalManifestError("Please remove your --no-embed-manifest argument.")
  100. class _RemovedWinPrivateAssembliesAction(_RemovedFlagAction):
  101. def __call__(self, *args, **kwargs):
  102. from PyInstaller.exceptions import RemovedWinSideBySideSupportError
  103. raise RemovedWinSideBySideSupportError("Please remove your --win-private-assemblies argument.")
  104. class _RemovedWinNoPreferRedirectsAction(_RemovedFlagAction):
  105. def __call__(self, *args, **kwargs):
  106. from PyInstaller.exceptions import RemovedWinSideBySideSupportError
  107. raise RemovedWinSideBySideSupportError("Please remove your --win-no-prefer-redirects argument.")
  108. # An object used in place of a "path string", which knows how to repr() itself using variable names instead of
  109. # hard-coded paths.
  110. class Path:
  111. def __init__(self, *parts):
  112. self.path = os.path.join(*parts)
  113. self.variable_prefix = self.filename_suffix = None
  114. def __repr__(self):
  115. if self.filename_suffix is None:
  116. self.variable_prefix, self.filename_suffix = make_variable_path(self.path)
  117. if self.variable_prefix is None:
  118. return repr(self.path)
  119. return "os.path.join(" + self.variable_prefix + "," + repr(self.filename_suffix) + ")"
  120. # An object used to construct extra preamble for the spec file, in order to accommodate extra collect_*() calls from the
  121. # command-line
  122. class Preamble:
  123. def __init__(
  124. self, datas, binaries, hiddenimports, collect_data, collect_binaries, collect_submodules, collect_all,
  125. copy_metadata, recursive_copy_metadata
  126. ):
  127. # Initialize with literal values - will be switched to preamble variable name later, if necessary
  128. self.binaries = binaries or []
  129. self.hiddenimports = hiddenimports or []
  130. self.datas = datas or []
  131. # Preamble content
  132. self.content = []
  133. # Import statements
  134. if collect_data:
  135. self._add_hookutil_import('collect_data_files')
  136. if collect_binaries:
  137. self._add_hookutil_import('collect_dynamic_libs')
  138. if collect_submodules:
  139. self._add_hookutil_import('collect_submodules')
  140. if collect_all:
  141. self._add_hookutil_import('collect_all')
  142. if copy_metadata or recursive_copy_metadata:
  143. self._add_hookutil_import('copy_metadata')
  144. if self.content:
  145. self.content += [''] # empty line to separate the section
  146. # Variables
  147. if collect_data or copy_metadata or collect_all or recursive_copy_metadata:
  148. self._add_var('datas', self.datas)
  149. self.datas = 'datas' # switch to variable
  150. if collect_binaries or collect_all:
  151. self._add_var('binaries', self.binaries)
  152. self.binaries = 'binaries' # switch to variable
  153. if collect_submodules or collect_all:
  154. self._add_var('hiddenimports', self.hiddenimports)
  155. self.hiddenimports = 'hiddenimports' # switch to variable
  156. # Content - collect_data_files
  157. for entry in collect_data:
  158. self._add_collect_data(entry)
  159. # Content - copy_metadata
  160. for entry in copy_metadata:
  161. self._add_copy_metadata(entry)
  162. # Content - copy_metadata(..., recursive=True)
  163. for entry in recursive_copy_metadata:
  164. self._add_recursive_copy_metadata(entry)
  165. # Content - collect_binaries
  166. for entry in collect_binaries:
  167. self._add_collect_binaries(entry)
  168. # Content - collect_submodules
  169. for entry in collect_submodules:
  170. self._add_collect_submodules(entry)
  171. # Content - collect_all
  172. for entry in collect_all:
  173. self._add_collect_all(entry)
  174. # Merge
  175. if self.content and self.content[-1] != '':
  176. self.content += [''] # empty line
  177. self.content = '\n'.join(self.content)
  178. def _add_hookutil_import(self, name):
  179. self.content += ['from PyInstaller.utils.hooks import {0}'.format(name)]
  180. def _add_var(self, name, initial_value):
  181. self.content += ['{0} = {1}'.format(name, initial_value)]
  182. def _add_collect_data(self, name):
  183. self.content += ['datas += collect_data_files(\'{0}\')'.format(name)]
  184. def _add_copy_metadata(self, name):
  185. self.content += ['datas += copy_metadata(\'{0}\')'.format(name)]
  186. def _add_recursive_copy_metadata(self, name):
  187. self.content += ['datas += copy_metadata(\'{0}\', recursive=True)'.format(name)]
  188. def _add_collect_binaries(self, name):
  189. self.content += ['binaries += collect_dynamic_libs(\'{0}\')'.format(name)]
  190. def _add_collect_submodules(self, name):
  191. self.content += ['hiddenimports += collect_submodules(\'{0}\')'.format(name)]
  192. def _add_collect_all(self, name):
  193. self.content += [
  194. 'tmp_ret = collect_all(\'{0}\')'.format(name),
  195. 'datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]'
  196. ]
  197. def __add_options(parser):
  198. """
  199. Add the `Makespec` options to a option-parser instance or a option group.
  200. """
  201. g = parser.add_argument_group('What to generate')
  202. g.add_argument(
  203. "-D",
  204. "--onedir",
  205. dest="onefile",
  206. action="store_false",
  207. default=None,
  208. help="Create a one-folder bundle containing an executable (default)",
  209. )
  210. g.add_argument(
  211. "-F",
  212. "--onefile",
  213. dest="onefile",
  214. action="store_true",
  215. default=None,
  216. help="Create a one-file bundled executable.",
  217. )
  218. g.add_argument(
  219. "--specpath",
  220. metavar="DIR",
  221. help="Folder to store the generated spec file (default: current directory)",
  222. )
  223. g.add_argument(
  224. "-n",
  225. "--name",
  226. help="Name to assign to the bundled app and spec file (default: first script's basename)",
  227. )
  228. g.add_argument(
  229. "--contents-directory",
  230. help="For onedir builds only, specify the name of the directory in which all supporting files (i.e. everything "
  231. "except the executable itself) will be placed in. Use \".\" to re-enable old onedir layout without contents "
  232. "directory.",
  233. )
  234. g = parser.add_argument_group('What to bundle, where to search')
  235. g.add_argument(
  236. '--add-data',
  237. action=SourceDestAction,
  238. dest='datas',
  239. help="Additional data files or directories containing data files to be added to the application. The argument "
  240. 'value should be in form of "source:dest_dir", where source is the path to file (or directory) to be '
  241. "collected, dest_dir is the destination directory relative to the top-level application directory, and both "
  242. "paths are separated by a colon (:). To put a file in the top-level application directory, use . as a "
  243. "dest_dir. This option can be used multiple times."
  244. )
  245. g.add_argument(
  246. '--add-binary',
  247. action=SourceDestAction,
  248. dest="binaries",
  249. help='Additional binary files to be added to the executable. See the ``--add-data`` option for the format. '
  250. 'This option can be used multiple times.',
  251. )
  252. g.add_argument(
  253. "-p",
  254. "--paths",
  255. dest="pathex",
  256. metavar="DIR",
  257. action="append",
  258. default=[],
  259. help="A path to search for imports (like using PYTHONPATH). Multiple paths are allowed, separated by ``%s``, "
  260. "or use this option multiple times. Equivalent to supplying the ``pathex`` argument in the spec file." %
  261. repr(os.pathsep),
  262. )
  263. g.add_argument(
  264. '--hidden-import',
  265. '--hiddenimport',
  266. action='append',
  267. default=[],
  268. metavar="MODULENAME",
  269. dest='hiddenimports',
  270. help='Name an import not visible in the code of the script(s). This option can be used multiple times.',
  271. )
  272. g.add_argument(
  273. '--collect-submodules',
  274. action="append",
  275. default=[],
  276. metavar="MODULENAME",
  277. dest='collect_submodules',
  278. help='Collect all submodules from the specified package or module. This option can be used multiple times.',
  279. )
  280. g.add_argument(
  281. '--collect-data',
  282. '--collect-datas',
  283. action="append",
  284. default=[],
  285. metavar="MODULENAME",
  286. dest='collect_data',
  287. help='Collect all data from the specified package or module. This option can be used multiple times.',
  288. )
  289. g.add_argument(
  290. '--collect-binaries',
  291. action="append",
  292. default=[],
  293. metavar="MODULENAME",
  294. dest='collect_binaries',
  295. help='Collect all binaries from the specified package or module. This option can be used multiple times.',
  296. )
  297. g.add_argument(
  298. '--collect-all',
  299. action="append",
  300. default=[],
  301. metavar="MODULENAME",
  302. dest='collect_all',
  303. help='Collect all submodules, data files, and binaries from the specified package or module. This option can '
  304. 'be used multiple times.',
  305. )
  306. g.add_argument(
  307. '--copy-metadata',
  308. action="append",
  309. default=[],
  310. metavar="PACKAGENAME",
  311. dest='copy_metadata',
  312. help='Copy metadata for the specified package. This option can be used multiple times.',
  313. )
  314. g.add_argument(
  315. '--recursive-copy-metadata',
  316. action="append",
  317. default=[],
  318. metavar="PACKAGENAME",
  319. dest='recursive_copy_metadata',
  320. help='Copy metadata for the specified package and all its dependencies. This option can be used multiple '
  321. 'times.',
  322. )
  323. g.add_argument(
  324. "--additional-hooks-dir",
  325. action="append",
  326. dest="hookspath",
  327. default=[],
  328. help="An additional path to search for hooks. This option can be used multiple times.",
  329. )
  330. g.add_argument(
  331. '--runtime-hook',
  332. action='append',
  333. dest='runtime_hooks',
  334. default=[],
  335. help='Path to a custom runtime hook file. A runtime hook is code that is bundled with the executable and is '
  336. 'executed before any other code or module to set up special features of the runtime environment. This option '
  337. 'can be used multiple times.',
  338. )
  339. g.add_argument(
  340. '--exclude-module',
  341. dest='excludes',
  342. action='append',
  343. default=[],
  344. help='Optional module or package (the Python name, not the path name) that will be ignored (as though it was '
  345. 'not found). This option can be used multiple times.',
  346. )
  347. g.add_argument(
  348. '--key',
  349. dest='key',
  350. help=argparse.SUPPRESS,
  351. type=removed_key_option,
  352. )
  353. g.add_argument(
  354. '--splash',
  355. dest='splash',
  356. metavar="IMAGE_FILE",
  357. help="(EXPERIMENTAL) Add an splash screen with the image IMAGE_FILE to the application. The splash screen can "
  358. "display progress updates while unpacking.",
  359. )
  360. g.add_argument(
  361. '--splash-center',
  362. dest='splash_center',
  363. default=None,
  364. choices={'default', 'primary', 'virtual', 'active'},
  365. help="Splash screen centering mode. See the splash screen documentation for details.",
  366. )
  367. g = parser.add_argument_group('How to generate')
  368. g.add_argument(
  369. "-d",
  370. "--debug",
  371. # If this option is not specified, then its default value is an empty list (no debug options selected).
  372. default=[],
  373. # Note that ``nargs`` is omitted. This produces a single item not stored in a list, as opposed to a list
  374. # containing one item, as per `nargs <https://docs.python.org/3/library/argparse.html#nargs>`_.
  375. nargs=None,
  376. # The options specified must come from this list.
  377. choices=DEBUG_ALL_CHOICE + DEBUG_ARGUMENT_CHOICES,
  378. # Append choice, rather than storing them (which would overwrite any previous selections).
  379. action='append',
  380. # Allow newlines in the help text; see the ``_SmartFormatter`` in ``__main__.py``.
  381. help=(
  382. "R|Provide assistance with debugging a frozen\n"
  383. "application. This argument may be provided multiple\n"
  384. "times to select several of the following options.\n"
  385. "\n"
  386. "- all: All three of the following options.\n"
  387. "\n"
  388. "- imports: specify the -v option to the underlying\n"
  389. " Python interpreter, causing it to print a message\n"
  390. " each time a module is initialized, showing the\n"
  391. " place (filename or built-in module) from which it\n"
  392. " is loaded. See\n"
  393. " https://docs.python.org/3/using/cmdline.html#id4.\n"
  394. "\n"
  395. "- bootloader: tell the bootloader to issue progress\n"
  396. " messages while initializing and starting the\n"
  397. " bundled app. Used to diagnose problems with\n"
  398. " missing imports.\n"
  399. "\n"
  400. "- noarchive: instead of storing all frozen Python\n"
  401. " source files as an archive inside the resulting\n"
  402. " executable, store them as files in the resulting\n"
  403. " output directory.\n"
  404. "\n"
  405. ),
  406. )
  407. g.add_argument(
  408. '--optimize',
  409. dest='optimize',
  410. metavar='LEVEL',
  411. type=int,
  412. choices={-1, 0, 1, 2},
  413. default=None,
  414. help='Bytecode optimization level used for collected python modules and scripts. For details, see the section '
  415. '“Bytecode Optimization Level” in PyInstaller manual.',
  416. )
  417. g.add_argument(
  418. '--python-option',
  419. dest='python_options',
  420. metavar='PYTHON_OPTION',
  421. action='append',
  422. default=[],
  423. help='Specify a command-line option to pass to the Python interpreter at runtime. Currently supports '
  424. '"v" (equivalent to "--debug imports"), "u", "W <warning control>", "X <xoption>", and "hash_seed=<value>". '
  425. 'For details, see the section "Specifying Python Interpreter Options" in PyInstaller manual.',
  426. )
  427. g.add_argument(
  428. "-s",
  429. "--strip",
  430. action="store_true",
  431. help="Apply a symbol-table strip to the executable and shared libs (not recommended for Windows)",
  432. )
  433. g.add_argument(
  434. "--noupx",
  435. action="store_true",
  436. default=False,
  437. help="Do not use UPX even if it is available (works differently between Windows and *nix)",
  438. )
  439. g.add_argument(
  440. "--upx-exclude",
  441. dest="upx_exclude",
  442. metavar="FILE",
  443. action="append",
  444. help="Prevent a binary from being compressed when using upx. This is typically used if upx corrupts certain "
  445. "binaries during compression. FILE is the filename of the binary without path. This option can be used "
  446. "multiple times.",
  447. )
  448. g = parser.add_argument_group('Windows and macOS specific options')
  449. g.add_argument(
  450. "-c",
  451. "--console",
  452. "--nowindowed",
  453. dest="console",
  454. action="store_true",
  455. default=None,
  456. help="Open a console window for standard i/o (default). On Windows this option has no effect if the first "
  457. "script is a '.pyw' file.",
  458. )
  459. g.add_argument(
  460. "-w",
  461. "--windowed",
  462. "--noconsole",
  463. dest="console",
  464. action="store_false",
  465. default=None,
  466. help="Windows and macOS: do not provide a console window for standard i/o. On macOS this also triggers "
  467. "building a macOS .app bundle. On Windows this option is automatically set if the first script is a '.pyw' "
  468. "file. This option is ignored on *NIX systems.",
  469. )
  470. g.add_argument(
  471. "--hide-console",
  472. type=str,
  473. choices={'hide-early', 'hide-late', 'minimize-early', 'minimize-late'},
  474. default=None,
  475. help="Windows only: in console-enabled executable, have bootloader automatically hide or minimize the console "
  476. "window if the program owns the console window (i.e., was not launched from an existing console window).",
  477. )
  478. g.add_argument(
  479. "-i",
  480. "--icon",
  481. action='append',
  482. dest="icon_file",
  483. metavar='<FILE.ico or FILE.exe,ID or FILE.icns or Image or "NONE">',
  484. help="FILE.ico: apply the icon to a Windows executable. FILE.exe,ID: extract the icon with ID from an exe. "
  485. "FILE.icns: apply the icon to the .app bundle on macOS. If an image file is entered that isn't in the "
  486. "platform format (ico on Windows, icns on Mac), PyInstaller tries to use Pillow to translate the icon into "
  487. "the correct format (if Pillow is installed). Use \"NONE\" to not apply any icon, thereby making the OS show "
  488. "some default (default: apply PyInstaller's icon). This option can be used multiple times.",
  489. )
  490. g.add_argument(
  491. "--disable-windowed-traceback",
  492. dest="disable_windowed_traceback",
  493. action="store_true",
  494. default=False,
  495. help="Disable traceback dump of unhandled exception in windowed (noconsole) mode (Windows and macOS only), "
  496. "and instead display a message that this feature is disabled.",
  497. )
  498. g = parser.add_argument_group('Windows specific options')
  499. g.add_argument(
  500. "--version-file",
  501. dest="version_file",
  502. metavar="FILE",
  503. help="Add a version resource from FILE to the exe.",
  504. )
  505. g.add_argument(
  506. "--manifest",
  507. metavar="<FILE or XML>",
  508. help="Add manifest FILE or XML to the exe.",
  509. )
  510. g.add_argument(
  511. "-m",
  512. dest="shorthand_manifest",
  513. metavar="<FILE or XML>",
  514. help="Deprecated shorthand for --manifest.",
  515. )
  516. g.add_argument(
  517. "--no-embed-manifest",
  518. action=_RemovedNoEmbedManifestAction,
  519. )
  520. g.add_argument(
  521. "-r",
  522. "--resource",
  523. dest="resources",
  524. metavar="RESOURCE",
  525. action="append",
  526. default=[],
  527. help="Add or update a resource to a Windows executable. The RESOURCE is one to four items, "
  528. "FILE[,TYPE[,NAME[,LANGUAGE]]]. FILE can be a data file or an exe/dll. For data files, at least TYPE and NAME "
  529. "must be specified. LANGUAGE defaults to 0 or may be specified as wildcard * to update all resources of the "
  530. "given TYPE and NAME. For exe/dll files, all resources from FILE will be added/updated to the final executable "
  531. "if TYPE, NAME and LANGUAGE are omitted or specified as wildcard *. This option can be used multiple times.",
  532. )
  533. g.add_argument(
  534. '--uac-admin',
  535. dest='uac_admin',
  536. action="store_true",
  537. default=False,
  538. help="Using this option creates a Manifest that will request elevation upon application start.",
  539. )
  540. g.add_argument(
  541. '--uac-uiaccess',
  542. dest='uac_uiaccess',
  543. action="store_true",
  544. default=False,
  545. help="Using this option allows an elevated application to work with Remote Desktop.",
  546. )
  547. g = parser.add_argument_group('Windows Side-by-side Assembly searching options (advanced)')
  548. g.add_argument(
  549. "--win-private-assemblies",
  550. action=_RemovedWinPrivateAssembliesAction,
  551. )
  552. g.add_argument(
  553. "--win-no-prefer-redirects",
  554. action=_RemovedWinNoPreferRedirectsAction,
  555. )
  556. g = parser.add_argument_group('macOS specific options')
  557. g.add_argument(
  558. "--argv-emulation",
  559. dest="argv_emulation",
  560. action="store_true",
  561. default=False,
  562. help="Enable argv emulation for macOS app bundles. If enabled, the initial open document/URL event is "
  563. "processed by the bootloader and the passed file paths or URLs are appended to sys.argv.",
  564. )
  565. g.add_argument(
  566. '--osx-bundle-identifier',
  567. dest='bundle_identifier',
  568. help="macOS .app bundle identifier is used as the default unique program name for code signing purposes. "
  569. "The usual form is a hierarchical name in reverse DNS notation. For example: com.mycompany.department.appname "
  570. "(default: first script's basename)",
  571. )
  572. g.add_argument(
  573. '--target-architecture',
  574. '--target-arch',
  575. dest='target_arch',
  576. metavar='ARCH',
  577. default=None,
  578. help="Target architecture (macOS only; valid values: x86_64, arm64, universal2). Enables switching between "
  579. "universal2 and single-arch version of frozen application (provided python installation supports the target "
  580. "architecture). If not target architecture is not specified, the current running architecture is targeted.",
  581. )
  582. g.add_argument(
  583. '--codesign-identity',
  584. dest='codesign_identity',
  585. metavar='IDENTITY',
  586. default=None,
  587. help="Code signing identity (macOS only). Use the provided identity to sign collected binaries and generated "
  588. "executable. If signing identity is not provided, ad-hoc signing is performed instead.",
  589. )
  590. g.add_argument(
  591. '--osx-entitlements-file',
  592. dest='entitlements_file',
  593. metavar='FILENAME',
  594. default=None,
  595. help="Entitlements file to use when code-signing the collected binaries (macOS only).",
  596. )
  597. g = parser.add_argument_group('Rarely used special options')
  598. g.add_argument(
  599. "--runtime-tmpdir",
  600. dest="runtime_tmpdir",
  601. metavar="PATH",
  602. help="Where to extract libraries and support files in `onefile` mode. If this option is given, the bootloader "
  603. "will ignore any temp-folder location defined by the run-time OS. The ``_MEIxxxxxx``-folder will be created "
  604. "here. Please use this option only if you know what you are doing. Note that on POSIX systems, PyInstaller's "
  605. "bootloader does NOT perform shell-style environment variable expansion on the given path string. Therefore, "
  606. "using environment variables (e.g., ``~`` or ``$HOME``) in path will NOT work.",
  607. )
  608. g.add_argument(
  609. "--bootloader-ignore-signals",
  610. action="store_true",
  611. default=False,
  612. help="Tell the bootloader to ignore signals rather than forwarding them to the child process. Useful in "
  613. "situations where for example a supervisor process signals both the bootloader and the child (e.g., via a "
  614. "process group) to avoid signalling the child twice.",
  615. )
  616. def main(
  617. scripts,
  618. name=None,
  619. onefile=False,
  620. console=True,
  621. debug=[],
  622. python_options=[],
  623. strip=False,
  624. noupx=False,
  625. upx_exclude=None,
  626. runtime_tmpdir=None,
  627. contents_directory=None,
  628. pathex=[],
  629. version_file=None,
  630. specpath=None,
  631. bootloader_ignore_signals=False,
  632. disable_windowed_traceback=False,
  633. datas=[],
  634. binaries=[],
  635. icon_file=None,
  636. manifest=None,
  637. resources=[],
  638. bundle_identifier=None,
  639. hiddenimports=[],
  640. hookspath=[],
  641. runtime_hooks=[],
  642. excludes=[],
  643. uac_admin=False,
  644. uac_uiaccess=False,
  645. collect_submodules=[],
  646. collect_binaries=[],
  647. collect_data=[],
  648. collect_all=[],
  649. copy_metadata=[],
  650. splash=None,
  651. recursive_copy_metadata=[],
  652. target_arch=None,
  653. codesign_identity=None,
  654. entitlements_file=None,
  655. argv_emulation=False,
  656. hide_console=None,
  657. optimize=None,
  658. splash_center=None,
  659. **_kwargs
  660. ):
  661. # Default values for onefile and console when not explicitly specified on command-line (indicated by None)
  662. if onefile is None:
  663. onefile = False
  664. if console is None:
  665. console = True
  666. # If appname is not specified - use the basename of the main script as name.
  667. if name is None:
  668. name = os.path.splitext(os.path.basename(scripts[0]))[0]
  669. # If specpath not specified - use default value - current working directory.
  670. if specpath is None:
  671. specpath = DEFAULT_SPECPATH
  672. else:
  673. # Expand starting tilde into user's home directory, as a work-around for tilde not being expanded by shell when
  674. # using `--specpath=~/path/abc` instead of `--specpath ~/path/abc` (or when the path argument is quoted).
  675. specpath = os.path.expanduser(specpath)
  676. # If cwd is the root directory of PyInstaller, generate the .spec file in ./appname/ subdirectory.
  677. if specpath == HOMEPATH:
  678. specpath = os.path.join(HOMEPATH, name)
  679. # Create directory tree if missing.
  680. if not os.path.exists(specpath):
  681. os.makedirs(specpath)
  682. # Handle additional EXE options.
  683. exe_options = ''
  684. if version_file:
  685. exe_options += "\n version='%s'," % escape_win_filepath(version_file)
  686. if uac_admin:
  687. exe_options += "\n uac_admin=True,"
  688. if uac_uiaccess:
  689. exe_options += "\n uac_uiaccess=True,"
  690. if icon_file:
  691. # Icon file for Windows.
  692. # On Windows, the default icon is embedded in the bootloader executable.
  693. if icon_file[0] == 'NONE':
  694. exe_options += "\n icon='NONE',"
  695. else:
  696. exe_options += "\n icon=[%s]," % ','.join("'%s'" % escape_win_filepath(ic) for ic in icon_file)
  697. # Icon file for macOS.
  698. # We need to encapsulate it into apostrofes.
  699. icon_file = "'%s'" % icon_file[0]
  700. else:
  701. # On macOS, the default icon has to be copied into the .app bundle.
  702. # The the text value 'None' means - use default icon.
  703. icon_file = 'None'
  704. if contents_directory:
  705. exe_options += "\n contents_directory='%s'," % (contents_directory or "_internal")
  706. if hide_console:
  707. exe_options += "\n hide_console='%s'," % hide_console
  708. if bundle_identifier:
  709. # We need to encapsulate it into apostrofes.
  710. bundle_identifier = "'%s'" % bundle_identifier
  711. if _kwargs["shorthand_manifest"]:
  712. manifest = _kwargs["shorthand_manifest"]
  713. logger.log(
  714. logging.DEPRECATION, "PyInstaller v7 will remove the -m shorthand flag. Please use --manifest=%s instead",
  715. manifest
  716. )
  717. if manifest:
  718. if "<" in manifest:
  719. # Assume XML string
  720. exe_options += "\n manifest='%s'," % manifest.replace("'", "\\'")
  721. else:
  722. # Assume filename
  723. exe_options += "\n manifest='%s'," % escape_win_filepath(manifest)
  724. if resources:
  725. resources = list(map(escape_win_filepath, resources))
  726. exe_options += "\n resources=%s," % repr(resources)
  727. hiddenimports = hiddenimports or []
  728. upx_exclude = upx_exclude or []
  729. if is_darwin and onefile and not console:
  730. from PyInstaller.building.osx import WINDOWED_ONEFILE_DEPRCATION
  731. logger.log(logging.DEPRECATION, WINDOWED_ONEFILE_DEPRCATION)
  732. # If file extension of the first script is '.pyw', force --windowed option.
  733. if is_win and os.path.splitext(scripts[0])[-1] == '.pyw':
  734. console = False
  735. # If script paths are relative, make them relative to the directory containing .spec file.
  736. scripts = [make_path_spec_relative(x, specpath) for x in scripts]
  737. # With absolute paths replace prefix with variable HOMEPATH.
  738. scripts = list(map(Path, scripts))
  739. # Translate the default of ``debug=None`` to an empty list.
  740. if debug is None:
  741. debug = []
  742. # Translate the ``all`` option.
  743. if DEBUG_ALL_CHOICE[0] in debug:
  744. debug = DEBUG_ARGUMENT_CHOICES
  745. # Create preamble (for collect_*() calls)
  746. preamble = Preamble(
  747. datas, binaries, hiddenimports, collect_data, collect_binaries, collect_submodules, collect_all, copy_metadata,
  748. recursive_copy_metadata
  749. )
  750. if splash:
  751. splash_options = ""
  752. if splash_center:
  753. splash_options = f"\n center={splash_center!r},"
  754. splash_init = splashtmpl % {
  755. 'splash_image': splash,
  756. 'splash_options': splash_options,
  757. }
  758. splash_binaries = "\n splash.binaries,"
  759. splash_target = "\n splash,"
  760. else:
  761. splash_init = splash_binaries = splash_target = ""
  762. # Infer byte-code optimization level.
  763. opt_level = sum([opt == 'O' for opt in python_options])
  764. if opt_level > 2:
  765. logger.warning(
  766. "The switch '--python-option O' has been specified %d times - it should be specified at most twice!",
  767. opt_level,
  768. )
  769. opt_level = 2
  770. if optimize is None:
  771. if opt_level == 0:
  772. # Infer from running python process
  773. optimize = sys.flags.optimize
  774. else:
  775. # Infer from `--python-option O` switch(es).
  776. optimize = opt_level
  777. elif optimize != opt_level and opt_level != 0:
  778. logger.warning(
  779. "Mismatch between optimization level passed via --optimize switch (%d) and number of '--python-option O' "
  780. "switches (%d)!",
  781. optimize,
  782. opt_level,
  783. )
  784. if optimize >= 0:
  785. # Ensure OPTIONs passed to bootloader match the optimization settings.
  786. python_options += max(0, optimize - opt_level) * ['O']
  787. # Create OPTIONs array
  788. if 'imports' in debug and 'v' not in python_options:
  789. python_options.append('v')
  790. python_options_array = [(opt, None, 'OPTION') for opt in python_options]
  791. d = {
  792. 'scripts': scripts,
  793. 'pathex': pathex or [],
  794. 'binaries': preamble.binaries,
  795. 'datas': preamble.datas,
  796. 'hiddenimports': preamble.hiddenimports,
  797. 'preamble': preamble.content,
  798. 'name': name,
  799. 'noarchive': 'noarchive' in debug,
  800. 'optimize': optimize,
  801. 'options': python_options_array,
  802. 'debug_bootloader': 'bootloader' in debug,
  803. 'bootloader_ignore_signals': bootloader_ignore_signals,
  804. 'strip': strip,
  805. 'upx': not noupx,
  806. 'upx_exclude': upx_exclude,
  807. 'runtime_tmpdir': runtime_tmpdir,
  808. 'exe_options': exe_options,
  809. # Directory with additional custom import hooks.
  810. 'hookspath': hookspath,
  811. # List with custom runtime hook files.
  812. 'runtime_hooks': runtime_hooks or [],
  813. # List of modules/packages to ignore.
  814. 'excludes': excludes or [],
  815. # only Windows and macOS distinguish windowed and console apps
  816. 'console': console,
  817. 'disable_windowed_traceback': disable_windowed_traceback,
  818. # Icon filename. Only macOS uses this item.
  819. 'icon': icon_file,
  820. # .app bundle identifier. Only macOS uses this item.
  821. 'bundle_identifier': bundle_identifier,
  822. # argv emulation (macOS only)
  823. 'argv_emulation': argv_emulation,
  824. # Target architecture (macOS only)
  825. 'target_arch': target_arch,
  826. # Code signing identity (macOS only)
  827. 'codesign_identity': codesign_identity,
  828. # Entitlements file (macOS only)
  829. 'entitlements_file': entitlements_file,
  830. # splash screen
  831. 'splash_init': splash_init,
  832. 'splash_target': splash_target,
  833. 'splash_binaries': splash_binaries,
  834. }
  835. # Write down .spec file to filesystem.
  836. specfnm = os.path.join(specpath, name + '.spec')
  837. with open(specfnm, 'w', encoding='utf-8') as specfile:
  838. if onefile:
  839. specfile.write(onefiletmplt % d)
  840. # For macOS create .app bundle.
  841. if is_darwin and not console:
  842. specfile.write(bundleexetmplt % d)
  843. else:
  844. specfile.write(onedirtmplt % d)
  845. # For macOS create .app bundle.
  846. if is_darwin and not console:
  847. specfile.write(bundletmplt % d)
  848. return specfnm