Files

256 lines
8.3 KiB
Python

"""cxfreeze command line tool."""
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
from cx_Freeze import __version__, setup
from cx_Freeze._pyproject import get_pyproject_tool_data
__all__ = ["main"]
DESCRIPTION = """
Freeze a Python script and all of its referenced modules to a base \
executable which can then be distributed without requiring a Python \
installation.
"""
VERSION = f"""
%(prog)s {__version__}
Copyright (c) 2020-2024 Marcelo Duarte. All rights reserved.
Copyright (c) 2007-2019 Anthony Tuininga. All rights reserved.
Copyright (c) 2001-2006 Computronix Corporation. All rights reserved.
"""
EPILOG = """
Note:
* Windows only options are ignored by other OS and \
when used by Python app from Microsoft Store.
Additional help:
%(prog)s build_exe --help
Linux and similar OS:
%(prog)s bdist_appimage --help
%(prog)s bdist_deb --help
%(prog)s bdist_rpm --help
macOS:
%(prog)s bdist_dmg --help
%(prog)s bdist_mac --help
Windows:
%(prog)s bdist_msi --help
"""
def prepare_parser() -> argparse.ArgumentParser:
"""Helper function to parse the arguments."""
parser = argparse.ArgumentParser(
prog="cxfreeze",
description=DESCRIPTION,
epilog=EPILOG,
formatter_class=argparse.RawDescriptionHelpFormatter,
add_help=False,
)
# Executable parameters
parser.add_argument(
"--script",
metavar="NAME",
help="the name of the file containing the script which is to be "
"frozen",
)
parser.add_argument(
"--init-script",
metavar="NAME",
help="script which will be executed upon startup; if the name of the "
"file is not an absolute file name, the subdirectory initscripts "
"(rooted in the directory in which the cx_Freeze package is found) "
"will be searched for a file matching the name",
)
parser.add_argument(
"--base",
"--base-name",
metavar="NAME",
help="the name of the base executable; the pre-defined values are: "
'"console", "gui" and "service"; a user-defined base is accepted '
"if it is given with an absolute path name [default: console]",
)
parser.add_argument(
"--target-name",
metavar="NAME",
help="the name of the target executable; the default value is the "
"name of the script; it is recommended NOT to use an extension "
"(automatically added on Windows); target-name with version is "
"supported; if specified a path, raise an error",
)
parser.add_argument(
"--target-dir",
metavar="DIR",
help="directory for built executables and dependent files",
)
parser.add_argument(
"--icon",
metavar="NAME",
help="name of icon which should be included in the executable itself "
"on Windows or placed in the target directory for other platforms; "
"it is recommended NOT to use an extension (automatically added "
'".ico" on Windows, ".icns" on macOS and ".png" or ".svg" on Linux '
"and others)",
)
parser.add_argument(
"--manifest",
metavar="NAME",
help="name of manifest which should be included in the executable "
"itself (Windows only)",
)
parser.add_argument(
"--uac-admin",
action="store_true",
help="creates a manifest for an application that will request "
"elevation (Windows only)",
)
parser.add_argument(
"--uac-uiaccess",
action="store_true",
dest="uac_uiaccess",
help="changes the application manifest to bypass user interface "
"control (Windows only)",
)
parser.add_argument(
"--shortcut-name",
metavar="NAME",
help="the name to give a shortcut for the executable when included in "
"an MSI package (Windows only)",
)
parser.add_argument(
"--shortcut-dir",
metavar="DIR",
help="the directory in which to place the shortcut when being instal"
"led by an MSI package; see the MSI Shortcut table documentation for "
"more information on what values can be placed here (Windows only)",
)
parser.add_argument(
"--copyright",
help="the copyright value to include in the version resource "
"associated with executable (Windows only)",
)
parser.add_argument(
"--trademarks",
help="the trademarks value to include in the version resource "
"associated with the executable (Windows only)",
)
# Command positional parameter
parser.add_argument(
"command",
nargs=argparse.OPTIONAL,
metavar="COMMAND",
help="build, build_exe or supported bdist commands (and to be "
"backwards compatible, can replace --script option)",
)
# Version
parser.add_argument("--version", action="version", version=VERSION)
return parser
def main() -> None:
"""Entry point for cxfreeze command line tool."""
sys.setrecursionlimit(sys.getrecursionlimit() * 10)
parser = prepare_parser()
args, argv = parser.parse_known_args()
script = args.script
command = args.command
# help
if "-h" in argv or "--help" in argv:
if command is None:
parser.print_help()
else:
setup(
executables=None,
script_args=[command, "--help"],
script_name=parser.prog,
)
parser.exit()
# usage
deprecated = []
if script is None:
if command is None:
parser.error("--script or command must be specified")
elif not command.startswith(("build", "bdist", "install")):
args.script, command = command, script # backwards compatible
deprecated.append("usage: required to use --script NAME")
if command is None:
command = "build_exe"
# deprecated options
if command == "build_exe" or "build_exe" in argv:
args_to_replace = [
("--install-dir", "--build-exe"),
("--exclude-modules", "--excludes"),
("--include-modules", "--includes"),
("-c", None),
("--compress", None),
("-OO", "--optimize=2"), # test -OO before -O
("-O", "--optimize=1"),
("-z", "--zip-includes"),
("--default-path", "--path"),
("-s", "--silent"),
]
new_argv = []
for arg in argv:
new_argv.append(arg)
for search, replace in args_to_replace:
if arg.startswith(search):
new_argv.pop()
if replace is None:
deprecated.append(f"{search} option removed")
else:
new_argv.append(arg.replace(search, replace))
deprecated.append(
f"{search} option replaced by {replace}"
)
break
argv = new_argv
# redirected options
if args.target_dir:
argv.append(f"--build-exe={args.target_dir}")
delattr(args, "target_dir")
# finalize command line options
executables = []
script_args = [command, *argv]
if args.script:
delattr(args, "command")
executables = [vars(args)]
if script_args[0] == "build" and "build_exe" not in script_args:
script_args.insert(1, "build_exe")
# fix sys.path for cxfreeze command line
command = Path(sys.argv[0])
if command.stem == "cxfreeze":
path_to_remove = os.fspath(command.parent)
if path_to_remove in sys.path:
sys.path.remove(path_to_remove)
sys.path.insert(0, os.getcwd())
# get options from pyproject.toml
options = get_pyproject_tool_data()
executables.extend(options.pop("executables", []))
setup(
command_options=options,
executables=executables,
script_args=script_args,
script_name=parser.prog,
)
if deprecated:
for warning_msg in deprecated:
print("WARNING: deprecated", warning_msg)