Skip to content

Commit ba51fdd

Browse files
committed
Ux and function fix
1 parent 4ece21b commit ba51fdd

File tree

2,505 files changed

+76515
-39
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,505 files changed

+76515
-39
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#-----------------------------------------------------------------------------
2+
# Copyright (c) 2005-2023, PyInstaller Development Team.
3+
#
4+
# Distributed under the terms of the GNU General Public License (version 2
5+
# or later) with exception for distributing the bootloader.
6+
#
7+
# The full license is in the file COPYING.txt, distributed with this software.
8+
#
9+
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
10+
#-----------------------------------------------------------------------------
11+
12+
__all__ = ('HOMEPATH', 'PLATFORM', '__version__', 'DEFAULT_DISTPATH', 'DEFAULT_SPECPATH', 'DEFAULT_WORKPATH')
13+
14+
import os
15+
import sys
16+
17+
from PyInstaller import compat
18+
from PyInstaller.utils.git import get_repo_revision
19+
20+
# Note: Keep this variable as plain string so it could be updated automatically when doing a release.
21+
__version__ = '6.10.0'
22+
23+
# Absolute path of this package's directory. Save this early so all submodules can use the absolute path. This is
24+
# required for example if the current directory changes prior to loading the hooks.
25+
PACKAGEPATH = os.path.abspath(os.path.dirname(__file__))
26+
27+
HOMEPATH = os.path.dirname(PACKAGEPATH)
28+
29+
# Update __version__ as necessary.
30+
if os.path.exists(os.path.join(HOMEPATH, 'setup.py')):
31+
# PyInstaller is run directly from source without installation, or __version__ is called from 'setup.py'...
32+
if compat.getenv('PYINSTALLER_DO_RELEASE') == '1':
33+
# Suppress the git revision when doing a release.
34+
pass
35+
elif 'sdist' not in sys.argv:
36+
# and 'setup.py' was not called with 'sdist' argument. For creating source tarball we do not want git revision
37+
# in the filename.
38+
try:
39+
__version__ += get_repo_revision()
40+
except Exception:
41+
# Write to stderr because stdout is used for eval() statement in some subprocesses.
42+
sys.stderr.write('WARN: failed to parse git revision')
43+
else:
44+
# PyInstaller was installed by `python setup.py install'.
45+
from importlib.metadata import version
46+
__version__ = version('PyInstaller')
47+
# Default values of paths where to put files created by PyInstaller. If changing these, do not forget to update the
48+
# help text for corresponding command-line options, defined in build_main.
49+
50+
# Where to put created .spec file.
51+
DEFAULT_SPECPATH = os.getcwd()
52+
# Where to put the final frozen application.
53+
DEFAULT_DISTPATH = os.path.join(os.getcwd(), 'dist')
54+
# Where to put all the temporary files; .log, .pyz, etc.
55+
DEFAULT_WORKPATH = os.path.join(os.getcwd(), 'build')
56+
57+
PLATFORM = compat.system + '-' + compat.architecture
58+
# Include machine name in path to bootloader for some machines (e.g., 'arm'). Explicitly avoid doing this on macOS,
59+
# where we keep universal2 bootloaders in Darwin-64bit folder regardless of whether we are on x86_64 or arm64.
60+
if compat.machine and not compat.is_darwin:
61+
PLATFORM += '-' + compat.machine
62+
# Similarly, disambiguate musl Linux from glibc Linux.
63+
if compat.is_musl:
64+
PLATFORM += '-musl'
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
#-----------------------------------------------------------------------------
2+
# Copyright (c) 2013-2023, PyInstaller Development Team.
3+
#
4+
# Distributed under the terms of the GNU General Public License (version 2
5+
# or later) with exception for distributing the bootloader.
6+
#
7+
# The full license is in the file COPYING.txt, distributed with this software.
8+
#
9+
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
10+
#-----------------------------------------------------------------------------
11+
"""
12+
Main command-line interface to PyInstaller.
13+
"""
14+
from __future__ import annotations
15+
16+
import argparse
17+
import os
18+
import platform
19+
import sys
20+
import pathlib
21+
from collections import defaultdict
22+
23+
from PyInstaller import __version__
24+
from PyInstaller import log as logging
25+
# Note: do not import anything else until compat.check_requirements function is run!
26+
from PyInstaller import compat
27+
28+
try:
29+
from argcomplete import autocomplete
30+
except ImportError:
31+
32+
def autocomplete(parser):
33+
return None
34+
35+
36+
logger = logging.getLogger(__name__)
37+
38+
# Taken from https://stackoverflow.com/a/22157136 to format args more flexibly: any help text which beings with ``R|``
39+
# will have all newlines preserved; the help text will be line wrapped. See
40+
# https://docs.python.org/3/library/argparse.html#formatter-class.
41+
42+
43+
# This is used by the ``--debug`` option.
44+
class _SmartFormatter(argparse.HelpFormatter):
45+
def _split_lines(self, text, width):
46+
if text.startswith('R|'):
47+
# The underlying implementation of ``RawTextHelpFormatter._split_lines`` invokes this; mimic it.
48+
return text[2:].splitlines()
49+
else:
50+
# Invoke the usual formatter.
51+
return super()._split_lines(text, width)
52+
53+
54+
def run_makespec(filenames, **opts):
55+
# Split pathex by using the path separator
56+
temppaths = opts['pathex'][:]
57+
pathex = opts['pathex'] = []
58+
for p in temppaths:
59+
pathex.extend(p.split(os.pathsep))
60+
61+
import PyInstaller.building.makespec
62+
63+
spec_file = PyInstaller.building.makespec.main(filenames, **opts)
64+
logger.info('wrote %s' % spec_file)
65+
return spec_file
66+
67+
68+
def run_build(pyi_config, spec_file, **kwargs):
69+
import PyInstaller.building.build_main
70+
PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs)
71+
72+
73+
def __add_options(parser):
74+
parser.add_argument(
75+
'-v',
76+
'--version',
77+
action='version',
78+
version=__version__,
79+
help='Show program version info and exit.',
80+
)
81+
82+
83+
class _PyiArgumentParser(argparse.ArgumentParser):
84+
def __init__(self, *args, **kwargs):
85+
self._pyi_action_groups = defaultdict(list)
86+
super().__init__(*args, **kwargs)
87+
88+
def _add_options(self, __add_options: callable, name: str = ""):
89+
"""
90+
Mutate self with the given callable, storing any new actions added in a named group
91+
"""
92+
n_actions_before = len(getattr(self, "_actions", []))
93+
__add_options(self) # preserves old behavior
94+
new_actions = getattr(self, "_actions", [])[n_actions_before:]
95+
self._pyi_action_groups[name].extend(new_actions)
96+
97+
def _option_name(self, action):
98+
"""
99+
Get the option name(s) associated with an action
100+
101+
For options that define both short and long names, this function will
102+
return the long names joined by "/"
103+
"""
104+
longnames = [name for name in action.option_strings if name.startswith("--")]
105+
if longnames:
106+
name = "/".join(longnames)
107+
else:
108+
name = action.option_strings[0]
109+
return name
110+
111+
def _forbid_options(self, args: argparse.Namespace, group: str, errmsg: str = ""):
112+
"""Forbid options from a named action group"""
113+
options = defaultdict(str)
114+
for action in self._pyi_action_groups[group]:
115+
dest = action.dest
116+
name = self._option_name(action)
117+
if getattr(args, dest) is not self.get_default(dest):
118+
if dest in options:
119+
options[dest] += "/"
120+
options[dest] += name
121+
122+
# if any options from the forbidden group are not the default values,
123+
# the user must have passed them in, so issue an error report
124+
if options:
125+
sep = "\n "
126+
bad = sep.join(options.values())
127+
if errmsg:
128+
errmsg = "\n" + errmsg
129+
raise SystemExit(f"option(s) not allowed:{sep}{bad}{errmsg}")
130+
131+
132+
def generate_parser() -> _PyiArgumentParser:
133+
"""
134+
Build an argparse parser for PyInstaller's main CLI.
135+
"""
136+
137+
import PyInstaller.building.build_main
138+
import PyInstaller.building.makespec
139+
import PyInstaller.log
140+
141+
parser = _PyiArgumentParser(formatter_class=_SmartFormatter)
142+
parser.prog = "pyinstaller"
143+
144+
parser._add_options(__add_options)
145+
parser._add_options(PyInstaller.building.makespec.__add_options, name="makespec")
146+
parser._add_options(PyInstaller.building.build_main.__add_options, name="build_main")
147+
parser._add_options(PyInstaller.log.__add_options, name="log")
148+
149+
parser.add_argument(
150+
'filenames',
151+
metavar='scriptname',
152+
nargs='+',
153+
help="Name of scriptfiles to be processed or exactly one .spec file. If a .spec file is specified, most "
154+
"options are unnecessary and are ignored.",
155+
)
156+
157+
return parser
158+
159+
160+
def run(pyi_args: list | None = None, pyi_config: dict | None = None):
161+
"""
162+
pyi_args allows running PyInstaller programmatically without a subprocess
163+
pyi_config allows checking configuration once when running multiple tests
164+
"""
165+
compat.check_requirements()
166+
check_unsafe_privileges()
167+
168+
import PyInstaller.log
169+
170+
old_sys_argv = sys.argv
171+
try:
172+
parser = generate_parser()
173+
autocomplete(parser)
174+
if pyi_args is None:
175+
pyi_args = sys.argv[1:]
176+
try:
177+
index = pyi_args.index("--")
178+
except ValueError:
179+
index = len(pyi_args)
180+
args = parser.parse_args(pyi_args[:index])
181+
spec_args = pyi_args[index + 1:]
182+
PyInstaller.log.__process_options(parser, args)
183+
184+
# Print PyInstaller version, Python version, and platform as the first line to stdout. This helps us identify
185+
# PyInstaller, Python, and platform version when users report issues.
186+
try:
187+
from _pyinstaller_hooks_contrib import __version__ as contrib_hooks_version
188+
except Exception:
189+
contrib_hooks_version = 'unknown'
190+
191+
logger.info('PyInstaller: %s, contrib hooks: %s', __version__, contrib_hooks_version)
192+
logger.info('Python: %s%s', platform.python_version(), " (conda)" if compat.is_conda else "")
193+
logger.info('Platform: %s', platform.platform())
194+
logger.info('Python environment: %s', sys.prefix)
195+
196+
# Skip creating .spec when .spec file is supplied.
197+
if args.filenames[0].endswith('.spec'):
198+
parser._forbid_options(
199+
args, group="makespec", errmsg="makespec options not valid when a .spec file is given"
200+
)
201+
spec_file = args.filenames[0]
202+
else:
203+
# Ensure that the given script files exist, before trying to generate the .spec file.
204+
# This prevents us from overwriting an existing (and customized) .spec file if user makes a typo in the
205+
# .spec file's suffix when trying to build it, for example, `pyinstaller program.cpes` (see #8276).
206+
# It also prevents creation of a .spec file when `pyinstaller program.py` is accidentally ran from a
207+
# directory that does not contain the script (for example, due to failing to change the directory prior
208+
# to running the command).
209+
for filename in args.filenames:
210+
if not os.path.isfile(filename):
211+
raise SystemExit(f"Script file {filename!r} does not exist.")
212+
spec_file = run_makespec(**vars(args))
213+
214+
sys.argv = [spec_file, *spec_args]
215+
run_build(pyi_config, spec_file, **vars(args))
216+
217+
except KeyboardInterrupt:
218+
raise SystemExit("Aborted by user request.")
219+
except RecursionError:
220+
from PyInstaller import _recursion_too_deep_message
221+
_recursion_too_deep_message.raise_with_msg()
222+
finally:
223+
sys.argv = old_sys_argv
224+
225+
226+
def _console_script_run():
227+
# Python prepends the main script's parent directory to sys.path. When PyInstaller is ran via the usual
228+
# `pyinstaller` CLI entry point, this directory is $pythonprefix/bin which should not be in sys.path.
229+
if os.path.basename(sys.path[0]) in ("bin", "Scripts"):
230+
sys.path.pop(0)
231+
run()
232+
233+
234+
def check_unsafe_privileges():
235+
"""
236+
Forbid dangerous usage of PyInstaller with escalated privileges
237+
"""
238+
if compat.is_win:
239+
# Discourage (with the intention to eventually block) people using *run as admin* with PyInstaller.
240+
# There are 4 cases, block case 3 but be careful not to also block case 2.
241+
# 1. User has no admin access: TokenElevationTypeDefault
242+
# 2. User is an admin/UAC disabled (common on CI/VMs): TokenElevationTypeDefault
243+
# 3. User has used *run as administrator* to elevate: TokenElevationTypeFull
244+
# 4. User can escalate but hasn't: TokenElevationTypeLimited
245+
# See https://techcommunity.microsoft.com/t5/windows-blog-archive/how-to-determine-if-a-user-is-a-member-of-the-administrators/ba-p/228476
246+
import ctypes
247+
248+
advapi32 = ctypes.CDLL("Advapi32.dll")
249+
kernel32 = ctypes.CDLL("kernel32.dll")
250+
251+
kernel32.GetCurrentProcess.restype = ctypes.c_void_p
252+
process = kernel32.GetCurrentProcess()
253+
254+
token = ctypes.c_void_p()
255+
try:
256+
TOKEN_QUERY = 8
257+
assert advapi32.OpenProcessToken(ctypes.c_void_p(process), TOKEN_QUERY, ctypes.byref(token))
258+
259+
elevation_type = ctypes.c_int()
260+
TokenElevationType = 18
261+
assert advapi32.GetTokenInformation(
262+
token, TokenElevationType, ctypes.byref(elevation_type), ctypes.sizeof(elevation_type),
263+
ctypes.byref(ctypes.c_int())
264+
)
265+
finally:
266+
kernel32.CloseHandle(token)
267+
268+
if elevation_type.value == 2: # TokenElevationTypeFull
269+
logger.log(
270+
logging.DEPRECATION,
271+
"Running PyInstaller as admin is not necessary nor sensible. Run PyInstaller from a non-administrator "
272+
"terminal. PyInstaller 7.0 will block this."
273+
)
274+
275+
elif compat.is_darwin or compat.is_linux:
276+
# Discourage (with the intention to eventually block) people using *sudo* with PyInstaller.
277+
# Again there are 4 cases, block only case 4.
278+
# 1. Non-root: os.getuid() != 0
279+
# 2. Logged in as root (usually a VM): os.getlogin() == "root", os.getuid() == 0
280+
# 3. No named users (e.g. most Docker containers): os.getlogin() fails
281+
# 4. Regular user using escalation: os.getlogin() != "root", os.getuid() == 0
282+
try:
283+
user = os.getlogin()
284+
except OSError:
285+
user = ""
286+
if os.getuid() == 0 and user and user != "root":
287+
logger.log(
288+
logging.DEPRECATION,
289+
"Running PyInstaller as root is not necessary nor sensible. Do not use PyInstaller with sudo. "
290+
"PyInstaller 7.0 will block this."
291+
)
292+
293+
if compat.is_win:
294+
# Do not let people run PyInstaller from admin cmd's default working directory (C:\Windows\system32)
295+
try:
296+
pathlib.Path().resolve().relative_to(r"C:\Windows")
297+
except ValueError:
298+
pass
299+
else:
300+
raise SystemExit(
301+
f"Error: Do not run pyinstaller from {pathlib.Path().resolve()}. cd to where your code is and run "
302+
"pyinstaller from there. Hint: You can open a terminal where your code is by going to the parent "
303+
"folder in Windows file explorer then typing cmd into the address bar."
304+
)
305+
306+
307+
if __name__ == '__main__':
308+
run()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)