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