makespec.py 31 KB

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