makespec.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  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 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 quote_win_filepath(path):
  30. # quote 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 absolut, 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=False,
  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=False,
  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=True,
  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. help="Windows and Mac OS X: do not provide a console window for standard i/o. On Mac OS this also triggers "
  415. "building a Mac OS .app bundle. On Windows this option is automatically set if the first script is a '.pyw' "
  416. "file. This option is ignored on *NIX systems.",
  417. )
  418. g.add_argument(
  419. "-i",
  420. "--icon",
  421. dest="icon_file",
  422. metavar='<FILE.ico or FILE.exe,ID or FILE.icns or "NONE">',
  423. help="FILE.ico: apply the icon to a Windows executable. FILE.exe,ID: extract the icon with ID from an exe. "
  424. "FILE.icns: apply the icon to the .app bundle on Mac OS. Use \"NONE\" to not apply any icon, thereby making "
  425. "the OS to show some default (default: apply PyInstaller's icon)",
  426. )
  427. g.add_argument(
  428. "--disable-windowed-traceback",
  429. dest="disable_windowed_traceback",
  430. action="store_true",
  431. default=False,
  432. help="Disable traceback dump of unhandled exception in windowed (noconsole) mode (Windows and macOS only), "
  433. "and instead display a message that this feature is disabled.",
  434. )
  435. g = parser.add_argument_group('Windows specific options')
  436. g.add_argument(
  437. "--version-file",
  438. dest="version_file",
  439. metavar="FILE",
  440. help="Add a version resource from FILE to the exe.",
  441. )
  442. g.add_argument(
  443. "-m",
  444. "--manifest",
  445. metavar="<FILE or XML>",
  446. help="Add manifest FILE or XML to the exe.",
  447. )
  448. g.add_argument(
  449. "--no-embed-manifest",
  450. dest="embed_manifest",
  451. action="store_false",
  452. help="Generate an external .exe.manifest file instead of embedding the manifest into the exe. Applicable only "
  453. "to onedir mode; in onefile mode, the manifest is always embedded, regardless of this option.",
  454. )
  455. g.add_argument(
  456. "-r",
  457. "--resource",
  458. dest="resources",
  459. metavar="RESOURCE",
  460. action="append",
  461. default=[],
  462. help="Add or update a resource to a Windows executable. The RESOURCE is one to four items, "
  463. "FILE[,TYPE[,NAME[,LANGUAGE]]]. FILE can be a data file or an exe/dll. For data files, at least TYPE and NAME "
  464. "must be specified. LANGUAGE defaults to 0 or may be specified as wildcard * to update all resources of the "
  465. "given TYPE and NAME. For exe/dll files, all resources from FILE will be added/updated to the final executable "
  466. "if TYPE, NAME and LANGUAGE are omitted or specified as wildcard *. This option can be used multiple times.",
  467. )
  468. g.add_argument(
  469. '--uac-admin',
  470. dest='uac_admin',
  471. action="store_true",
  472. default=False,
  473. help="Using this option creates a Manifest that will request elevation upon application start.",
  474. )
  475. g.add_argument(
  476. '--uac-uiaccess',
  477. dest='uac_uiaccess',
  478. action="store_true",
  479. default=False,
  480. help="Using this option allows an elevated application to work with Remote Desktop.",
  481. )
  482. g = parser.add_argument_group('Windows Side-by-side Assembly searching options (advanced)')
  483. g.add_argument(
  484. "--win-private-assemblies",
  485. dest="win_private_assemblies",
  486. action="store_true",
  487. help="Any Shared Assemblies bundled into the application will be changed into Private Assemblies. This means "
  488. "the exact versions of these assemblies will always be used, and any newer versions installed on user machines "
  489. "at the system level will be ignored.",
  490. )
  491. g.add_argument(
  492. "--win-no-prefer-redirects",
  493. dest="win_no_prefer_redirects",
  494. action="store_true",
  495. help="While searching for Shared or Private Assemblies to bundle into the application, PyInstaller will "
  496. "prefer not to follow policies that redirect to newer versions, and will try to bundle the exact versions of "
  497. "the assembly.",
  498. )
  499. g = parser.add_argument_group('Mac OS specific options')
  500. g.add_argument(
  501. '--osx-bundle-identifier',
  502. dest='bundle_identifier',
  503. help="Mac OS .app bundle identifier is used as the default unique program name for code signing purposes. "
  504. "The usual form is a hierarchical name in reverse DNS notation. For example: com.mycompany.department.appname "
  505. "(default: first script's basename)",
  506. )
  507. g.add_argument(
  508. '--target-architecture',
  509. '--target-arch',
  510. dest='target_arch',
  511. metavar='ARCH',
  512. default=None,
  513. help="Target architecture (macOS only; valid values: x86_64, arm64, universal2). Enables switching between "
  514. "universal2 and single-arch version of frozen application (provided python installation supports the target "
  515. "architecture). If not target architecture is not specified, the current running architecture is targeted.",
  516. )
  517. g.add_argument(
  518. '--codesign-identity',
  519. dest='codesign_identity',
  520. metavar='IDENTITY',
  521. default=None,
  522. help="Code signing identity (macOS only). Use the provided identity to sign collected binaries and generated "
  523. "executable. If signing identity is not provided, ad-hoc signing is performed instead.",
  524. )
  525. g.add_argument(
  526. '--osx-entitlements-file',
  527. dest='entitlements_file',
  528. metavar='FILENAME',
  529. default=None,
  530. help="Entitlements file to use when code-signing the collected binaries (macOS only).",
  531. )
  532. g = parser.add_argument_group('Rarely used special options')
  533. g.add_argument(
  534. "--runtime-tmpdir",
  535. dest="runtime_tmpdir",
  536. metavar="PATH",
  537. help="Where to extract libraries and support files in `onefile`-mode. If this option is given, the bootloader "
  538. "will ignore any temp-folder location defined by the run-time OS. The ``_MEIxxxxxx``-folder will be created "
  539. "here. Please use this option only if you know what you are doing.",
  540. )
  541. g.add_argument(
  542. "--bootloader-ignore-signals",
  543. action="store_true",
  544. default=False,
  545. help="Tell the bootloader to ignore signals rather than forwarding them to the child process. Useful in "
  546. "situations where for example a supervisor process signals both the bootloader and the child (e.g., via a "
  547. "process group) to avoid signalling the child twice.",
  548. )
  549. def main(
  550. scripts,
  551. name=None,
  552. onefile=None,
  553. console=True,
  554. debug=None,
  555. python_options=[],
  556. strip=False,
  557. noupx=False,
  558. upx_exclude=None,
  559. runtime_tmpdir=None,
  560. pathex=None,
  561. version_file=None,
  562. specpath=None,
  563. bootloader_ignore_signals=False,
  564. disable_windowed_traceback=False,
  565. datas=None,
  566. binaries=None,
  567. icon_file=None,
  568. manifest=None,
  569. embed_manifest=True,
  570. resources=None,
  571. bundle_identifier=None,
  572. hiddenimports=None,
  573. hookspath=None,
  574. key=None,
  575. runtime_hooks=None,
  576. excludes=None,
  577. uac_admin=False,
  578. uac_uiaccess=False,
  579. win_no_prefer_redirects=False,
  580. win_private_assemblies=False,
  581. collect_submodules=None,
  582. collect_binaries=None,
  583. collect_data=None,
  584. collect_all=None,
  585. copy_metadata=None,
  586. splash=None,
  587. recursive_copy_metadata=None,
  588. target_arch=None,
  589. codesign_identity=None,
  590. entitlements_file=None,
  591. **_kwargs
  592. ):
  593. # If appname is not specified - use the basename of the main script as name.
  594. if name is None:
  595. name = os.path.splitext(os.path.basename(scripts[0]))[0]
  596. # If specpath not specified - use default value - current working directory.
  597. if specpath is None:
  598. specpath = DEFAULT_SPECPATH
  599. else:
  600. # Expand tilde to user's home directory.
  601. specpath = expand_path(specpath)
  602. # If cwd is the root directory of PyInstaller, generate the .spec file in ./appname/ subdirectory.
  603. if specpath == HOMEPATH:
  604. specpath = os.path.join(HOMEPATH, name)
  605. # Create directory tree if missing.
  606. if not os.path.exists(specpath):
  607. os.makedirs(specpath)
  608. # Handle additional EXE options.
  609. exe_options = ''
  610. if version_file:
  611. exe_options = "%s, version='%s'" % (exe_options, quote_win_filepath(version_file))
  612. if uac_admin:
  613. exe_options = "%s, uac_admin=%s" % (exe_options, 'True')
  614. if uac_uiaccess:
  615. exe_options = "%s, uac_uiaccess=%s" % (exe_options, 'True')
  616. if icon_file:
  617. # Icon file for Windows.
  618. # On Windows, the default icon is embedded in the bootloader executable.
  619. exe_options = "%s, icon='%s'" % (exe_options, quote_win_filepath(icon_file))
  620. # Icon file for Mac OS.
  621. # We need to encapsulate it into apostrofes.
  622. icon_file = "'%s'" % icon_file
  623. else:
  624. # On Mac OS, the default icon has to be copied into the .app bundle.
  625. # The the text value 'None' means - use default icon.
  626. icon_file = 'None'
  627. if bundle_identifier:
  628. # We need to encapsulate it into apostrofes.
  629. bundle_identifier = "'%s'" % bundle_identifier
  630. if manifest:
  631. if "<" in manifest:
  632. # Assume XML string
  633. exe_options = "%s, manifest='%s'" % (exe_options, manifest.replace("'", "\\'"))
  634. else:
  635. # Assume filename
  636. exe_options = "%s, manifest='%s'" % (exe_options, quote_win_filepath(manifest))
  637. if not embed_manifest:
  638. exe_options = "%s, embed_manifest=%s" % (exe_options, 'False')
  639. if resources:
  640. resources = list(map(quote_win_filepath, resources))
  641. exe_options = "%s, resources=%s" % (exe_options, repr(resources))
  642. hiddenimports = hiddenimports or []
  643. upx_exclude = upx_exclude or []
  644. # If file extension of the first script is '.pyw', force --windowed option.
  645. if is_win and os.path.splitext(scripts[0])[-1] == '.pyw':
  646. console = False
  647. # If script paths are relative, make them relative to the directory containing .spec file.
  648. scripts = [make_path_spec_relative(x, specpath) for x in scripts]
  649. # With absolute paths replace prefix with variable HOMEPATH.
  650. scripts = list(map(Path, scripts))
  651. if key:
  652. # Try to import tinyaes as we need it for bytecode obfuscation.
  653. try:
  654. import tinyaes # noqa: F401 (test import)
  655. except ImportError:
  656. logger.error(
  657. 'We need tinyaes to use byte-code obfuscation but we could not find it. You can install it '
  658. 'with pip by running:\n pip install tinyaes'
  659. )
  660. sys.exit(1)
  661. cipher_init = cipher_init_template % {'key': key}
  662. else:
  663. cipher_init = cipher_absent_template
  664. # Translate the default of ``debug=None`` to an empty list.
  665. if debug is None:
  666. debug = []
  667. # Translate the ``all`` option.
  668. if DEBUG_ALL_CHOICE[0] in debug:
  669. debug = DEBUG_ARGUMENT_CHOICES
  670. # Create preamble (for collect_*() calls)
  671. preamble = Preamble(
  672. datas, binaries, hiddenimports, collect_data, collect_binaries, collect_submodules, collect_all, copy_metadata,
  673. recursive_copy_metadata
  674. )
  675. if splash:
  676. splash_init = splashtmpl % {'splash_image': splash}
  677. splash_binaries = "\n" + " " * (10 if onefile else 15) + "splash.binaries,"
  678. splash_target = "\n" + " " * 10 + "splash,"
  679. else:
  680. splash_init = splash_binaries = splash_target = ""
  681. # Create OPTIONs array
  682. if 'imports' in debug and 'v' not in python_options:
  683. python_options.append('v')
  684. python_options_array = [(opt, None, 'OPTION') for opt in python_options]
  685. d = {
  686. 'scripts': scripts,
  687. 'pathex': pathex or [],
  688. 'binaries': preamble.binaries,
  689. 'datas': preamble.datas,
  690. 'hiddenimports': preamble.hiddenimports,
  691. 'preamble': preamble.content,
  692. 'name': name,
  693. 'noarchive': 'noarchive' in debug,
  694. 'options': python_options_array,
  695. 'debug_bootloader': 'bootloader' in debug,
  696. 'bootloader_ignore_signals': bootloader_ignore_signals,
  697. 'strip': strip,
  698. 'upx': not noupx,
  699. 'upx_exclude': upx_exclude,
  700. 'runtime_tmpdir': runtime_tmpdir,
  701. 'exe_options': exe_options,
  702. 'cipher_init': cipher_init,
  703. # Directory with additional custom import hooks.
  704. 'hookspath': hookspath,
  705. # List with custom runtime hook files.
  706. 'runtime_hooks': runtime_hooks or [],
  707. # List of modules/pakages to ignore.
  708. 'excludes': excludes or [],
  709. # only Windows and Mac OS distinguish windowed and console apps
  710. 'console': console,
  711. 'disable_windowed_traceback': disable_windowed_traceback,
  712. # Icon filename. Only Mac OS uses this item.
  713. 'icon': icon_file,
  714. # .app bundle identifier. Only OSX uses this item.
  715. 'bundle_identifier': bundle_identifier,
  716. # Target architecture (macOS only)
  717. 'target_arch': target_arch,
  718. # Code signing identity (macOS only)
  719. 'codesign_identity': codesign_identity,
  720. # Entitlements file (macOS only)
  721. 'entitlements_file': entitlements_file,
  722. # Windows assembly searching options
  723. 'win_no_prefer_redirects': win_no_prefer_redirects,
  724. 'win_private_assemblies': win_private_assemblies,
  725. # splash screen
  726. 'splash_init': splash_init,
  727. 'splash_target': splash_target,
  728. 'splash_binaries': splash_binaries,
  729. }
  730. # Write down .spec file to filesystem.
  731. specfnm = os.path.join(specpath, name + '.spec')
  732. with open(specfnm, 'w', encoding='utf-8') as specfile:
  733. if onefile:
  734. specfile.write(onefiletmplt % d)
  735. # For Mac OS create .app bundle.
  736. if is_darwin and not console:
  737. specfile.write(bundleexetmplt % d)
  738. else:
  739. specfile.write(onedirtmplt % d)
  740. # For Mac OS create .app bundle.
  741. if is_darwin and not console:
  742. specfile.write(bundletmplt % d)
  743. return specfnm