diff --git a/.azure-pipelines/windows-layout-steps.yml b/.azure-pipelines/windows-layout-steps.yml
index afd897817904947..e15729fac3443db 100644
--- a/.azure-pipelines/windows-layout-steps.yml
+++ b/.azure-pipelines/windows-layout-steps.yml
@@ -12,7 +12,7 @@ steps:
displayName: Show layout info (${{ parameters.kind }})
- ${{ if eq(parameters.fulltest, 'true') }}:
- - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results-${{ parameters.kind }}.xml" --tempdir "$(Build.BinariesDirectory)\tmp-${{ parameters.kind }}-$(arch)" -i test_launcher
+ - script: .\python.exe -m test -q -uall -u-cpu -rwW --slowest --timeout=1200 -j0 --junit-xml="$(Build.BinariesDirectory)\test-results-${{ parameters.kind }}.xml" --tempdir "$(Build.BinariesDirectory)\tmp-${{ parameters.kind }}-$(arch)"
workingDirectory: $(Build.BinariesDirectory)\layout-${{ parameters.kind }}-$(arch)
displayName: ${{ parameters.kind }} Tests
env:
diff --git a/.gitattributes b/.gitattributes
index 4d52f900b93179e..f00d82e6978c40b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -19,7 +19,7 @@
*.zip binary
# Specific binary files
-PC/classicAppCompat.* binary
+# -- None right now --
# Text files that should not be subject to eol conversion
[attr]noeol -text
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 0cdf17838b5798f..fd929b0c468699b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -188,12 +188,8 @@ Tools/wasm/wasi @brettcannon @emmatyping @savannahostrowski
PC/ @python/windows-team
PCbuild/ @python/windows-team
-# Windows installer packages
-Tools/msi/ @python/windows-team
-Tools/nuget/ @python/windows-team
-
-# Windows Launcher
-PC/launcher.c @python/windows-team @vsajip
+# Windows Venv launcher/redirector
+PC/venvlauncher.c @python/windows-team @vsajip
# ----------------------------------------------------------------------------
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b541dbd0c23d326..0e5f1fbf530eed8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -181,22 +181,6 @@ jobs:
free-threading: ${{ matrix.free-threading }}
interpreter: ${{ matrix.interpreter }}
- build-windows-msi:
- # ${{ '' } is a hack to nest jobs under the same sidebar category.
- name: Windows MSI${{ '' }} # zizmor: ignore[obfuscation]
- needs: build-context
- if: fromJSON(needs.build-context.outputs.run-windows-msi)
- strategy:
- fail-fast: false
- matrix:
- arch:
- - x86
- - x64
- - arm64
- uses: ./.github/workflows/reusable-windows-msi.yml
- with:
- arch: ${{ matrix.arch }}
-
build-macos:
name: >-
macOS
@@ -659,7 +643,6 @@ jobs:
- check-generated-files
- check-c-api-docs
- build-windows
- - build-windows-msi
- build-macos
- build-ubuntu
- build-ubuntu-ssltests
@@ -680,7 +663,6 @@ jobs:
allowed-failures: >-
build-android,
build-emscripten,
- build-windows-msi,
build-ubuntu-ssltests,
test-hypothesis,
cifuzz,
diff --git a/.github/workflows/reusable-context.yml b/.github/workflows/reusable-context.yml
index 6595aef0377e500..c998cbff181dd12 100644
--- a/.github/workflows/reusable-context.yml
+++ b/.github/workflows/reusable-context.yml
@@ -47,9 +47,6 @@ on: # yamllint disable-line rule:truthy
run-wasi:
description: Whether to run the WASI tests
value: ${{ jobs.compute-changes.outputs.run-wasi }} # bool
- run-windows-msi:
- description: Whether to run the MSI installer smoke tests
- value: ${{ jobs.compute-changes.outputs.run-windows-msi }} # bool
run-windows-tests:
description: Whether to run the Windows tests
value: ${{ jobs.compute-changes.outputs.run-windows-tests }} # bool
@@ -73,7 +70,6 @@ jobs:
run-ubuntu: ${{ steps.changes.outputs.run-ubuntu }}
run-emscripten: ${{ steps.changes.outputs.run-emscripten }}
run-wasi: ${{ steps.changes.outputs.run-wasi }}
- run-windows-msi: ${{ steps.changes.outputs.run-windows-msi }}
run-windows-tests: ${{ steps.changes.outputs.run-windows-tests }}
steps:
- name: Set up Python
diff --git a/.github/workflows/reusable-windows-msi.yml b/.github/workflows/reusable-windows-msi.yml
deleted file mode 100644
index d57046ae5ca8f00..000000000000000
--- a/.github/workflows/reusable-windows-msi.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: Reusable Windows MSI
-
-on:
- workflow_call:
- inputs:
- arch:
- description: CPU architecture
- required: true
- type: string
-
-permissions:
- contents: read
-
-env:
- FORCE_COLOR: 1
-
-jobs:
- build:
- name: installer for ${{ inputs.arch }}
- runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2025' }}
- timeout-minutes: 60
- env:
- ARCH: ${{ inputs.arch }}
- IncludeFreethreaded: true
- steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- with:
- persist-credentials: false
- - name: Build CPython installer
- run: ./Tools/msi/build.bat --doc -"${ARCH}"
- shell: bash
diff --git a/.gitignore b/.gitignore
index 118eb5ee76e8051..2a8dd6a7c3eabd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,7 +50,7 @@ gmon.out
*.exe
-# Ignore core dumps... but not Tools/msi/core/ or the like.
+# Ignore core dumps... but not .../core/ subdirectories
core
!core/
@@ -158,7 +158,6 @@ Tools/unicode/data/
/coverage/
/externals/
/htmlcov/
-Tools/msi/obj
Tools/ssl/amd64
Tools/ssl/win32
Tools/freeze/test/outdir
diff --git a/Include/internal/pycore_magic_number.h b/Include/internal/pycore_magic_number.h
index 53ed570f99f118a..e0c5cfc02bbc359 100644
--- a/Include/internal/pycore_magic_number.h
+++ b/Include/internal/pycore_magic_number.h
@@ -310,9 +310,6 @@ Known values:
You should always use the *upcoming* tag. For example, if 3.12a6 came out
a week ago, I should put "Python 3.12a7" next to my new magic number.
-Whenever PYC_MAGIC_NUMBER is changed, the ranges in the magic_values array in
-PC/launcher.c must also be updated.
-
*/
#define PYC_MAGIC_NUMBER 3704
diff --git a/InternalDocs/interpreter.md b/InternalDocs/interpreter.md
index 7fc41a807dd5667..fa89996aa654291 100644
--- a/InternalDocs/interpreter.md
+++ b/InternalDocs/interpreter.md
@@ -307,9 +307,7 @@ With a new bytecode you must also change what is called the "magic number" for
.pyc files: bump the value of the variable `MAGIC_NUMBER` in
[`Lib/importlib/_bootstrap_external.py`](../Lib/importlib/_bootstrap_external.py).
Changing this number will lead to all .pyc files with the old `MAGIC_NUMBER`
-to be recompiled by the interpreter on import. Whenever `MAGIC_NUMBER` is
-changed, the ranges in the `magic_values` array in
-[`PC/launcher.c`](../PC/launcher.c) may also need to be updated. Changes to
+to be recompiled by the interpreter on import. Changes to
[`Lib/importlib/_bootstrap_external.py`](../Lib/importlib/_bootstrap_external.py)
will take effect only after running `make regen-importlib`.
diff --git a/Lib/test/test_launcher.py b/Lib/test/test_launcher.py
deleted file mode 100644
index 795890bd7e4b473..000000000000000
--- a/Lib/test/test_launcher.py
+++ /dev/null
@@ -1,804 +0,0 @@
-import contextlib
-import itertools
-import os
-import re
-import shutil
-import subprocess
-import sys
-import sysconfig
-import tempfile
-import unittest
-from pathlib import Path
-from test import support
-
-if sys.platform != "win32":
- raise unittest.SkipTest("test only applies to Windows")
-
-# Get winreg after the platform check
-import winreg
-
-
-PY_EXE = "py.exe"
-DEBUG_BUILD = False
-if sys.executable.casefold().endswith("_d.exe".casefold()):
- PY_EXE = "py_d.exe"
- DEBUG_BUILD = True
-
-# Registry data to create. On removal, everything beneath top-level names will
-# be deleted.
-TEST_DATA = {
- "PythonTestSuite": {
- "DisplayName": "Python Test Suite",
- "SupportUrl": "https://www.python.org/",
- "3.100": {
- "DisplayName": "X.Y version",
- "InstallPath": {
- None: sys.prefix,
- "ExecutablePath": "X.Y.exe",
- }
- },
- "3.100-32": {
- "DisplayName": "X.Y-32 version",
- "InstallPath": {
- None: sys.prefix,
- "ExecutablePath": "X.Y-32.exe",
- }
- },
- "3.100-arm64": {
- "DisplayName": "X.Y-arm64 version",
- "InstallPath": {
- None: sys.prefix,
- "ExecutablePath": "X.Y-arm64.exe",
- "ExecutableArguments": "-X fake_arg_for_test",
- }
- },
- "ignored": {
- "DisplayName": "Ignored because no ExecutablePath",
- "InstallPath": {
- None: sys.prefix,
- }
- },
- },
- "PythonTestSuite1": {
- "DisplayName": "Python Test Suite Single",
- "3.100": {
- "DisplayName": "Single Interpreter",
- "InstallPath": {
- None: sys.prefix,
- "ExecutablePath": sys.executable,
- }
- }
- },
-}
-
-
-TEST_PY_ENV = dict(
- PY_PYTHON="PythonTestSuite/3.100",
- PY_PYTHON2="PythonTestSuite/3.100-32",
- PY_PYTHON3="PythonTestSuite/3.100-arm64",
-)
-
-
-TEST_PY_DEFAULTS = "\n".join([
- "[defaults]",
- *[f"{k[3:].lower()}={v}" for k, v in TEST_PY_ENV.items()],
-])
-
-
-TEST_PY_COMMANDS = "\n".join([
- "[commands]",
- "test-command=TEST_EXE.exe",
-])
-
-
-def quote(s):
- s = str(s)
- return f'"{s}"' if " " in s else s
-
-
-def create_registry_data(root, data):
- def _create_registry_data(root, key, value):
- if isinstance(value, dict):
- # For a dict, we recursively create keys
- with winreg.CreateKeyEx(root, key) as hkey:
- for k, v in value.items():
- _create_registry_data(hkey, k, v)
- elif isinstance(value, str):
- # For strings, we set values. 'key' may be None in this case
- winreg.SetValueEx(root, key, None, winreg.REG_SZ, value)
- else:
- raise TypeError("don't know how to create data for '{}'".format(value))
-
- for k, v in data.items():
- _create_registry_data(root, k, v)
-
-
-def enum_keys(root):
- for i in itertools.count():
- try:
- yield winreg.EnumKey(root, i)
- except OSError as ex:
- if ex.winerror == 259:
- break
- raise
-
-
-def delete_registry_data(root, keys):
- ACCESS = winreg.KEY_WRITE | winreg.KEY_ENUMERATE_SUB_KEYS
- for key in list(keys):
- with winreg.OpenKey(root, key, access=ACCESS) as hkey:
- delete_registry_data(hkey, enum_keys(hkey))
- winreg.DeleteKey(root, key)
-
-
-def is_installed(tag):
- key = rf"Software\Python\PythonCore\{tag}\InstallPath"
- for root, flag in [
- (winreg.HKEY_CURRENT_USER, 0),
- (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY),
- (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY),
- ]:
- try:
- winreg.CloseKey(winreg.OpenKey(root, key, access=winreg.KEY_READ | flag))
- return True
- except OSError:
- pass
- return False
-
-
-class PreservePyIni:
- def __init__(self, path, content):
- self.path = Path(path)
- self.content = content
- self._preserved = None
-
- def __enter__(self):
- try:
- self._preserved = self.path.read_bytes()
- except FileNotFoundError:
- self._preserved = None
- self.path.write_text(self.content, encoding="utf-16")
-
- def __exit__(self, *exc_info):
- if self._preserved is None:
- self.path.unlink()
- else:
- self.path.write_bytes(self._preserved)
-
-
-class RunPyMixin:
- py_exe = None
-
- @classmethod
- def find_py(cls):
- py_exe = None
- if sysconfig.is_python_build():
- py_exe = Path(sys.executable).parent / PY_EXE
- else:
- for p in os.getenv("PATH").split(";"):
- if p:
- py_exe = Path(p) / PY_EXE
- if py_exe.is_file():
- break
- else:
- py_exe = None
-
- # Test launch and check version, to exclude installs of older
- # releases when running outside of a source tree
- if py_exe:
- try:
- with subprocess.Popen(
- [py_exe, "-h"],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- encoding="ascii",
- errors="ignore",
- ) as p:
- p.stdin.close()
- version = next(p.stdout, "\n").splitlines()[0].rpartition(" ")[2]
- p.stdout.read()
- p.wait(10)
- if not sys.version.startswith(version):
- py_exe = None
- except OSError:
- py_exe = None
-
- if not py_exe:
- raise unittest.SkipTest(
- "cannot locate '{}' for test".format(PY_EXE)
- )
- return py_exe
-
- def get_py_exe(self):
- if not self.py_exe:
- self.py_exe = self.find_py()
- return self.py_exe
-
- def run_py(self, args, env=None, allow_fail=False, expect_returncode=0, argv=None):
- if not self.py_exe:
- self.py_exe = self.find_py()
-
- ignore = {"VIRTUAL_ENV", "PY_PYTHON", "PY_PYTHON2", "PY_PYTHON3"}
- env = {
- **{k.upper(): v for k, v in os.environ.items() if k.upper() not in ignore},
- "PYLAUNCHER_DEBUG": "1",
- "PYLAUNCHER_DRYRUN": "1",
- "PYLAUNCHER_LIMIT_TO_COMPANY": "",
- **{k.upper(): v for k, v in (env or {}).items()},
- }
- if ini_dir := getattr(self, '_ini_dir', None):
- env.setdefault("_PYLAUNCHER_INIDIR", ini_dir)
- if not argv:
- argv = [self.py_exe, *args]
- with subprocess.Popen(
- argv,
- env=env,
- executable=self.py_exe,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- ) as p:
- p.stdin.close()
- p.wait(10)
- out = p.stdout.read().decode("utf-8", "replace")
- err = p.stderr.read().decode("ascii", "replace").replace("\uFFFD", "?")
- if p.returncode != expect_returncode and support.verbose and not allow_fail:
- print("++ COMMAND ++")
- print([self.py_exe, *args])
- print("++ STDOUT ++")
- print(out)
- print("++ STDERR ++")
- print(err)
- if allow_fail and p.returncode != expect_returncode:
- raise subprocess.CalledProcessError(p.returncode, [self.py_exe, *args], out, err)
- else:
- self.assertEqual(expect_returncode, p.returncode)
- data = {
- s.partition(":")[0]: s.partition(":")[2].lstrip()
- for s in err.splitlines()
- if not s.startswith("#") and ":" in s
- }
- data["stdout"] = out
- data["stderr"] = err
- return data
-
- def py_ini(self, content):
- ini_dir = getattr(self, '_ini_dir', None)
- if not ini_dir:
- local_appdata = os.environ.get("LOCALAPPDATA")
- if not local_appdata:
- raise unittest.SkipTest("LOCALAPPDATA environment variable is "
- "missing or empty")
- ini_dir = local_appdata
- return PreservePyIni(Path(ini_dir) / "py.ini", content)
-
- @contextlib.contextmanager
- def script(self, content, encoding="utf-8"):
- file = Path(tempfile.mktemp(dir=os.getcwd()) + ".py")
- if isinstance(content, bytes):
- file.write_bytes(content)
- else:
- file.write_text(content, encoding=encoding)
- try:
- yield file
- finally:
- file.unlink()
-
- @contextlib.contextmanager
- def fake_venv(self):
- venv = Path.cwd() / "Scripts"
- venv.mkdir(exist_ok=True, parents=True)
- venv_exe = (venv / ("python_d.exe" if DEBUG_BUILD else "python.exe"))
- venv_exe.touch()
- try:
- yield venv_exe, {"VIRTUAL_ENV": str(venv.parent)}
- finally:
- shutil.rmtree(venv)
-
-
-class TestLauncher(unittest.TestCase, RunPyMixin):
- @classmethod
- def setUpClass(cls):
- with winreg.CreateKey(winreg.HKEY_CURRENT_USER, rf"Software\Python") as key:
- create_registry_data(key, TEST_DATA)
-
- if support.verbose:
- p = subprocess.check_output("reg query HKCU\\Software\\Python /s")
- #print(p.decode('mbcs'))
-
- cls._ini_dir = tempfile.mkdtemp()
- cls.addClassCleanup(shutil.rmtree, cls._ini_dir, ignore_errors=True)
-
- @classmethod
- def tearDownClass(cls):
- with winreg.OpenKey(winreg.HKEY_CURRENT_USER, rf"Software\Python", access=winreg.KEY_WRITE | winreg.KEY_ENUMERATE_SUB_KEYS) as key:
- delete_registry_data(key, TEST_DATA)
-
-
- def test_version(self):
- data = self.run_py(["-0"])
- self.assertEqual(self.py_exe, Path(data["argv0"]))
- self.assertEqual(sys.version.partition(" ")[0], data["version"])
-
- def test_help_option(self):
- data = self.run_py(["-h"])
- self.assertEqual("True", data["SearchInfo.help"])
-
- def test_list_option(self):
- for opt, v1, v2 in [
- ("-0", "True", "False"),
- ("-0p", "False", "True"),
- ("--list", "True", "False"),
- ("--list-paths", "False", "True"),
- ]:
- with self.subTest(opt):
- data = self.run_py([opt])
- self.assertEqual(v1, data["SearchInfo.list"])
- self.assertEqual(v2, data["SearchInfo.listPaths"])
-
- def test_list(self):
- data = self.run_py(["--list"])
- found = {}
- expect = {}
- for line in data["stdout"].splitlines():
- m = re.match(r"\s*(.+?)\s+?(\*\s+)?(.+)$", line)
- if m:
- found[m.group(1)] = m.group(3)
- for company in TEST_DATA:
- company_data = TEST_DATA[company]
- tags = [t for t in company_data if isinstance(company_data[t], dict)]
- for tag in tags:
- arg = f"-V:{company}/{tag}"
- expect[arg] = company_data[tag]["DisplayName"]
- expect.pop(f"-V:{company}/ignored", None)
-
- actual = {k: v for k, v in found.items() if k in expect}
- try:
- self.assertDictEqual(expect, actual)
- except:
- if support.verbose:
- print("*** STDOUT ***")
- print(data["stdout"])
- raise
-
- def test_list_paths(self):
- data = self.run_py(["--list-paths"])
- found = {}
- expect = {}
- for line in data["stdout"].splitlines():
- m = re.match(r"\s*(.+?)\s+?(\*\s+)?(.+)$", line)
- if m:
- found[m.group(1)] = m.group(3)
- for company in TEST_DATA:
- company_data = TEST_DATA[company]
- tags = [t for t in company_data if isinstance(company_data[t], dict)]
- for tag in tags:
- arg = f"-V:{company}/{tag}"
- install = company_data[tag]["InstallPath"]
- try:
- expect[arg] = install["ExecutablePath"]
- try:
- expect[arg] += " " + install["ExecutableArguments"]
- except KeyError:
- pass
- except KeyError:
- expect[arg] = str(Path(install[None]) / Path(sys.executable).name)
-
- expect.pop(f"-V:{company}/ignored", None)
-
- actual = {k: v for k, v in found.items() if k in expect}
- try:
- self.assertDictEqual(expect, actual)
- except:
- if support.verbose:
- print("*** STDOUT ***")
- print(data["stdout"])
- raise
-
- def test_filter_to_company(self):
- company = "PythonTestSuite"
- data = self.run_py([f"-V:{company}/"])
- self.assertEqual("X.Y.exe", data["LaunchCommand"])
- self.assertEqual(company, data["env.company"])
- self.assertEqual("3.100", data["env.tag"])
-
- def test_filter_to_company_with_default(self):
- company = "PythonTestSuite"
- data = self.run_py([f"-V:{company}/"], env=dict(PY_PYTHON="3.0"))
- self.assertEqual("X.Y.exe", data["LaunchCommand"])
- self.assertEqual(company, data["env.company"])
- self.assertEqual("3.100", data["env.tag"])
-
- def test_filter_to_tag(self):
- company = "PythonTestSuite"
- data = self.run_py(["-V:3.100"])
- self.assertEqual("X.Y.exe", data["LaunchCommand"])
- self.assertEqual(company, data["env.company"])
- self.assertEqual("3.100", data["env.tag"])
-
- data = self.run_py(["-V:3.100-32"])
- self.assertEqual("X.Y-32.exe", data["LaunchCommand"])
- self.assertEqual(company, data["env.company"])
- self.assertEqual("3.100-32", data["env.tag"])
-
- data = self.run_py(["-V:3.100-arm64"])
- self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test", data["LaunchCommand"])
- self.assertEqual(company, data["env.company"])
- self.assertEqual("3.100-arm64", data["env.tag"])
-
- def test_filter_to_company_and_tag(self):
- company = "PythonTestSuite"
- data = self.run_py([f"-V:{company}/3.1"], expect_returncode=103)
-
- data = self.run_py([f"-V:{company}/3.100"])
- self.assertEqual("X.Y.exe", data["LaunchCommand"])
- self.assertEqual(company, data["env.company"])
- self.assertEqual("3.100", data["env.tag"])
-
- def test_filter_with_single_install(self):
- company = "PythonTestSuite1"
- data = self.run_py(
- ["-V:Nonexistent"],
- env={"PYLAUNCHER_LIMIT_TO_COMPANY": company},
- expect_returncode=103,
- )
-
- def test_search_major_3(self):
- try:
- data = self.run_py(["-3"], allow_fail=True)
- except subprocess.CalledProcessError:
- raise unittest.SkipTest("requires at least one Python 3.x install")
- self.assertEqual("PythonCore", data["env.company"])
- self.assertStartsWith(data["env.tag"], "3.")
-
- def test_search_major_3_32(self):
- try:
- data = self.run_py(["-3-32"], allow_fail=True)
- except subprocess.CalledProcessError:
- if not any(is_installed(f"3.{i}-32") for i in range(5, 11)):
- raise unittest.SkipTest("requires at least one 32-bit Python 3.x install")
- raise
- self.assertEqual("PythonCore", data["env.company"])
- self.assertStartsWith(data["env.tag"], "3.")
- self.assertEndsWith(data["env.tag"], "-32")
-
- def test_search_major_2(self):
- try:
- data = self.run_py(["-2"], allow_fail=True)
- except subprocess.CalledProcessError:
- if not is_installed("2.7"):
- raise unittest.SkipTest("requires at least one Python 2.x install")
- raise
- self.assertEqual("PythonCore", data["env.company"])
- self.assertStartsWith(data["env.tag"], "2.")
-
- def test_py_default(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- data = self.run_py(["-arg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual("X.Y.exe -arg", data["stdout"].strip())
-
- def test_py2_default(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- data = self.run_py(["-2", "-arg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-32", data["SearchInfo.tag"])
- self.assertEqual("X.Y-32.exe -arg", data["stdout"].strip())
-
- def test_py3_default(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- data = self.run_py(["-3", "-arg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-arm64", data["SearchInfo.tag"])
- self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test -arg", data["stdout"].strip())
-
- def test_py_default_env(self):
- data = self.run_py(["-arg"], env=TEST_PY_ENV)
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual("X.Y.exe -arg", data["stdout"].strip())
-
- def test_py2_default_env(self):
- data = self.run_py(["-2", "-arg"], env=TEST_PY_ENV)
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-32", data["SearchInfo.tag"])
- self.assertEqual("X.Y-32.exe -arg", data["stdout"].strip())
-
- def test_py3_default_env(self):
- data = self.run_py(["-3", "-arg"], env=TEST_PY_ENV)
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-arm64", data["SearchInfo.tag"])
- self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test -arg", data["stdout"].strip())
-
- def test_py_default_short_argv0(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- for argv0 in ['"py.exe"', 'py.exe', '"py"', 'py']:
- with self.subTest(argv0):
- data = self.run_py(["--version"], argv=f'{argv0} --version')
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual("X.Y.exe --version", data["stdout"].strip())
-
- def test_py_default_in_list(self):
- data = self.run_py(["-0"], env=TEST_PY_ENV)
- default = None
- for line in data["stdout"].splitlines():
- m = re.match(r"\s*-V:(.+?)\s+?\*\s+(.+)$", line)
- if m:
- default = m.group(1)
- break
- self.assertEqual("PythonTestSuite/3.100", default)
-
- def test_virtualenv_in_list(self):
- with self.fake_venv() as (venv_exe, env):
- data = self.run_py(["-0p"], env=env)
- for line in data["stdout"].splitlines():
- m = re.match(r"\s*\*\s+(.+)$", line)
- if m:
- self.assertEqual(str(venv_exe), m.group(1))
- break
- else:
- if support.verbose:
- print(data["stdout"])
- print(data["stderr"])
- self.fail("did not find active venv path")
-
- data = self.run_py(["-0"], env=env)
- for line in data["stdout"].splitlines():
- m = re.match(r"\s*\*\s+(.+)$", line)
- if m:
- self.assertEqual("Active venv", m.group(1))
- break
- else:
- self.fail("did not find active venv entry")
-
- def test_virtualenv_with_env(self):
- with self.fake_venv() as (venv_exe, env):
- data1 = self.run_py([], env={**env, "PY_PYTHON": "PythonTestSuite/3"})
- data2 = self.run_py(["-V:PythonTestSuite/3"], env={**env, "PY_PYTHON": "PythonTestSuite/3"})
- # Compare stdout, because stderr goes via ascii
- self.assertEqual(data1["stdout"].strip(), quote(venv_exe))
- self.assertEqual(data1["SearchInfo.lowPriorityTag"], "True")
- # Ensure passing the argument doesn't trigger the same behaviour
- self.assertNotEqual(data2["stdout"].strip(), quote(venv_exe))
- self.assertNotEqual(data2["SearchInfo.lowPriorityTag"], "True")
-
- def test_py_shebang(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! /usr/bin/python -prearg") as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip())
-
- def test_python_shebang(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! python -prearg") as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip())
-
- def test_py2_shebang(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! /usr/bin/python2 -prearg") as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-32", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y-32.exe -prearg {quote(script)} -postarg",
- data["stdout"].strip())
-
- def test_py3_shebang(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! /usr/bin/python3 -prearg") as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-arm64", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {quote(script)} -postarg",
- data["stdout"].strip())
-
- def test_py_shebang_nl(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! /usr/bin/python -prearg\n") as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg",
- data["stdout"].strip())
-
- def test_py2_shebang_nl(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! /usr/bin/python2 -prearg\n") as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-32", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y-32.exe -prearg {quote(script)} -postarg",
- data["stdout"].strip())
-
- def test_py3_shebang_nl(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! /usr/bin/python3 -prearg\n") as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100-arm64", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {quote(script)} -postarg",
- data["stdout"].strip())
-
- def test_py_shebang_short_argv0(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script("#! /usr/bin/python -prearg") as script:
- # Override argv to only pass "py.exe" as the command
- data = self.run_py([script, "-postarg"], argv=f'"py.exe" "{script}" -postarg')
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual(f'X.Y.exe -prearg "{script}" -postarg', data["stdout"].strip())
-
- def test_py_shebang_valid_bom(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- content = "#! /usr/bin/python -prearg".encode("utf-8")
- with self.script(b"\xEF\xBB\xBF" + content) as script:
- data = self.run_py([script, "-postarg"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y.exe -prearg {quote(script)} -postarg", data["stdout"].strip())
-
- def test_py_shebang_invalid_bom(self):
- with self.py_ini(TEST_PY_DEFAULTS):
- content = "#! /usr/bin/python3 -prearg".encode("utf-8")
- with self.script(b"\xEF\xAA\xBF" + content) as script:
- data = self.run_py([script, "-postarg"])
- self.assertIn("Invalid BOM", data["stderr"])
- self.assertEqual("PythonTestSuite", data["SearchInfo.company"])
- self.assertEqual("3.100", data["SearchInfo.tag"])
- self.assertEqual(f"X.Y.exe {quote(script)} -postarg", data["stdout"].strip())
-
- def test_py_handle_64_in_ini(self):
- with self.py_ini("\n".join(["[defaults]", "python=3.999-64"])):
- # Expect this to fail, but should get oldStyleTag flipped on
- data = self.run_py([], allow_fail=True, expect_returncode=103)
- self.assertEqual("3.999-64", data["SearchInfo.tag"])
- self.assertEqual("True", data["SearchInfo.oldStyleTag"])
-
- def test_search_path(self):
- exe = Path("arbitrary-exe-name.exe").absolute()
- exe.touch()
- self.addCleanup(exe.unlink)
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script(f"#! /usr/bin/env {exe.stem} -prearg") as script:
- data = self.run_py(
- [script, "-postarg"],
- env={"PATH": f"{exe.parent};{os.getenv('PATH')}"},
- )
- self.assertEqual(f"{quote(exe)} -prearg {quote(script)} -postarg",
- data["stdout"].strip())
-
- def test_search_path_exe(self):
- # Leave the .exe on the name to ensure we don't add it a second time
- exe = Path("arbitrary-exe-name.exe").absolute()
- exe.touch()
- self.addCleanup(exe.unlink)
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script(f"#! /usr/bin/env {exe.name} -prearg") as script:
- data = self.run_py(
- [script, "-postarg"],
- env={"PATH": f"{exe.parent};{os.getenv('PATH')}"},
- )
- self.assertEqual(f"{quote(exe)} -prearg {quote(script)} -postarg",
- data["stdout"].strip())
-
- def test_recursive_search_path(self):
- stem = self.get_py_exe().stem
- with self.py_ini(TEST_PY_DEFAULTS):
- with self.script(f"#! /usr/bin/env {stem}") as script:
- data = self.run_py(
- [script],
- env={"PATH": f"{self.get_py_exe().parent};{os.getenv('PATH')}"},
- )
- # The recursive search is ignored and we get normal "py" behavior
- self.assertEqual(f"X.Y.exe {quote(script)}", data["stdout"].strip())
-
- def test_install(self):
- data = self.run_py(["-V:3.10"], env={"PYLAUNCHER_ALWAYS_INSTALL": "1"}, expect_returncode=111)
- cmd = data["stdout"].strip()
- # If winget is runnable, we should find it. Otherwise, we'll be trying
- # to open the Store.
- try:
- subprocess.check_call(["winget.exe", "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- except FileNotFoundError:
- self.assertIn("ms-windows-store://", cmd)
- else:
- self.assertIn("winget.exe", cmd)
- # Both command lines include the store ID
- self.assertIn("9PJPW5LDXLZ5", cmd)
-
- def test_literal_shebang_absolute(self):
- with self.script("#! C:/some_random_app -witharg") as script:
- data = self.run_py([script])
- self.assertEqual(
- f"C:\\some_random_app -witharg {quote(script)}",
- data["stdout"].strip(),
- )
-
- def test_literal_shebang_relative(self):
- with self.script("#! ..\\some_random_app -witharg") as script:
- data = self.run_py([script])
- self.assertEqual(
- f"{quote(script.parent.parent / 'some_random_app')} -witharg {quote(script)}",
- data["stdout"].strip(),
- )
-
- def test_literal_shebang_quoted(self):
- with self.script('#! "some random app" -witharg') as script:
- data = self.run_py([script])
- self.assertEqual(
- f"{quote(script.parent / 'some random app')} -witharg {quote(script)}",
- data["stdout"].strip(),
- )
-
- with self.script('#! some" random "app -witharg') as script:
- data = self.run_py([script])
- self.assertEqual(
- f"{quote(script.parent / 'some random app')} -witharg {quote(script)}",
- data["stdout"].strip(),
- )
-
- def test_literal_shebang_quoted_escape(self):
- with self.script('#! some\\" random "app -witharg') as script:
- data = self.run_py([script])
- self.assertEqual(
- f"{quote(script.parent / 'some/ random app')} -witharg {quote(script)}",
- data["stdout"].strip(),
- )
-
- def test_literal_shebang_command(self):
- with self.py_ini(TEST_PY_COMMANDS):
- with self.script('#! test-command arg1') as script:
- data = self.run_py([script])
- self.assertEqual(
- f"TEST_EXE.exe arg1 {quote(script)}",
- data["stdout"].strip(),
- )
-
- def test_literal_shebang_invalid_template(self):
- with self.script('#! /usr/bin/not-python arg1') as script:
- data = self.run_py([script])
- expect = script.parent / "/usr/bin/not-python"
- self.assertEqual(
- f"{quote(expect)} arg1 {quote(script)}",
- data["stdout"].strip(),
- )
-
- def test_shebang_command_in_venv(self):
- stem = "python-that-is-not-on-path"
-
- # First ensure that our test name doesn't exist, and the launcher does
- # not match any installed env
- with self.script(f'#! /usr/bin/env {stem} arg1') as script:
- data = self.run_py([script], expect_returncode=103)
-
- with self.fake_venv() as (venv_exe, env):
- # Put a "normal" Python on PATH as a distraction.
- # The active VIRTUAL_ENV should be preferred when the name isn't an
- # exact match.
- exe = Path(Path(venv_exe).name).absolute()
- exe.touch()
- self.addCleanup(exe.unlink)
- env["PATH"] = f"{exe.parent};{os.environ['PATH']}"
-
- with self.script(f'#! /usr/bin/env {stem} arg1') as script:
- data = self.run_py([script], env=env)
- self.assertEqual(data["stdout"].strip(), f"{quote(venv_exe)} arg1 {quote(script)}")
-
- with self.script(f'#! /usr/bin/env {exe.stem} arg1') as script:
- data = self.run_py([script], env=env)
- self.assertEqual(data["stdout"].strip(), f"{quote(exe)} arg1 {quote(script)}")
-
- def test_shebang_executable_extension(self):
- with self.script('#! /usr/bin/env python3.99') as script:
- data = self.run_py([script], expect_returncode=103)
- expect = "# Search PATH for python3.99.exe"
- actual = [line.strip() for line in data["stderr"].splitlines()
- if line.startswith("# Search PATH")]
- self.assertEqual([expect], actual)
diff --git a/Lib/test/test_tools/test_compute_changes.py b/Lib/test/test_tools/test_compute_changes.py
index eb1ecd447a7ce0a..4c2b9966c4cd5b6 100644
--- a/Lib/test/test_tools/test_compute_changes.py
+++ b/Lib/test/test_tools/test_compute_changes.py
@@ -109,15 +109,6 @@ def test_unix(self):
self.assertTrue(result.run_tests)
self.assertFalse(result.run_windows_tests)
- def test_msi(self):
- for f in (
- ".github/workflows/reusable-windows-msi.yml",
- "Tools/msi/build.bat",
- ):
- with self.subTest(f=f):
- result = process_changed_files({Path(f)})
- self.assertTrue(result.run_windows_msi)
-
def test_all_run(self):
for f in (
".github/workflows/some-new-workflow.yml",
diff --git a/Misc/NEWS.d/next/Build/2026-07-10-13-25-55.gh-issue-153511.dCPBwU.rst b/Misc/NEWS.d/next/Build/2026-07-10-13-25-55.gh-issue-153511.dCPBwU.rst
new file mode 100644
index 000000000000000..0d9163c8dc4d0ee
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2026-07-10-13-25-55.gh-issue-153511.dCPBwU.rst
@@ -0,0 +1,3 @@
+Removes the legacy MSI and Nuget build scripts, the legacy ``py.exe``
+launcher (now replaced by the Python install manager), and adds a
+``PC/layout`` readme.
diff --git a/PC/classicAppCompat.can.xml b/PC/classicAppCompat.can.xml
deleted file mode 100644
index df361f8e3e2cd32..000000000000000
--- a/PC/classicAppCompat.can.xml
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/PC/classicAppCompat.cat b/PC/classicAppCompat.cat
deleted file mode 100644
index 3d213596accffa2..000000000000000
Binary files a/PC/classicAppCompat.cat and /dev/null differ
diff --git a/PC/classicAppCompat.sccd b/PC/classicAppCompat.sccd
deleted file mode 100644
index d7a70cdd0533c3d..000000000000000
--- a/PC/classicAppCompat.sccd
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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
diff --git a/PC/launcher-usage.txt b/PC/launcher-usage.txt
deleted file mode 100644
index aad103509daa283..000000000000000
--- a/PC/launcher-usage.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-Python Launcher for Windows Version %s
-
-usage:
-%s [launcher-args] [python-args] [script [script-args]]
-
-Launcher arguments:
--2 : Launch the latest Python 2.x version
--3 : Launch the latest Python 3.x version
--X.Y : Launch the specified Python version
-
-The above default to an architecture native runtime, but will select any
-available. Add a "-32" to the argument to only launch 32-bit runtimes,
-or add "-64" to omit 32-bit runtimes (this latter option is deprecated).
-
-To select a specific runtime, use the -V: options.
-
--V:TAG : Launch a Python runtime with the specified tag
--V:COMPANY/TAG : Launch a Python runtime from the specified company and
- with the specified tag
-
--0 --list : List the available pythons
--0p --list-paths : List with paths
-
-If no options are given but a script is specified, the script is checked for a
-shebang line. Otherwise, an active virtual environment or global default will
-be selected.
-
-See https://docs.python.org/using/windows.html#python-launcher-for-windows for
-additional configuration.
-
-The following help text is from Python:
diff --git a/PC/launcher.c b/PC/launcher.c
deleted file mode 100644
index 5667fc851aa5904..000000000000000
--- a/PC/launcher.c
+++ /dev/null
@@ -1,2047 +0,0 @@
-/*
- * Copyright (C) 2011-2013 Vinay Sajip.
- * Licensed to PSF under a contributor agreement.
- *
- * Based on the work of:
- *
- * Mark Hammond (original author of Python version)
- * Curt Hagenlocher (job management)
- */
-
-#include
-#include
-#include
-#include
-
-#define BUFSIZE 256
-#define MSGSIZE 1024
-
-/* Build options. */
-#define SKIP_PREFIX
-#define SEARCH_PATH
-
-/* Error codes */
-
-#define RC_NO_STD_HANDLES 100
-#define RC_CREATE_PROCESS 101
-#define RC_BAD_VIRTUAL_PATH 102
-#define RC_NO_PYTHON 103
-#define RC_NO_MEMORY 104
-/*
- * SCRIPT_WRAPPER is used to choose one of the variants of an executable built
- * from this source file. If not defined, the PEP 397 Python launcher is built;
- * if defined, a script launcher of the type used by setuptools is built, which
- * looks for a script name related to the executable name and runs that script
- * with the appropriate Python interpreter.
- *
- * SCRIPT_WRAPPER should be undefined in the source, and defined in a VS project
- * which builds the setuptools-style launcher.
- */
-#if defined(SCRIPT_WRAPPER)
-#define RC_NO_SCRIPT 105
-#endif
-/*
- * VENV_REDIRECT is used to choose the variant that looks for an adjacent or
- * one-level-higher pyvenv.cfg, and uses its "home" property to locate and
- * launch the original python.exe.
- */
-#if defined(VENV_REDIRECT)
-#define RC_NO_VENV_CFG 106
-#define RC_BAD_VENV_CFG 107
-#endif
-
-/* Just for now - static definition */
-
-static FILE * log_fp = NULL;
-
-static wchar_t *
-skip_whitespace(wchar_t * p)
-{
- while (*p && isspace(*p))
- ++p;
- return p;
-}
-
-static void
-debug(wchar_t * format, ...)
-{
- va_list va;
-
- if (log_fp != NULL) {
- va_start(va, format);
- vfwprintf_s(log_fp, format, va);
- va_end(va);
- }
-}
-
-static void
-winerror(int rc, wchar_t * message, int size)
-{
- FormatMessageW(
- FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- message, size, NULL);
-}
-
-static void
-error(int rc, wchar_t * format, ... )
-{
- va_list va;
- wchar_t message[MSGSIZE];
- wchar_t win_message[MSGSIZE];
- int len;
-
- va_start(va, format);
- len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
- va_end(va);
-
- if (rc == 0) { /* a Windows error */
- winerror(GetLastError(), win_message, MSGSIZE);
- if (len >= 0) {
- _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %ls",
- win_message);
- }
- }
-
-#if !defined(_WINDOWS)
- fwprintf(stderr, L"%ls\n", message);
-#else
- MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...",
- MB_OK);
-#endif
- exit(rc);
-}
-
-/*
- * This function is here to simplify memory management
- * and to treat blank values as if they are absent.
- */
-static wchar_t * get_env(wchar_t * key)
-{
- /* This is not thread-safe, just like getenv */
- static wchar_t buf[BUFSIZE];
- DWORD result = GetEnvironmentVariableW(key, buf, BUFSIZE);
-
- if (result >= BUFSIZE) {
- /* Large environment variable. Accept some leakage */
- wchar_t *buf2 = (wchar_t*)malloc(sizeof(wchar_t) * (result+1));
- if (buf2 == NULL) {
- error(RC_NO_MEMORY, L"Could not allocate environment buffer");
- }
- GetEnvironmentVariableW(key, buf2, result);
- return buf2;
- }
-
- if (result == 0)
- /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND,
- or an empty environment variable. */
- return NULL;
-
- return buf;
-}
-
-#if defined(Py_DEBUG)
-/* Do not define EXECUTABLEPATH_VALUE in debug builds as it'll
- never point to the debug build. */
-#if defined(_WINDOWS)
-
-#define PYTHON_EXECUTABLE L"pythonw_d.exe"
-
-#else
-
-#define PYTHON_EXECUTABLE L"python_d.exe"
-
-#endif
-#else
-#if defined(_WINDOWS)
-
-#define PYTHON_EXECUTABLE L"pythonw.exe"
-#define EXECUTABLEPATH_VALUE L"WindowedExecutablePath"
-
-#else
-
-#define PYTHON_EXECUTABLE L"python.exe"
-#define EXECUTABLEPATH_VALUE L"ExecutablePath"
-
-#endif
-#endif
-
-#define MAX_VERSION_SIZE 8
-
-typedef struct {
- wchar_t version[MAX_VERSION_SIZE]; /* m.n */
- int bits; /* 32 or 64 */
- wchar_t executable[MAX_PATH];
- wchar_t exe_display[MAX_PATH];
-} INSTALLED_PYTHON;
-
-/*
- * To avoid messing about with heap allocations, just assume we can allocate
- * statically and never have to deal with more versions than this.
- */
-#define MAX_INSTALLED_PYTHONS 100
-
-static INSTALLED_PYTHON installed_pythons[MAX_INSTALLED_PYTHONS];
-
-static size_t num_installed_pythons = 0;
-
-/*
- * To hold SOFTWARE\Python\PythonCore\X.Y...\InstallPath
- * The version name can be longer than MAX_VERSION_SIZE, but will be
- * truncated to just X.Y for comparisons.
- */
-#define IP_BASE_SIZE 80
-#define IP_VERSION_SIZE 8
-#define IP_SIZE (IP_BASE_SIZE + IP_VERSION_SIZE)
-#define CORE_PATH L"SOFTWARE\\Python\\PythonCore"
-/*
- * Installations from the Microsoft Store will set the same registry keys,
- * but because of a limitation in Windows they cannot be enumerated normally
- * (unless you have no other Python installations... which is probably false
- * because that's the most likely way to get this launcher!)
- * This key is under HKEY_LOCAL_MACHINE
- */
-#define LOOKASIDE_PATH L"SOFTWARE\\Microsoft\\AppModel\\Lookaside\\user\\Software\\Python\\PythonCore"
-
-static wchar_t * location_checks[] = {
- L"\\",
- L"\\PCbuild\\win32\\",
- L"\\PCbuild\\amd64\\",
- /* To support early 32bit versions of Python that stuck the build binaries
- * directly in PCbuild... */
- L"\\PCbuild\\",
- NULL
-};
-
-static INSTALLED_PYTHON *
-find_existing_python(const wchar_t * path)
-{
- INSTALLED_PYTHON * result = NULL;
- size_t i;
- INSTALLED_PYTHON * ip;
-
- for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) {
- if (_wcsicmp(path, ip->executable) == 0) {
- result = ip;
- break;
- }
- }
- return result;
-}
-
-static INSTALLED_PYTHON *
-find_existing_python2(int bits, const wchar_t * version)
-{
- INSTALLED_PYTHON * result = NULL;
- size_t i;
- INSTALLED_PYTHON * ip;
-
- for (i = 0, ip = installed_pythons; i < num_installed_pythons; i++, ip++) {
- if (bits == ip->bits && _wcsicmp(version, ip->version) == 0) {
- result = ip;
- break;
- }
- }
- return result;
-}
-
-static void
-_locate_pythons_for_key(HKEY root, LPCWSTR subkey, REGSAM flags, int bits,
- int display_name_only)
-{
- HKEY core_root, ip_key;
- LSTATUS status = RegOpenKeyExW(root, subkey, 0, flags, &core_root);
- wchar_t message[MSGSIZE];
- DWORD i;
- size_t n;
- BOOL ok, append_name;
- DWORD type, data_size, attrs;
- INSTALLED_PYTHON * ip, * pip;
- wchar_t ip_version[IP_VERSION_SIZE];
- wchar_t ip_path[IP_SIZE];
- wchar_t * check;
- wchar_t ** checkp;
- wchar_t *key_name = (root == HKEY_LOCAL_MACHINE) ? L"HKLM" : L"HKCU";
-
- if (status != ERROR_SUCCESS)
- debug(L"locate_pythons_for_key: unable to open PythonCore key in %ls\n",
- key_name);
- else {
- ip = &installed_pythons[num_installed_pythons];
- for (i = 0; num_installed_pythons < MAX_INSTALLED_PYTHONS; i++) {
- status = RegEnumKeyW(core_root, i, ip_version, IP_VERSION_SIZE);
- if (status != ERROR_SUCCESS) {
- if (status != ERROR_NO_MORE_ITEMS) {
- /* unexpected error */
- winerror(status, message, MSGSIZE);
- debug(L"Can't enumerate registry key for version %ls: %ls\n",
- ip_version, message);
- }
- break;
- }
- else {
- wcsncpy_s(ip->version, MAX_VERSION_SIZE, ip_version,
- MAX_VERSION_SIZE-1);
- /* Still treating version as "x.y" rather than sys.winver
- * When PEP 514 tags are properly used, we shouldn't need
- * to strip this off here.
- */
- check = wcsrchr(ip->version, L'-');
- if (check && !wcscmp(check, L"-32")) {
- *check = L'\0';
- }
- _snwprintf_s(ip_path, IP_SIZE, _TRUNCATE,
- L"%ls\\%ls\\InstallPath", subkey, ip_version);
- status = RegOpenKeyExW(root, ip_path, 0, flags, &ip_key);
- if (status != ERROR_SUCCESS) {
- winerror(status, message, MSGSIZE);
- /* Note: 'message' already has a trailing \n*/
- debug(L"%ls\\%ls: %ls", key_name, ip_path, message);
- continue;
- }
- data_size = sizeof(ip->executable) - 1;
- append_name = FALSE;
-#ifdef EXECUTABLEPATH_VALUE
- status = RegQueryValueExW(ip_key, EXECUTABLEPATH_VALUE, NULL, &type,
- (LPBYTE)ip->executable, &data_size);
-#else
- status = ERROR_FILE_NOT_FOUND; /* actual error doesn't matter */
-#endif
- if (status != ERROR_SUCCESS || type != REG_SZ || !data_size) {
- append_name = TRUE;
- data_size = sizeof(ip->executable) - 1;
- status = RegQueryValueExW(ip_key, NULL, NULL, &type,
- (LPBYTE)ip->executable, &data_size);
- if (status != ERROR_SUCCESS) {
- winerror(status, message, MSGSIZE);
- debug(L"%ls\\%ls: %ls\n", key_name, ip_path, message);
- RegCloseKey(ip_key);
- continue;
- }
- }
- RegCloseKey(ip_key);
- if (type != REG_SZ) {
- continue;
- }
-
- data_size = data_size / sizeof(wchar_t) - 1; /* for NUL */
- if (ip->executable[data_size - 1] == L'\\')
- --data_size; /* reg value ended in a backslash */
- /* ip->executable is data_size long */
- for (checkp = location_checks; *checkp; ++checkp) {
- check = *checkp;
- if (append_name) {
- _snwprintf_s(&ip->executable[data_size],
- MAX_PATH - data_size,
- MAX_PATH - data_size,
- L"%ls%ls", check, PYTHON_EXECUTABLE);
- }
- attrs = GetFileAttributesW(ip->executable);
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- winerror(GetLastError(), message, MSGSIZE);
- debug(L"locate_pythons_for_key: %ls: %ls",
- ip->executable, message);
- }
- else if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
- debug(L"locate_pythons_for_key: '%ls' is a directory\n",
- ip->executable);
- }
- else if (find_existing_python(ip->executable)) {
- debug(L"locate_pythons_for_key: %ls: already found\n",
- ip->executable);
- }
- else {
- /* check the executable type. */
- if (bits) {
- ip->bits = bits;
- } else {
- ok = GetBinaryTypeW(ip->executable, &attrs);
- if (!ok) {
- debug(L"Failure getting binary type: %ls\n",
- ip->executable);
- }
- else {
- if (attrs == SCS_64BIT_BINARY)
- ip->bits = 64;
- else if (attrs == SCS_32BIT_BINARY)
- ip->bits = 32;
- else
- ip->bits = 0;
- }
- }
- if (ip->bits == 0) {
- debug(L"locate_pythons_for_key: %ls: \
-invalid binary type: %X\n",
- ip->executable, attrs);
- }
- else {
- if (display_name_only) {
- /* display just the executable name. This is
- * primarily for the Store installs */
- const wchar_t *name = wcsrchr(ip->executable, L'\\');
- if (name) {
- wcscpy_s(ip->exe_display, MAX_PATH, name+1);
- }
- }
- if (wcschr(ip->executable, L' ') != NULL) {
- /* has spaces, so quote, and set original as
- * the display name */
- if (!ip->exe_display[0]) {
- wcscpy_s(ip->exe_display, MAX_PATH, ip->executable);
- }
- n = wcslen(ip->executable);
- memmove(&ip->executable[1],
- ip->executable, n * sizeof(wchar_t));
- ip->executable[0] = L'\"';
- ip->executable[n + 1] = L'\"';
- ip->executable[n + 2] = L'\0';
- }
- debug(L"locate_pythons_for_key: %ls \
-is a %dbit executable\n",
- ip->executable, ip->bits);
- if (find_existing_python2(ip->bits, ip->version)) {
- debug(L"locate_pythons_for_key: %ls-%i: already \
-found\n", ip->version, ip->bits);
- }
- else {
- ++num_installed_pythons;
- pip = ip++;
- if (num_installed_pythons >=
- MAX_INSTALLED_PYTHONS)
- break;
- }
- }
- }
- }
- }
- }
- RegCloseKey(core_root);
- }
-}
-
-static int
-compare_pythons(const void * p1, const void * p2)
-{
- INSTALLED_PYTHON * ip1 = (INSTALLED_PYTHON *) p1;
- INSTALLED_PYTHON * ip2 = (INSTALLED_PYTHON *) p2;
- /* note reverse sorting on version */
- int result = CompareStringW(LOCALE_INVARIANT, SORT_DIGITSASNUMBERS,
- ip2->version, -1, ip1->version, -1);
- switch (result) {
- case 0:
- error(0, L"CompareStringW failed");
- return 0;
- case CSTR_LESS_THAN:
- return -1;
- case CSTR_EQUAL:
- return ip2->bits - ip1->bits; /* 64 before 32 */
- case CSTR_GREATER_THAN:
- return 1;
- default:
- return 0; // This should never be reached.
- }
-}
-
-static void
-locate_pythons_for_key(HKEY root, REGSAM flags)
-{
- _locate_pythons_for_key(root, CORE_PATH, flags, 0, FALSE);
-}
-
-static void
-locate_store_pythons(void)
-{
-#if defined(_M_X64)
- /* 64bit process, so look in native registry */
- _locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH,
- KEY_READ, 64, TRUE);
-#else
- /* 32bit process, so check that we're on 64bit OS */
- BOOL f64 = FALSE;
- if (IsWow64Process(GetCurrentProcess(), &f64) && f64) {
- _locate_pythons_for_key(HKEY_LOCAL_MACHINE, LOOKASIDE_PATH,
- KEY_READ | KEY_WOW64_64KEY, 64, TRUE);
- }
-#endif
-}
-
-static void
-locate_venv_python(void)
-{
- static wchar_t venv_python[MAX_PATH];
- INSTALLED_PYTHON * ip;
- wchar_t *virtual_env = get_env(L"VIRTUAL_ENV");
- DWORD attrs;
-
- /* Check for VIRTUAL_ENV environment variable */
- if (virtual_env == NULL || virtual_env[0] == L'\0') {
- return;
- }
-
- /* Check for a python executable in the venv */
- debug(L"Checking for Python executable in virtual env '%ls'\n", virtual_env);
- _snwprintf_s(venv_python, MAX_PATH, _TRUNCATE,
- L"%ls\\Scripts\\%ls", virtual_env, PYTHON_EXECUTABLE);
- attrs = GetFileAttributesW(venv_python);
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- debug(L"Python executable %ls missing from virtual env\n", venv_python);
- return;
- }
-
- ip = &installed_pythons[num_installed_pythons++];
- wcscpy_s(ip->executable, MAX_PATH, venv_python);
- ip->bits = 0;
- wcscpy_s(ip->version, MAX_VERSION_SIZE, L"venv");
-}
-
-static void
-locate_all_pythons(void)
-{
- /* venv Python is highest priority */
- locate_venv_python();
-#if defined(_M_X64)
- /* If we are a 64bit process, first hit the 32bit keys. */
- debug(L"locating Pythons in 32bit registry\n");
- locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_32KEY);
- locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_32KEY);
-#else
- /* If we are a 32bit process on a 64bit Windows, first hit the 64bit keys.*/
- BOOL f64 = FALSE;
- if (IsWow64Process(GetCurrentProcess(), &f64) && f64) {
- debug(L"locating Pythons in 64bit registry\n");
- locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ | KEY_WOW64_64KEY);
- locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ | KEY_WOW64_64KEY);
- }
-#endif
- /* now hit the "native" key for this process bittedness. */
- debug(L"locating Pythons in native registry\n");
- locate_pythons_for_key(HKEY_CURRENT_USER, KEY_READ);
- locate_pythons_for_key(HKEY_LOCAL_MACHINE, KEY_READ);
- /* Store-installed Python is lowest priority */
- locate_store_pythons();
- qsort(installed_pythons, num_installed_pythons, sizeof(INSTALLED_PYTHON),
- compare_pythons);
-}
-
-static INSTALLED_PYTHON *
-find_python_by_version(wchar_t const * wanted_ver)
-{
- INSTALLED_PYTHON * result = NULL;
- INSTALLED_PYTHON * ip = installed_pythons;
- size_t i, n;
- size_t wlen = wcslen(wanted_ver);
- int bits = 0;
-
- if (wcsstr(wanted_ver, L"-32")) {
- bits = 32;
- wlen -= wcslen(L"-32");
- }
- else if (wcsstr(wanted_ver, L"-64")) { /* Added option to select 64 bit explicitly */
- bits = 64;
- wlen -= wcslen(L"-64");
- }
- for (i = 0; i < num_installed_pythons; i++, ip++) {
- n = wcslen(ip->version);
- /*
- * If wlen is greater than 1, we're probably trying to find a specific
- * version and thus want an exact match: 3.1 != 3.10. Otherwise, we
- * just want a prefix match.
- */
- if ((wlen > 1) && (n != wlen)) {
- continue;
- }
- if (n > wlen) {
- n = wlen;
- }
- if ((wcsncmp(ip->version, wanted_ver, n) == 0) &&
- /* bits == 0 => don't care */
- ((bits == 0) || (ip->bits == bits))) {
- result = ip;
- break;
- }
- }
- return result;
-}
-
-
-static wchar_t appdata_ini_path[MAX_PATH];
-static wchar_t launcher_ini_path[MAX_PATH];
-
-/*
- * Get a value either from the environment or a configuration file.
- * The key passed in will either be "python", "python2" or "python3".
- */
-static wchar_t *
-get_configured_value(wchar_t * key)
-{
-/*
- * Note: this static value is used to return a configured value
- * obtained either from the environment or configuration file.
- * This should be OK since there wouldn't be any concurrent calls.
- */
- static wchar_t configured_value[MSGSIZE];
- wchar_t * result = NULL;
- wchar_t * found_in = L"environment";
- DWORD size;
-
- /* First, search the environment. */
- _snwprintf_s(configured_value, MSGSIZE, _TRUNCATE, L"py_%ls", key);
- result = get_env(configured_value);
- if (result == NULL && appdata_ini_path[0]) {
- /* Not in environment: check local configuration. */
- size = GetPrivateProfileStringW(L"defaults", key, NULL,
- configured_value, MSGSIZE,
- appdata_ini_path);
- if (size > 0) {
- result = configured_value;
- found_in = appdata_ini_path;
- }
- }
- if (result == NULL && launcher_ini_path[0]) {
- /* Not in environment or local: check global configuration. */
- size = GetPrivateProfileStringW(L"defaults", key, NULL,
- configured_value, MSGSIZE,
- launcher_ini_path);
- if (size > 0) {
- result = configured_value;
- found_in = launcher_ini_path;
- }
- }
- if (result) {
- debug(L"found configured value '%ls=%ls' in %ls\n",
- key, result, found_in ? found_in : L"(unknown)");
- } else {
- debug(L"found no configured value for '%ls'\n", key);
- }
- return result;
-}
-
-static INSTALLED_PYTHON *
-locate_python(wchar_t * wanted_ver, BOOL from_shebang)
-{
- static wchar_t config_key [] = { L"pythonX" };
- static wchar_t * last_char = &config_key[sizeof(config_key) /
- sizeof(wchar_t) - 2];
- INSTALLED_PYTHON * result = NULL;
- size_t n = wcslen(wanted_ver);
- wchar_t * configured_value;
-
- if (num_installed_pythons == 0)
- locate_all_pythons();
-
- if (n == 1) { /* just major version specified */
- *last_char = *wanted_ver;
- configured_value = get_configured_value(config_key);
- if (configured_value != NULL)
- wanted_ver = configured_value;
- }
- if (*wanted_ver) {
- result = find_python_by_version(wanted_ver);
- debug(L"search for Python version '%ls' found ", wanted_ver);
- if (result) {
- debug(L"'%ls'\n", result->executable);
- } else {
- debug(L"no interpreter\n");
- }
- }
- else {
- *last_char = L'\0'; /* look for an overall default */
- result = find_python_by_version(L"venv");
- if (result == NULL) {
- configured_value = get_configured_value(config_key);
- if (configured_value)
- result = find_python_by_version(configured_value);
- }
- /* Not found a value yet - try by major version.
- * If we're looking for an interpreter specified in a shebang line,
- * we want to try Python 2 first, then Python 3 (for Unix and backward
- * compatibility). If we're being called interactively, assume the user
- * wants the latest version available, so try Python 3 first, then
- * Python 2.
- */
- if (result == NULL)
- result = find_python_by_version(from_shebang ? L"2" : L"3");
- if (result == NULL)
- result = find_python_by_version(from_shebang ? L"3" : L"2");
- debug(L"search for default Python found ");
- if (result) {
- debug(L"version %ls at '%ls'\n",
- result->version, result->executable);
- } else {
- debug(L"no interpreter\n");
- }
- }
- return result;
-}
-
-#if defined(SCRIPT_WRAPPER)
-/*
- * Check for a script located alongside the executable
- */
-
-#if defined(_WINDOWS)
-#define SCRIPT_SUFFIX L"-script.pyw"
-#else
-#define SCRIPT_SUFFIX L"-script.py"
-#endif
-
-static wchar_t wrapped_script_path[MAX_PATH];
-
-/* Locate the script being wrapped.
- *
- * This code should store the name of the wrapped script in
- * wrapped_script_path, or terminate the program with an error if there is no
- * valid wrapped script file.
- */
-static void
-locate_wrapped_script(void)
-{
- wchar_t * p;
- size_t plen;
- DWORD attrs;
-
- plen = GetModuleFileNameW(NULL, wrapped_script_path, MAX_PATH);
- p = wcsrchr(wrapped_script_path, L'.');
- if (p == NULL) {
- debug(L"GetModuleFileNameW returned value has no extension: %ls\n",
- wrapped_script_path);
- error(RC_NO_SCRIPT, L"Wrapper name '%ls' is not valid.", wrapped_script_path);
- }
-
- wcsncpy_s(p, MAX_PATH - (p - wrapped_script_path) + 1, SCRIPT_SUFFIX, _TRUNCATE);
- attrs = GetFileAttributesW(wrapped_script_path);
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- debug(L"File '%ls' non-existent\n", wrapped_script_path);
- error(RC_NO_SCRIPT, L"Script file '%ls' is not present.", wrapped_script_path);
- }
-
- debug(L"Using wrapped script file '%ls'\n", wrapped_script_path);
-}
-#endif
-
-/*
- * Process creation code
- */
-
-static BOOL
-safe_duplicate_handle(HANDLE in, HANDLE * pout)
-{
- BOOL ok;
- HANDLE process = GetCurrentProcess();
- DWORD rc;
-
- *pout = NULL;
- ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
- DUPLICATE_SAME_ACCESS);
- if (!ok) {
- rc = GetLastError();
- if (rc == ERROR_INVALID_HANDLE) {
- debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n");
- ok = TRUE;
- }
- else {
- debug(L"DuplicateHandle returned %d\n", rc);
- }
- }
- return ok;
-}
-
-static BOOL WINAPI
-ctrl_c_handler(DWORD code)
-{
- return TRUE; /* We just ignore all control events. */
-}
-
-static void
-run_child(wchar_t * cmdline)
-{
- HANDLE job;
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
- DWORD rc;
- BOOL ok;
- STARTUPINFOW si;
- PROCESS_INFORMATION pi;
-
-#if defined(_WINDOWS)
- /*
- When explorer launches a Windows (GUI) application, it displays
- the "app starting" (the "pointer + hourglass") cursor for a number
- of seconds, or until the app does something UI-ish (eg, creating a
- window, or fetching a message). As this launcher doesn't do this
- directly, that cursor remains even after the child process does these
- things. We avoid that by doing a simple post+get message.
- See http://bugs.python.org/issue17290
- */
- MSG msg;
-
- PostMessage(0, 0, 0, 0);
- GetMessage(&msg, 0, 0, 0);
-#endif
-
- debug(L"run_child: about to run '%ls'\n", cmdline);
- job = CreateJobObject(NULL, NULL);
- ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
- &info, sizeof(info), &rc);
- if (!ok || (rc != sizeof(info)) || !job)
- error(RC_CREATE_PROCESS, L"Job information querying failed");
- info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
- JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
- ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
- sizeof(info));
- if (!ok)
- error(RC_CREATE_PROCESS, L"Job information setting failed");
- memset(&si, 0, sizeof(si));
- GetStartupInfoW(&si);
- ok = safe_duplicate_handle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput);
- if (!ok)
- error(RC_NO_STD_HANDLES, L"stdin duplication failed");
- ok = safe_duplicate_handle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput);
- if (!ok)
- error(RC_NO_STD_HANDLES, L"stdout duplication failed");
- ok = safe_duplicate_handle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError);
- if (!ok)
- error(RC_NO_STD_HANDLES, L"stderr duplication failed");
-
- ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
- if (!ok)
- error(RC_CREATE_PROCESS, L"control handler setting failed");
-
- si.dwFlags = STARTF_USESTDHANDLES;
- ok = CreateProcessW(NULL, cmdline, NULL, NULL, TRUE,
- 0, NULL, NULL, &si, &pi);
- if (!ok)
- error(RC_CREATE_PROCESS, L"Unable to create process using '%ls'", cmdline);
- AssignProcessToJobObject(job, pi.hProcess);
- CloseHandle(pi.hThread);
- WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
- ok = GetExitCodeProcess(pi.hProcess, &rc);
- if (!ok)
- error(RC_CREATE_PROCESS, L"Failed to get exit code of process");
- debug(L"child process exit code: %d\n", rc);
- exit(rc);
-}
-
-static void
-invoke_child(wchar_t * executable, wchar_t * suffix, wchar_t * cmdline)
-{
- wchar_t * child_command;
- size_t child_command_size;
- BOOL no_suffix = (suffix == NULL) || (*suffix == L'\0');
- BOOL no_cmdline = (*cmdline == L'\0');
-
- if (no_suffix && no_cmdline)
- run_child(executable);
- else {
- if (no_suffix) {
- /* add 2 for space separator + terminating NUL. */
- child_command_size = wcslen(executable) + wcslen(cmdline) + 2;
- }
- else {
- /* add 3 for 2 space separators + terminating NUL. */
- child_command_size = wcslen(executable) + wcslen(suffix) +
- wcslen(cmdline) + 3;
- }
- child_command = calloc(child_command_size, sizeof(wchar_t));
- if (child_command == NULL)
- error(RC_CREATE_PROCESS, L"unable to allocate %zd bytes for child command.",
- child_command_size);
- if (no_suffix)
- _snwprintf_s(child_command, child_command_size,
- child_command_size - 1, L"%ls %ls",
- executable, cmdline);
- else
- _snwprintf_s(child_command, child_command_size,
- child_command_size - 1, L"%ls %ls %ls",
- executable, suffix, cmdline);
- run_child(child_command);
- free(child_command);
- }
-}
-
-typedef struct {
- wchar_t *shebang;
- BOOL search;
-} SHEBANG;
-
-static SHEBANG builtin_virtual_paths [] = {
- { L"/usr/bin/env python", TRUE },
- { L"/usr/bin/python", FALSE },
- { L"/usr/local/bin/python", FALSE },
- { L"python", FALSE },
- { NULL, FALSE },
-};
-
-/* For now, a static array of commands. */
-
-#define MAX_COMMANDS 100
-
-typedef struct {
- wchar_t key[MAX_PATH];
- wchar_t value[MSGSIZE];
-} COMMAND;
-
-static COMMAND commands[MAX_COMMANDS];
-static int num_commands = 0;
-
-#if defined(SKIP_PREFIX)
-
-static wchar_t * builtin_prefixes [] = {
- /* These must be in an order that the longest matches should be found,
- * i.e. if the prefix is "/usr/bin/env ", it should match that entry
- * *before* matching "/usr/bin/".
- */
- L"/usr/bin/env ",
- L"/usr/bin/",
- L"/usr/local/bin/",
- NULL
-};
-
-static wchar_t * skip_prefix(wchar_t * name)
-{
- wchar_t ** pp = builtin_prefixes;
- wchar_t * result = name;
- wchar_t * p;
- size_t n;
-
- for (; p = *pp; pp++) {
- n = wcslen(p);
- if (_wcsnicmp(p, name, n) == 0) {
- result += n; /* skip the prefix */
- if (p[n - 1] == L' ') /* No empty strings in table, so n > 1 */
- result = skip_whitespace(result);
- break;
- }
- }
- return result;
-}
-
-#endif
-
-#if defined(SEARCH_PATH)
-
-static COMMAND path_command;
-
-static COMMAND * find_on_path(wchar_t * name)
-{
- wchar_t * pathext;
- size_t varsize;
- wchar_t * context = NULL;
- wchar_t * extension;
- COMMAND * result = NULL;
- DWORD len;
- errno_t rc;
-
- wcscpy_s(path_command.key, MAX_PATH, name);
- if (wcschr(name, L'.') != NULL) {
- /* assume it has an extension. */
- len = SearchPathW(NULL, name, NULL, MSGSIZE, path_command.value, NULL);
- if (len) {
- result = &path_command;
- }
- }
- else {
- /* No extension - search using registered extensions. */
- rc = _wdupenv_s(&pathext, &varsize, L"PATHEXT");
- if (rc == 0) {
- extension = wcstok_s(pathext, L";", &context);
- while (extension) {
- len = SearchPathW(NULL, name, extension, MSGSIZE, path_command.value, NULL);
- if (len) {
- result = &path_command;
- break;
- }
- extension = wcstok_s(NULL, L";", &context);
- }
- free(pathext);
- }
- }
- return result;
-}
-
-#endif
-
-static COMMAND * find_command(wchar_t * name)
-{
- COMMAND * result = NULL;
- COMMAND * cp = commands;
- int i;
-
- for (i = 0; i < num_commands; i++, cp++) {
- if (_wcsicmp(cp->key, name) == 0) {
- result = cp;
- break;
- }
- }
-#if defined(SEARCH_PATH)
- if (result == NULL)
- result = find_on_path(name);
-#endif
- return result;
-}
-
-static void
-update_command(COMMAND * cp, wchar_t * name, wchar_t * cmdline)
-{
- wcsncpy_s(cp->key, MAX_PATH, name, _TRUNCATE);
- wcsncpy_s(cp->value, MSGSIZE, cmdline, _TRUNCATE);
-}
-
-static void
-add_command(wchar_t * name, wchar_t * cmdline)
-{
- if (num_commands >= MAX_COMMANDS) {
- debug(L"can't add %ls = '%ls': no room\n", name, cmdline);
- }
- else {
- COMMAND * cp = &commands[num_commands++];
-
- update_command(cp, name, cmdline);
- }
-}
-
-static void
-read_config_file(wchar_t * config_path)
-{
- wchar_t keynames[MSGSIZE];
- wchar_t value[MSGSIZE];
- DWORD read;
- wchar_t * key;
- COMMAND * cp;
- wchar_t * cmdp;
-
- read = GetPrivateProfileStringW(L"commands", NULL, NULL, keynames, MSGSIZE,
- config_path);
- if (read == MSGSIZE - 1) {
- debug(L"read_commands: %ls: not enough space for names\n", config_path);
- }
- key = keynames;
- while (*key) {
- read = GetPrivateProfileStringW(L"commands", key, NULL, value, MSGSIZE,
- config_path);
- if (read == MSGSIZE - 1) {
- debug(L"read_commands: %ls: not enough space for %ls\n",
- config_path, key);
- }
- cmdp = skip_whitespace(value);
- if (*cmdp) {
- cp = find_command(key);
- if (cp == NULL)
- add_command(key, value);
- else
- update_command(cp, key, value);
- }
- key += wcslen(key) + 1;
- }
-}
-
-static void read_commands(void)
-{
- if (launcher_ini_path[0])
- read_config_file(launcher_ini_path);
- if (appdata_ini_path[0])
- read_config_file(appdata_ini_path);
-}
-
-static BOOL
-parse_shebang(wchar_t * shebang_line, int nchars, wchar_t ** command,
- wchar_t ** suffix, BOOL *search)
-{
- BOOL rc = FALSE;
- SHEBANG * vpp;
- size_t plen;
- wchar_t * p;
- wchar_t zapped;
- wchar_t * endp = shebang_line + nchars - 1;
- COMMAND * cp;
- wchar_t * skipped;
-
- *command = NULL; /* failure return */
- *suffix = NULL;
- *search = FALSE;
-
- if ((*shebang_line++ == L'#') && (*shebang_line++ == L'!')) {
- shebang_line = skip_whitespace(shebang_line);
- if (*shebang_line) {
- *command = shebang_line;
- for (vpp = builtin_virtual_paths; vpp->shebang; ++vpp) {
- plen = wcslen(vpp->shebang);
- if (wcsncmp(shebang_line, vpp->shebang, plen) == 0) {
- rc = TRUE;
- *search = vpp->search;
- /* We can do this because all builtin commands contain
- * "python".
- */
- *command = wcsstr(shebang_line, L"python");
- break;
- }
- }
- if (vpp->shebang == NULL) {
- /*
- * Not found in builtins - look in customized commands.
- *
- * We can't permanently modify the shebang line in case
- * it's not a customized command, but we can temporarily
- * stick a NUL after the command while searching for it,
- * then put back the char we zapped.
- */
-#if defined(SKIP_PREFIX)
- skipped = skip_prefix(shebang_line);
-#else
- skipped = shebang_line;
-#endif
- p = wcspbrk(skipped, L" \t\r\n");
- if (p != NULL) {
- zapped = *p;
- *p = L'\0';
- }
- cp = find_command(skipped);
- if (p != NULL)
- *p = zapped;
- if (cp != NULL) {
- *command = cp->value;
- if (p != NULL)
- *suffix = skip_whitespace(p);
- }
- }
- /* remove trailing whitespace */
- while ((endp > shebang_line) && isspace(*endp))
- --endp;
- if (endp > shebang_line)
- endp[1] = L'\0';
- }
- }
- return rc;
-}
-
-/* #define CP_UTF8 65001 defined in winnls.h */
-#define CP_UTF16LE 1200
-#define CP_UTF16BE 1201
-#define CP_UTF32LE 12000
-#define CP_UTF32BE 12001
-
-typedef struct {
- int length;
- char sequence[4];
- UINT code_page;
-} BOM;
-
-/*
- * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself
- * doesn't. Never mind, one day it might - there's no harm leaving it in.
- */
-static BOM BOMs[] = {
- { 3, { 0xEF, 0xBB, 0xBF }, CP_UTF8 }, /* UTF-8 - keep first */
- /* Test UTF-32LE before UTF-16LE since UTF-16LE BOM is a prefix
- * of UTF-32LE BOM. */
- { 4, { 0xFF, 0xFE, 0x00, 0x00 }, CP_UTF32LE }, /* UTF-32LE */
- { 4, { 0x00, 0x00, 0xFE, 0xFF }, CP_UTF32BE }, /* UTF-32BE */
- { 2, { 0xFF, 0xFE }, CP_UTF16LE }, /* UTF-16LE */
- { 2, { 0xFE, 0xFF }, CP_UTF16BE }, /* UTF-16BE */
- { 0 } /* sentinel */
-};
-
-static BOM *
-find_BOM(char * buffer)
-{
-/*
- * Look for a BOM in the input and return a pointer to the
- * corresponding structure, or NULL if not found.
- */
- BOM * result = NULL;
- BOM *bom;
-
- for (bom = BOMs; bom->length; bom++) {
- if (strncmp(bom->sequence, buffer, bom->length) == 0) {
- result = bom;
- break;
- }
- }
- return result;
-}
-
-static char *
-find_terminator(char * buffer, int len, BOM *bom)
-{
- char * result = NULL;
- char * end = buffer + len;
- char * p;
- char c;
- int cp;
-
- for (p = buffer; p < end; p++) {
- c = *p;
- if (c == '\r') {
- result = p;
- break;
- }
- if (c == '\n') {
- result = p;
- break;
- }
- }
- if (result != NULL) {
- cp = bom->code_page;
-
- /* adjustments to include all bytes of the char */
- /* no adjustment needed for UTF-8 or big endian */
- if (cp == CP_UTF16LE)
- ++result;
- else if (cp == CP_UTF32LE)
- result += 3;
- ++result; /* point just past terminator */
- }
- return result;
-}
-
-static BOOL
-validate_version(wchar_t * p)
-{
- /*
- Version information should start with the major version,
- Optionally followed by a period and a minor version,
- Optionally followed by a minus and one of 32 or 64.
- Valid examples:
- 2
- 3
- 2.7
- 3.6
- 2.7-32
- The intent is to add to the valid patterns:
- 3.10
- 3-32
- 3.6-64
- 3-64
- */
- BOOL result = (p != NULL); /* Default to False if null pointer. */
-
- result = result && iswdigit(*p); /* Result = False if first string element is not a digit. */
-
- while (result && iswdigit(*p)) /* Require a major version */
- ++p; /* Skip all leading digit(s) */
- if (result && (*p == L'.')) /* Allow . for major minor separator.*/
- {
- result = iswdigit(*++p); /* Must be at least one digit */
- while (result && iswdigit(*++p)) ; /* Skip any more Digits */
- }
- if (result && (*p == L'-')) { /* Allow - for Bits Separator */
- switch(*++p){
- case L'3': /* 3 is OK */
- result = (*++p == L'2') && !*++p; /* only if followed by 2 and ended.*/
- break;
- case L'6': /* 6 is OK */
- result = (*++p == L'4') && !*++p; /* only if followed by 4 and ended.*/
- break;
- default:
- result = FALSE;
- break;
- }
- }
- result = result && !*p; /* Must have reached EOS */
- return result;
-
-}
-
-typedef struct {
- unsigned short min;
- unsigned short max;
- wchar_t version[MAX_VERSION_SIZE];
-} PYC_MAGIC;
-
-static PYC_MAGIC magic_values[] = {
- { 50823, 50823, L"2.0" },
- { 60202, 60202, L"2.1" },
- { 60717, 60717, L"2.2" },
- { 62011, 62021, L"2.3" },
- { 62041, 62061, L"2.4" },
- { 62071, 62131, L"2.5" },
- { 62151, 62161, L"2.6" },
- { 62171, 62211, L"2.7" },
- { 3000, 3131, L"3.0" },
- { 3141, 3151, L"3.1" },
- { 3160, 3180, L"3.2" },
- { 3190, 3230, L"3.3" },
- { 3250, 3310, L"3.4" },
- { 3320, 3351, L"3.5" },
- { 3360, 3379, L"3.6" },
- { 3390, 3399, L"3.7" },
- { 3400, 3419, L"3.8" },
- { 3420, 3429, L"3.9" },
- { 3430, 3449, L"3.10" },
- /* Allow 50 magic numbers per version from here on */
- { 3450, 3499, L"3.11" },
- { 3500, 3549, L"3.12" },
- { 3550, 3599, L"3.13" },
- { 3600, 3649, L"3.14" },
- { 3650, 3699, L"3.15" },
- { 3700, 3749, L"3.16" },
- { 0 }
-};
-
-static INSTALLED_PYTHON *
-find_by_magic(unsigned short magic)
-{
- INSTALLED_PYTHON * result = NULL;
- PYC_MAGIC * mp;
-
- for (mp = magic_values; mp->min; mp++) {
- if ((magic >= mp->min) && (magic <= mp->max)) {
- result = locate_python(mp->version, FALSE);
- if (result != NULL)
- break;
- }
- }
- return result;
-}
-
-static void
-maybe_handle_shebang(wchar_t ** argv, wchar_t * cmdline)
-{
-/*
- * Look for a shebang line in the first argument. If found
- * and we spawn a child process, this never returns. If it
- * does return then we process the args "normally".
- *
- * argv[0] might be a filename with a shebang.
- */
- FILE * fp;
- errno_t rc = _wfopen_s(&fp, *argv, L"rb");
- char buffer[BUFSIZE];
- wchar_t shebang_line[BUFSIZE + 1];
- size_t read;
- char *p;
- char * start;
- char * shebang_alias = (char *) shebang_line;
- BOM* bom;
- int i, j, nchars = 0;
- int header_len;
- BOOL is_virt;
- BOOL search;
- wchar_t * command;
- wchar_t * suffix;
- COMMAND *cmd = NULL;
- INSTALLED_PYTHON * ip;
-
- if (rc == 0) {
- read = fread(buffer, sizeof(char), BUFSIZE, fp);
- debug(L"maybe_handle_shebang: read %zd bytes\n", read);
- fclose(fp);
-
- if ((read >= 4) && (buffer[3] == '\n') && (buffer[2] == '\r')) {
- ip = find_by_magic((((unsigned char)buffer[1]) << 8 |
- (unsigned char)buffer[0]) & 0xFFFF);
- if (ip != NULL) {
- debug(L"script file is compiled against Python %ls\n",
- ip->version);
- invoke_child(ip->executable, NULL, cmdline);
- }
- }
- /* Look for BOM */
- bom = find_BOM(buffer);
- if (bom == NULL) {
- start = buffer;
- debug(L"maybe_handle_shebang: BOM not found, using UTF-8\n");
- bom = BOMs; /* points to UTF-8 entry - the default */
- }
- else {
- debug(L"maybe_handle_shebang: BOM found, code page %u\n",
- bom->code_page);
- start = &buffer[bom->length];
- }
- p = find_terminator(start, BUFSIZE, bom);
- /*
- * If no CR or LF was found in the heading,
- * we assume it's not a shebang file.
- */
- if (p == NULL) {
- debug(L"maybe_handle_shebang: No line terminator found\n");
- }
- else {
- /*
- * Found line terminator - parse the shebang.
- *
- * Strictly, we don't need to handle UTF-16 anf UTF-32,
- * since Python itself doesn't.
- * Never mind, one day it might.
- */
- header_len = (int) (p - start);
- switch(bom->code_page) {
- case CP_UTF8:
- nchars = MultiByteToWideChar(bom->code_page,
- 0,
- start, header_len, shebang_line,
- BUFSIZE);
- break;
- case CP_UTF16BE:
- if (header_len % 2 != 0) {
- debug(L"maybe_handle_shebang: UTF-16BE, but an odd number \
-of bytes: %d\n", header_len);
- /* nchars = 0; Not needed - initialised to 0. */
- }
- else {
- for (i = header_len; i > 0; i -= 2) {
- shebang_alias[i - 1] = start[i - 2];
- shebang_alias[i - 2] = start[i - 1];
- }
- nchars = header_len / sizeof(wchar_t);
- }
- break;
- case CP_UTF16LE:
- if ((header_len % 2) != 0) {
- debug(L"UTF-16LE, but an odd number of bytes: %d\n",
- header_len);
- /* nchars = 0; Not needed - initialised to 0. */
- }
- else {
- /* no actual conversion needed. */
- memcpy(shebang_line, start, header_len);
- nchars = header_len / sizeof(wchar_t);
- }
- break;
- case CP_UTF32BE:
- if (header_len % 4 != 0) {
- debug(L"UTF-32BE, but not divisible by 4: %d\n",
- header_len);
- /* nchars = 0; Not needed - initialised to 0. */
- }
- else {
- for (i = header_len, j = header_len / 2; i > 0; i -= 4,
- j -= 2) {
- shebang_alias[j - 1] = start[i - 2];
- shebang_alias[j - 2] = start[i - 1];
- }
- nchars = header_len / sizeof(wchar_t);
- }
- break;
- case CP_UTF32LE:
- if (header_len % 4 != 0) {
- debug(L"UTF-32LE, but not divisible by 4: %d\n",
- header_len);
- /* nchars = 0; Not needed - initialised to 0. */
- }
- else {
- for (i = header_len, j = header_len / 2; i > 0; i -= 4,
- j -= 2) {
- shebang_alias[j - 1] = start[i - 3];
- shebang_alias[j - 2] = start[i - 4];
- }
- nchars = header_len / sizeof(wchar_t);
- }
- break;
- }
- if (nchars > 0) {
- shebang_line[--nchars] = L'\0';
- is_virt = parse_shebang(shebang_line, nchars, &command,
- &suffix, &search);
- if (command != NULL) {
- debug(L"parse_shebang: found command: %ls\n", command);
- if (!is_virt) {
- invoke_child(command, suffix, cmdline);
- }
- else {
- suffix = wcschr(command, L' ');
- if (suffix != NULL) {
- *suffix++ = L'\0';
- suffix = skip_whitespace(suffix);
- }
- if (wcsncmp(command, L"python", 6))
- error(RC_BAD_VIRTUAL_PATH, L"Unknown virtual \
-path '%ls'", command);
- command += 6; /* skip past "python" */
- if (search && ((*command == L'\0') || isspace(*command))) {
- /* Command is eligible for path search, and there
- * is no version specification.
- */
- debug(L"searching PATH for python executable\n");
- cmd = find_on_path(PYTHON_EXECUTABLE);
- debug(L"Python on path: %ls\n", cmd ? cmd->value : L"");
- if (cmd) {
- debug(L"located python on PATH: %ls\n", cmd->value);
- invoke_child(cmd->value, suffix, cmdline);
- /* Exit here, as we have found the command */
- return;
- }
- /* FALL THROUGH: No python found on PATH, so fall
- * back to locating the correct installed python.
- */
- }
- if (*command && !validate_version(command))
- error(RC_BAD_VIRTUAL_PATH, L"Invalid version \
-specification: '%ls'.\nIn the first line of the script, 'python' needs to be \
-followed by a valid version specifier.\nPlease check the documentation.",
- command);
- /* TODO could call validate_version(command) */
- ip = locate_python(command, TRUE);
- if (ip == NULL) {
- error(RC_NO_PYTHON, L"Requested Python version \
-(%ls) is not installed", command);
- }
- else {
- invoke_child(ip->executable, suffix, cmdline);
- }
- }
- }
- }
- }
- }
-}
-
-static wchar_t *
-skip_me(wchar_t * cmdline)
-{
- BOOL quoted;
- wchar_t c;
- wchar_t * result = cmdline;
-
- quoted = cmdline[0] == L'\"';
- if (!quoted)
- c = L' ';
- else {
- c = L'\"';
- ++result;
- }
- result = wcschr(result, c);
- if (result == NULL) /* when, for example, just exe name on command line */
- result = L"";
- else {
- ++result; /* skip past space or closing quote */
- result = skip_whitespace(result);
- }
- return result;
-}
-
-static DWORD version_high = 0;
-static DWORD version_low = 0;
-
-static void
-get_version_info(wchar_t * version_text, size_t size)
-{
- WORD maj, min, rel, bld;
-
- if (!version_high && !version_low)
- wcsncpy_s(version_text, size, L"0.1", _TRUNCATE); /* fallback */
- else {
- maj = HIWORD(version_high);
- min = LOWORD(version_high);
- rel = HIWORD(version_low);
- bld = LOWORD(version_low);
- _snwprintf_s(version_text, size, _TRUNCATE, L"%d.%d.%d.%d", maj,
- min, rel, bld);
- }
-}
-
-static void
-show_help_text(wchar_t ** argv)
-{
- wchar_t version_text [MAX_PATH];
-#if defined(_M_X64)
- BOOL canDo64bit = TRUE;
-#else
- /* If we are a 32bit process on a 64bit Windows, first hit the 64bit keys. */
- BOOL canDo64bit = FALSE;
- IsWow64Process(GetCurrentProcess(), &canDo64bit);
-#endif
-
- get_version_info(version_text, MAX_PATH);
- fwprintf(stdout, L"\
-Python Launcher for Windows Version %ls\n\n", version_text);
- fwprintf(stdout, L"\
-usage:\n\
-%ls [launcher-args] [python-args] [script [script-args]]\n\n", argv[0]);
- fputws(L"\
-Launcher arguments:\n\n\
--2 : Launch the latest Python 2.x version\n\
--3 : Launch the latest Python 3.x version\n\
--X.Y : Launch the specified Python version\n", stdout);
- if (canDo64bit) {
- fputws(L"\
- The above all default to 64 bit if a matching 64 bit python is present.\n\
--X.Y-32: Launch the specified 32bit Python version\n\
--X-32 : Launch the latest 32bit Python X version\n\
--X.Y-64: Launch the specified 64bit Python version\n\
--X-64 : Launch the latest 64bit Python X version", stdout);
- }
- fputws(L"\n-0 --list : List the available pythons", stdout);
- fputws(L"\n-0p --list-paths : List with paths", stdout);
- fputws(L"\n\n If no script is specified the specified interpreter is opened.", stdout);
- fputws(L"\nIf an exact version is not given, using the latest version can be overridden by", stdout);
- fputws(L"\nany of the following, (in priority order):", stdout);
- fputws(L"\n An active virtual environment", stdout);
- fputws(L"\n A shebang line in the script (if present)", stdout);
- fputws(L"\n With -2 or -3 flag a matching PY_PYTHON2 or PY_PYTHON3 Environment variable", stdout);
- fputws(L"\n A PY_PYTHON Environment variable", stdout);
- fputws(L"\n From [defaults] in py.ini in your %LOCALAPPDATA%\\py.ini", stdout);
- fputws(L"\n From [defaults] in py.ini beside py.exe (use `where py` to locate)", stdout);
- fputws(L"\n\nThe following help text is from Python:\n\n", stdout);
- fflush(stdout);
-}
-
-static BOOL
-show_python_list(wchar_t ** argv)
-{
- /*
- * Display options -0
- */
- INSTALLED_PYTHON * result = NULL;
- INSTALLED_PYTHON * ip = installed_pythons; /* List of installed pythons */
- INSTALLED_PYTHON * defpy = locate_python(L"", FALSE);
- size_t i = 0;
- wchar_t *p = argv[1];
- wchar_t *ver_fmt = L"-%ls-%d";
- wchar_t *fmt = L"\n %ls";
- wchar_t *defind = L" *"; /* Default indicator */
-
- /*
- * Output informational messages to stderr to keep output
- * clean for use in pipes, etc.
- */
- fwprintf(stderr,
- L"Installed Pythons found by %s Launcher for Windows", argv[0]);
- if (!_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths"))
- fmt = L"\n %-15ls%ls"; /* include path */
-
- if (num_installed_pythons == 0) /* We have somehow got here without searching for pythons */
- locate_all_pythons(); /* Find them, Populates installed_pythons */
-
- if (num_installed_pythons == 0) /* No pythons found */
- fwprintf(stderr, L"\nNo Installed Pythons Found!");
- else
- {
- for (i = 0; i < num_installed_pythons; i++, ip++) {
- wchar_t version[BUFSIZ];
- if (wcscmp(ip->version, L"venv") == 0) {
- wcscpy_s(version, BUFSIZ, L"(venv)");
- }
- else {
- swprintf_s(version, BUFSIZ, ver_fmt, ip->version, ip->bits);
- }
-
- if (ip->exe_display[0]) {
- fwprintf(stdout, fmt, version, ip->exe_display);
- }
- else {
- fwprintf(stdout, fmt, version, ip->executable);
- }
- /* If there is a default indicate it */
- if (defpy == ip)
- fwprintf(stderr, defind);
- }
- }
-
- if ((defpy == NULL) && (num_installed_pythons > 0))
- /* We have pythons but none is the default */
- fwprintf(stderr, L"\n\nCan't find a Default Python.\n\n");
- else
- fwprintf(stderr, L"\n\n"); /* End with a blank line */
- return FALSE; /* If this has been called we cannot continue */
-}
-
-#if defined(VENV_REDIRECT)
-
-static int
-find_home_value(const char *buffer, const char **start, DWORD *length)
-{
- for (const char *s = strstr(buffer, "home"); s; s = strstr(s + 1, "\nhome")) {
- if (*s == '\n') {
- ++s;
- }
- for (int i = 4; i > 0 && *s; --i, ++s);
-
- while (*s && iswspace(*s)) {
- ++s;
- }
- if (*s != L'=') {
- continue;
- }
-
- do {
- ++s;
- } while (*s && iswspace(*s));
-
- *start = s;
- char *nl = strchr(s, '\n');
- if (nl) {
- *length = (DWORD)((ptrdiff_t)nl - (ptrdiff_t)s);
- } else {
- *length = (DWORD)strlen(s);
- }
- return 1;
- }
- return 0;
-}
-#endif
-
-static wchar_t *
-wcsdup_pad(const wchar_t *s, int padding, int *newlen)
-{
- size_t len = wcslen(s);
- len += 1 + padding;
- wchar_t *r = (wchar_t *)malloc(len * sizeof(wchar_t));
- if (!r) {
- return NULL;
- }
- if (wcscpy_s(r, len, s)) {
- free(r);
- return NULL;
- }
- *newlen = len < MAXINT ? (int)len : MAXINT;
- return r;
-}
-
-static wchar_t *
-get_process_name(void)
-{
- DWORD bufferLen = MAX_PATH;
- DWORD len = bufferLen;
- wchar_t *r = NULL;
-
- while (!r) {
- r = (wchar_t *)malloc(bufferLen * sizeof(wchar_t));
- if (!r) {
- error(RC_NO_MEMORY, L"out of memory");
- return NULL;
- }
- len = GetModuleFileNameW(NULL, r, bufferLen);
- if (len == 0) {
- free(r);
- error(0, L"Failed to get module name");
- return NULL;
- } else if (len == bufferLen &&
- GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
- free(r);
- r = NULL;
- bufferLen *= 2;
- }
- }
-
- return r;
-}
-
-static int
-process(int argc, wchar_t ** argv)
-{
- wchar_t * wp;
- wchar_t * command;
- wchar_t * executable;
- wchar_t * p;
- wchar_t * argv0;
- int rc = 0;
- INSTALLED_PYTHON * ip;
- BOOL valid;
- DWORD size, attrs;
- wchar_t message[MSGSIZE];
- void * version_data;
- VS_FIXEDFILEINFO * file_info;
- UINT block_size;
-#if defined(VENV_REDIRECT)
- wchar_t * venv_cfg_path;
- int newlen;
-#elif defined(SCRIPT_WRAPPER)
- wchar_t * newcommand;
- wchar_t * av[2];
- int newlen;
- HRESULT hr;
- int index;
-#else
- HRESULT hr;
- int index;
-#endif
-
- setvbuf(stderr, (char *)NULL, _IONBF, 0);
- wp = get_env(L"PYLAUNCH_DEBUG");
- if ((wp != NULL) && (*wp != L'\0'))
- log_fp = stderr;
-
-#if defined(_M_X64)
- debug(L"launcher build: 64bit\n");
-#else
- debug(L"launcher build: 32bit\n");
-#endif
-#if defined(_WINDOWS)
- debug(L"launcher executable: Windows\n");
-#else
- debug(L"launcher executable: Console\n");
-#endif
-#if !defined(VENV_REDIRECT)
- /* Get the local appdata folder (non-roaming) */
- hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA,
- NULL, 0, appdata_ini_path);
- if (hr != S_OK) {
- debug(L"SHGetFolderPath failed: %X\n", hr);
- appdata_ini_path[0] = L'\0';
- }
- else {
- wcsncat_s(appdata_ini_path, MAX_PATH, L"\\py.ini", _TRUNCATE);
- attrs = GetFileAttributesW(appdata_ini_path);
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- debug(L"File '%ls' non-existent\n", appdata_ini_path);
- appdata_ini_path[0] = L'\0';
- } else {
- debug(L"Using local configuration file '%ls'\n", appdata_ini_path);
- }
- }
-#endif
- argv0 = get_process_name();
- size = GetFileVersionInfoSizeW(argv0, &size);
- if (size == 0) {
- winerror(GetLastError(), message, MSGSIZE);
- debug(L"GetFileVersionInfoSize failed: %ls\n", message);
- }
- else {
- version_data = malloc(size);
- if (version_data) {
- valid = GetFileVersionInfoW(argv0, 0, size,
- version_data);
- if (!valid)
- debug(L"GetFileVersionInfo failed: %X\n", GetLastError());
- else {
- valid = VerQueryValueW(version_data, L"\\",
- (LPVOID *) &file_info, &block_size);
- if (!valid)
- debug(L"VerQueryValue failed: %X\n", GetLastError());
- else {
- version_high = file_info->dwFileVersionMS;
- version_low = file_info->dwFileVersionLS;
- }
- }
- free(version_data);
- }
- }
-
-#if defined(VENV_REDIRECT)
- /* Allocate some extra space for new filenames */
- venv_cfg_path = wcsdup_pad(argv0, 32, &newlen);
- if (!venv_cfg_path) {
- error(RC_NO_MEMORY, L"Failed to copy module name");
- }
- p = wcsrchr(venv_cfg_path, L'\\');
-
- if (p == NULL) {
- error(RC_NO_VENV_CFG, L"No pyvenv.cfg file");
- }
- p[0] = L'\0';
- wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg");
- attrs = GetFileAttributesW(venv_cfg_path);
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- debug(L"File '%ls' non-existent\n", venv_cfg_path);
- p[0] = '\0';
- p = wcsrchr(venv_cfg_path, L'\\');
- if (p != NULL) {
- p[0] = '\0';
- wcscat_s(venv_cfg_path, newlen, L"\\pyvenv.cfg");
- attrs = GetFileAttributesW(venv_cfg_path);
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- debug(L"File '%ls' non-existent\n", venv_cfg_path);
- error(RC_NO_VENV_CFG, L"No pyvenv.cfg file");
- }
- }
- }
- debug(L"Using venv configuration file '%ls'\n", venv_cfg_path);
-#else
- /* Allocate some extra space for new filenames */
- if (wcscpy_s(launcher_ini_path, MAX_PATH, argv0)) {
- error(RC_NO_MEMORY, L"Failed to copy module name");
- }
- p = wcsrchr(launcher_ini_path, L'\\');
-
- if (p == NULL) {
- debug(L"GetModuleFileNameW returned value has no backslash: %ls\n",
- launcher_ini_path);
- launcher_ini_path[0] = L'\0';
- }
- else {
- p[0] = L'\0';
- wcscat_s(launcher_ini_path, MAX_PATH, L"\\py.ini");
- attrs = GetFileAttributesW(launcher_ini_path);
- if (attrs == INVALID_FILE_ATTRIBUTES) {
- debug(L"File '%ls' non-existent\n", launcher_ini_path);
- launcher_ini_path[0] = L'\0';
- } else {
- debug(L"Using global configuration file '%ls'\n", launcher_ini_path);
- }
- }
-#endif
-
- command = skip_me(GetCommandLineW());
- debug(L"Called with command line: %ls\n", command);
-
-#if !defined(VENV_REDIRECT)
- /* bpo-35811: The __PYVENV_LAUNCHER__ variable is used to
- * override sys.executable and locate the original prefix path.
- * However, if it is silently inherited by a non-venv Python
- * process, that process will believe it is running in the venv
- * still. This is the only place where *we* can clear it (that is,
- * when py.exe is being used to launch Python), so we do.
- */
- SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", NULL);
-#endif
-
-#if defined(SCRIPT_WRAPPER)
- /* The launcher is being used in "script wrapper" mode.
- * There should therefore be a Python script named -script.py in
- * the same directory as the launcher executable.
- * Put the script name into argv as the first (script name) argument.
- */
-
- /* Get the wrapped script name - if the script is not present, this will
- * terminate the program with an error.
- */
- locate_wrapped_script();
-
- /* Add the wrapped script to the start of command */
- newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */
- newcommand = malloc(sizeof(wchar_t) * newlen);
- if (!newcommand) {
- error(RC_NO_MEMORY, L"Could not allocate new command line");
- }
- else {
- wcscpy_s(newcommand, newlen, wrapped_script_path);
- wcscat_s(newcommand, newlen, L" ");
- wcscat_s(newcommand, newlen, command);
- debug(L"Running wrapped script with command line '%ls'\n", newcommand);
- read_commands();
- av[0] = wrapped_script_path;
- av[1] = NULL;
- maybe_handle_shebang(av, newcommand);
- /* Returns if no shebang line - pass to default processing */
- command = newcommand;
- valid = FALSE;
- }
-#elif defined(VENV_REDIRECT)
- {
- FILE *f;
- char buffer[4096]; /* 4KB should be enough for anybody */
- char *start;
- DWORD len, cch, cch_actual;
- size_t cb;
- if (_wfopen_s(&f, venv_cfg_path, L"r")) {
- error(RC_BAD_VENV_CFG, L"Cannot read '%ls'", venv_cfg_path);
- }
- cb = fread_s(buffer, sizeof(buffer), sizeof(buffer[0]),
- sizeof(buffer) / sizeof(buffer[0]), f);
- fclose(f);
-
- if (!find_home_value(buffer, &start, &len)) {
- error(RC_BAD_VENV_CFG, L"Cannot find home in '%ls'",
- venv_cfg_path);
- }
-
- cch = MultiByteToWideChar(CP_UTF8, 0, start, len, NULL, 0);
- if (!cch) {
- error(0, L"Cannot determine memory for home path");
- }
- cch += (DWORD)wcslen(PYTHON_EXECUTABLE) + 4; /* include sep, null and quotes */
- executable = (wchar_t *)malloc(cch * sizeof(wchar_t));
- if (executable == NULL) {
- error(RC_NO_MEMORY, L"A memory allocation failed");
- }
- /* start with a quote - we'll skip this ahead, but want it for the final string */
- executable[0] = L'"';
- cch_actual = MultiByteToWideChar(CP_UTF8, 0, start, len, &executable[1], cch - 1);
- if (!cch_actual) {
- error(RC_BAD_VENV_CFG, L"Cannot decode home path in '%ls'",
- venv_cfg_path);
- }
- cch_actual += 1; /* account for the first quote */
- executable[cch_actual] = L'\0';
- if (executable[cch_actual - 1] != L'\\') {
- executable[cch_actual++] = L'\\';
- executable[cch_actual] = L'\0';
- }
- if (wcscat_s(&executable[1], cch - 1, PYTHON_EXECUTABLE)) {
- error(RC_BAD_VENV_CFG, L"Cannot create executable path from '%ls'",
- venv_cfg_path);
- }
- /* there's no trailing quote, so we only have to skip one character for the test */
- if (GetFileAttributesW(&executable[1]) == INVALID_FILE_ATTRIBUTES) {
- error(RC_NO_PYTHON, L"No Python at '%ls'", executable);
- }
- /* now append the final quote */
- wcscat_s(executable, cch, L"\"");
- /* smuggle our original path through */
- if (!SetEnvironmentVariableW(L"__PYVENV_LAUNCHER__", argv0)) {
- error(0, L"Failed to set launcher environment");
- }
- valid = 1;
- }
-#else
- if (argc <= 1) {
- valid = FALSE;
- p = NULL;
- }
- else {
- p = argv[1];
- if ((argc == 2) && // list version args
- (!wcsncmp(p, L"-0", wcslen(L"-0")) ||
- !wcsncmp(p, L"--list", wcslen(L"--list"))))
- {
- show_python_list(argv);
- return rc;
- }
- valid = valid && (*p == L'-') && validate_version(&p[1]);
- if (valid) {
- ip = locate_python(&p[1], FALSE);
- if (ip == NULL)
- {
- fwprintf(stdout, \
- L"Python %ls not found!\n", &p[1]);
- valid = show_python_list(argv);
- error(RC_NO_PYTHON, L"Requested Python version (%ls) not \
-installed, use -0 for available pythons", &p[1]);
- }
- executable = ip->executable;
- command += wcslen(p);
- command = skip_whitespace(command);
- }
- else {
- for (index = 1; index < argc; ++index) {
- if (*argv[index] != L'-')
- break;
- }
- if (index < argc) {
- read_commands();
- maybe_handle_shebang(&argv[index], command);
- }
- }
- }
-#endif
-
- if (!valid) {
- if ((argc == 2) && (!_wcsicmp(p, L"-h") || !_wcsicmp(p, L"--help")))
- show_help_text(argv);
- if ((argc == 2) &&
- (!_wcsicmp(p, L"-0") || !_wcsicmp(p, L"--list") ||
- !_wcsicmp(p, L"-0p") || !_wcsicmp(p, L"--list-paths")))
- {
- executable = NULL; /* Info call only */
- }
- else {
- /* look for the default Python */
- ip = locate_python(L"", FALSE);
- if (ip == NULL)
- error(RC_NO_PYTHON, L"Can't find a default Python.");
- executable = ip->executable;
- }
- }
- if (executable != NULL)
- invoke_child(executable, NULL, command);
- else
- rc = RC_NO_PYTHON;
- return rc;
-}
-
-#if defined(_WINDOWS)
-
-int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
- LPWSTR lpstrCmd, int nShow)
-{
- return process(__argc, __wargv);
-}
-
-#else
-
-int cdecl wmain(int argc, wchar_t ** argv)
-{
- return process(argc, argv);
-}
-
-#endif
diff --git a/PC/launcher2.c b/PC/launcher2.c
deleted file mode 100644
index 4dd18c8eb5462e9..000000000000000
--- a/PC/launcher2.c
+++ /dev/null
@@ -1,2858 +0,0 @@
-/*
- * Rewritten Python launcher for Windows
- *
- * This new rewrite properly handles PEP 514 and allows any registered Python
- * runtime to be launched. It also enables auto-install of versions when they
- * are requested but no installation can be found.
- */
-
-#define __STDC_WANT_LIB_EXT1__ 1
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#define MS_WINDOWS
-#include "patchlevel.h"
-
-#define MAXLEN PATHCCH_MAX_CCH
-#define MSGSIZE 1024
-
-#define RC_NO_STD_HANDLES 100
-#define RC_CREATE_PROCESS 101
-#define RC_BAD_VIRTUAL_PATH 102
-#define RC_NO_PYTHON 103
-#define RC_NO_MEMORY 104
-#define RC_NO_SCRIPT 105
-#define RC_NO_VENV_CFG 106
-#define RC_BAD_VENV_CFG 107
-#define RC_NO_COMMANDLINE 108
-#define RC_INTERNAL_ERROR 109
-#define RC_DUPLICATE_ITEM 110
-#define RC_INSTALLING 111
-#define RC_NO_PYTHON_AT_ALL 112
-#define RC_NO_SHEBANG 113
-#define RC_RECURSIVE_SHEBANG 114
-
-static FILE * log_fp = NULL;
-
-void
-debug(wchar_t * format, ...)
-{
- va_list va;
-
- if (log_fp != NULL) {
- wchar_t buffer[MAXLEN];
- int r = 0;
- va_start(va, format);
- r = vswprintf_s(buffer, MAXLEN, format, va);
- va_end(va);
-
- if (r <= 0) {
- return;
- }
- fputws(buffer, log_fp);
- while (r && isspace(buffer[r])) {
- buffer[r--] = L'\0';
- }
- if (buffer[0]) {
- OutputDebugStringW(buffer);
- }
- }
-}
-
-
-void
-formatWinerror(int rc, wchar_t * message, int size)
-{
- FormatMessageW(
- FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- message, size, NULL);
-}
-
-
-void
-winerror(int err, wchar_t * format, ... )
-{
- va_list va;
- wchar_t message[MSGSIZE];
- wchar_t win_message[MSGSIZE];
- int len;
-
- if (err == 0) {
- err = GetLastError();
- }
-
- va_start(va, format);
- len = _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
- va_end(va);
-
- formatWinerror(err, win_message, MSGSIZE);
- if (len >= 0) {
- _snwprintf_s(&message[len], MSGSIZE - len, _TRUNCATE, L": %s",
- win_message);
- }
-
-#if !defined(_WINDOWS)
- fwprintf(stderr, L"%s\n", message);
-#else
- MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...",
- MB_OK);
-#endif
-}
-
-
-void
-error(wchar_t * format, ... )
-{
- va_list va;
- wchar_t message[MSGSIZE];
-
- va_start(va, format);
- _vsnwprintf_s(message, MSGSIZE, _TRUNCATE, format, va);
- va_end(va);
-
-#if !defined(_WINDOWS)
- fwprintf(stderr, L"%s\n", message);
-#else
- MessageBoxW(NULL, message, L"Python Launcher is sorry to say ...",
- MB_OK);
-#endif
-}
-
-
-typedef BOOL (*PIsWow64Process2)(HANDLE, USHORT*, USHORT*);
-
-
-USHORT
-_getNativeMachine(void)
-{
- static USHORT _nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN;
- if (_nativeMachine == IMAGE_FILE_MACHINE_UNKNOWN) {
- USHORT processMachine;
- HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
- PIsWow64Process2 IsWow64Process2 = kernel32 ?
- (PIsWow64Process2)GetProcAddress(kernel32, "IsWow64Process2") :
- NULL;
- if (!IsWow64Process2) {
- BOOL wow64Process;
- if (!IsWow64Process(NULL, &wow64Process)) {
- winerror(0, L"Checking process type");
- } else if (wow64Process) {
- // We should always be a 32-bit executable, so if running
- // under emulation, it must be a 64-bit host.
- _nativeMachine = IMAGE_FILE_MACHINE_AMD64;
- } else {
- // Not running under emulation, and an old enough OS to not
- // have IsWow64Process2, so assume it's x86.
- _nativeMachine = IMAGE_FILE_MACHINE_I386;
- }
- } else if (!IsWow64Process2(NULL, &processMachine, &_nativeMachine)) {
- winerror(0, L"Checking process type");
- }
- }
- return _nativeMachine;
-}
-
-
-bool
-isAMD64Host(void)
-{
- return _getNativeMachine() == IMAGE_FILE_MACHINE_AMD64;
-}
-
-
-bool
-isARM64Host(void)
-{
- return _getNativeMachine() == IMAGE_FILE_MACHINE_ARM64;
-}
-
-
-bool
-isEnvVarSet(const wchar_t *name)
-{
- /* only looking for non-empty, which means at least one character
- and the null terminator */
- return GetEnvironmentVariableW(name, NULL, 0) >= 2;
-}
-
-
-bool
-join(wchar_t *buffer, size_t bufferLength, const wchar_t *fragment)
-{
- if (SUCCEEDED(PathCchCombineEx(buffer, bufferLength, buffer, fragment, PATHCCH_ALLOW_LONG_PATHS))) {
- return true;
- }
- return false;
-}
-
-
-bool
-split_parent(wchar_t *buffer, size_t bufferLength)
-{
- return SUCCEEDED(PathCchRemoveFileSpec(buffer, bufferLength));
-}
-
-
-int
-_compare(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
-{
- // Empty strings sort first
- if (!x || !xLen) {
- return (!y || !yLen) ? 0 : -1;
- } else if (!y || !yLen) {
- return 1;
- }
- switch (CompareStringEx(
- LOCALE_NAME_INVARIANT, NORM_IGNORECASE | SORT_DIGITSASNUMBERS,
- x, xLen, y, yLen,
- NULL, NULL, 0
- )) {
- case CSTR_LESS_THAN:
- return -1;
- case CSTR_EQUAL:
- return 0;
- case CSTR_GREATER_THAN:
- return 1;
- default:
- winerror(0, L"Error comparing '%.*s' and '%.*s' (compare)", xLen, x, yLen, y);
- return -1;
- }
-}
-
-
-int
-_compareArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
-{
- // Empty strings sort first
- if (!x || !xLen) {
- return (!y || !yLen) ? 0 : -1;
- } else if (!y || !yLen) {
- return 1;
- }
- switch (CompareStringEx(
- LOCALE_NAME_INVARIANT, 0,
- x, xLen, y, yLen,
- NULL, NULL, 0
- )) {
- case CSTR_LESS_THAN:
- return -1;
- case CSTR_EQUAL:
- return 0;
- case CSTR_GREATER_THAN:
- return 1;
- default:
- winerror(0, L"Error comparing '%.*s' and '%.*s' (compareArgument)", xLen, x, yLen, y);
- return -1;
- }
-}
-
-int
-_comparePath(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
-{
- // Empty strings sort first
- if (!x || !xLen) {
- return !y || !yLen ? 0 : -1;
- } else if (!y || !yLen) {
- return 1;
- }
- switch (CompareStringOrdinal(x, xLen, y, yLen, TRUE)) {
- case CSTR_LESS_THAN:
- return -1;
- case CSTR_EQUAL:
- return 0;
- case CSTR_GREATER_THAN:
- return 1;
- default:
- winerror(0, L"Error comparing '%.*s' and '%.*s' (comparePath)", xLen, x, yLen, y);
- return -1;
- }
-}
-
-
-bool
-_startsWith(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
-{
- if (!x || !y) {
- return false;
- }
- yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen;
- xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen;
- return xLen >= yLen && 0 == _compare(x, yLen, y, yLen);
-}
-
-
-bool
-_startsWithArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
-{
- if (!x || !y) {
- return false;
- }
- yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen;
- xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen;
- return xLen >= yLen && 0 == _compareArgument(x, yLen, y, yLen);
-}
-
-
-// Unlike regular startsWith, this function requires that the following
-// character is either NULL (that is, the entire string matches) or is one of
-// the characters in 'separators'.
-bool
-_startsWithSeparated(const wchar_t *x, int xLen, const wchar_t *y, int yLen, const wchar_t *separators)
-{
- if (!x || !y) {
- return false;
- }
- yLen = yLen < 0 ? (int)wcsnlen_s(y, MAXLEN) : yLen;
- xLen = xLen < 0 ? (int)wcsnlen_s(x, MAXLEN) : xLen;
- if (xLen < yLen) {
- return false;
- }
- if (xLen == yLen) {
- return 0 == _compare(x, xLen, y, yLen);
- }
- return separators &&
- 0 == _compare(x, yLen, y, yLen) &&
- wcschr(separators, x[yLen]) != NULL;
-}
-
-
-
-/******************************************************************************\
- *** HELP TEXT ***
-\******************************************************************************/
-
-
-int
-showHelpText(wchar_t ** argv)
-{
- // The help text is stored in launcher-usage.txt, which is compiled into
- // the launcher and loaded at runtime if needed.
- //
- // The file must be UTF-8. There are two substitutions:
- // %ls - PY_VERSION (as wchar_t*)
- // %ls - argv[0] (as wchar_t*)
- HRSRC res = FindResourceExW(NULL, L"USAGE", MAKEINTRESOURCE(1), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
- HGLOBAL resData = res ? LoadResource(NULL, res) : NULL;
- const char *usage = resData ? (const char*)LockResource(resData) : NULL;
- if (usage == NULL) {
- winerror(0, L"Unable to load usage text");
- return RC_INTERNAL_ERROR;
- }
-
- DWORD cbData = SizeofResource(NULL, res);
- DWORD cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, NULL, 0);
- if (!cchUsage) {
- winerror(0, L"Unable to preprocess usage text");
- return RC_INTERNAL_ERROR;
- }
-
- cchUsage += 1;
- wchar_t *wUsage = (wchar_t*)malloc(cchUsage * sizeof(wchar_t));
- cchUsage = MultiByteToWideChar(CP_UTF8, 0, usage, cbData, wUsage, cchUsage);
- if (!cchUsage) {
- winerror(0, L"Unable to preprocess usage text");
- free((void *)wUsage);
- return RC_INTERNAL_ERROR;
- }
- // Ensure null termination
- wUsage[cchUsage] = L'\0';
-
- fwprintf(stdout, wUsage, (L"" PY_VERSION), argv[0]);
- fflush(stdout);
-
- free((void *)wUsage);
-
- return 0;
-}
-
-
-/******************************************************************************\
- *** SEARCH INFO ***
-\******************************************************************************/
-
-
-struct _SearchInfoBuffer {
- struct _SearchInfoBuffer *next;
- wchar_t buffer[0];
-};
-
-
-typedef struct {
- // the original string, managed by the OS
- const wchar_t *originalCmdLine;
- // pointer into the cmdline to mark what we've consumed
- const wchar_t *restOfCmdLine;
- // if known/discovered, the full executable path of our runtime
- const wchar_t *executablePath;
- // pointer and length into cmdline for the file to check for a
- // shebang line, if any. Length can be -1 if the string is null
- // terminated.
- const wchar_t *scriptFile;
- int scriptFileLength;
- // pointer and length into cmdline or a static string with the
- // name of the target executable. Length can be -1 if the string
- // is null terminated.
- const wchar_t *executable;
- int executableLength;
- // pointer and length into a string with additional interpreter
- // arguments to include before restOfCmdLine. Length can be -1 if
- // the string is null terminated.
- const wchar_t *executableArgs;
- int executableArgsLength;
- // pointer and length into cmdline or a static string with the
- // company name for PEP 514 lookup. Length can be -1 if the string
- // is null terminated.
- const wchar_t *company;
- int companyLength;
- // pointer and length into cmdline or a static string with the
- // tag for PEP 514 lookup. Length can be -1 if the string is
- // null terminated.
- const wchar_t *tag;
- int tagLength;
- // if true, treats 'tag' as a non-PEP 514 filter
- bool oldStyleTag;
- // if true, ignores 'tag' when a high priority environment is found
- // gh-92817: This is currently set when a tag is read from configuration,
- // the environment, or a shebang, rather than the command line, and the
- // only currently possible high priority environment is an active virtual
- // environment
- bool lowPriorityTag;
- // if true, allow PEP 514 lookup to override 'executable'
- bool allowExecutableOverride;
- // if true, allow a nearby pyvenv.cfg to locate the executable
- bool allowPyvenvCfg;
- // if true, allow defaults (env/py.ini) to clarify/override tags
- bool allowDefaults;
- // if true, prefer windowed (console-less) executable
- bool windowed;
- // if true, only list detected runtimes without launching
- bool list;
- // if true, only list detected runtimes with paths without launching
- bool listPaths;
- // if true, display help message before continuing
- bool help;
- // if set, limits search to registry keys with the specified Company
- // This is intended for debugging and testing only
- const wchar_t *limitToCompany;
- // dynamically allocated buffers to free later
- struct _SearchInfoBuffer *_buffer;
-} SearchInfo;
-
-
-wchar_t *
-allocSearchInfoBuffer(SearchInfo *search, int wcharCount)
-{
- struct _SearchInfoBuffer *buffer = (struct _SearchInfoBuffer*)malloc(
- sizeof(struct _SearchInfoBuffer) +
- wcharCount * sizeof(wchar_t)
- );
- if (!buffer) {
- return NULL;
- }
- buffer->next = search->_buffer;
- search->_buffer = buffer;
- return buffer->buffer;
-}
-
-
-void
-freeSearchInfo(SearchInfo *search)
-{
- struct _SearchInfoBuffer *b = search->_buffer;
- search->_buffer = NULL;
- while (b) {
- struct _SearchInfoBuffer *nextB = b->next;
- free((void *)b);
- b = nextB;
- }
-}
-
-
-void
-_debugStringAndLength(const wchar_t *s, int len, const wchar_t *name)
-{
- if (!s) {
- debug(L"%s: (null)\n", name);
- } else if (len == 0) {
- debug(L"%s: (empty)\n", name);
- } else if (len < 0) {
- debug(L"%s: %s\n", name, s);
- } else {
- debug(L"%s: %.*ls\n", name, len, s);
- }
-}
-
-
-void
-dumpSearchInfo(SearchInfo *search)
-{
- if (!log_fp) {
- return;
- }
-
-#ifdef __clang__
-#define DEBUGNAME(s) L # s
-#else
-#define DEBUGNAME(s) # s
-#endif
-#define DEBUG(s) debug(L"SearchInfo." DEBUGNAME(s) L": %s\n", (search->s) ? (search->s) : L"(null)")
-#define DEBUG_2(s, sl) _debugStringAndLength((search->s), (search->sl), L"SearchInfo." DEBUGNAME(s))
-#define DEBUG_BOOL(s) debug(L"SearchInfo." DEBUGNAME(s) L": %s\n", (search->s) ? L"True" : L"False")
- DEBUG(originalCmdLine);
- DEBUG(restOfCmdLine);
- DEBUG(executablePath);
- DEBUG_2(scriptFile, scriptFileLength);
- DEBUG_2(executable, executableLength);
- DEBUG_2(executableArgs, executableArgsLength);
- DEBUG_2(company, companyLength);
- DEBUG_2(tag, tagLength);
- DEBUG_BOOL(oldStyleTag);
- DEBUG_BOOL(lowPriorityTag);
- DEBUG_BOOL(allowDefaults);
- DEBUG_BOOL(allowExecutableOverride);
- DEBUG_BOOL(windowed);
- DEBUG_BOOL(list);
- DEBUG_BOOL(listPaths);
- DEBUG_BOOL(help);
- DEBUG(limitToCompany);
-#undef DEBUG_BOOL
-#undef DEBUG_2
-#undef DEBUG
-#undef DEBUGNAME
-}
-
-
-int
-findArgv0Length(const wchar_t *buffer, int bufferLength)
-{
- // Note: this implements semantics that are only valid for argv0.
- // Specifically, there is no escaping of quotes, and quotes within
- // the argument have no effect. A quoted argv0 must start and end
- // with a double quote character; otherwise, it ends at the first
- // ' ' or '\t'.
- int quoted = buffer[0] == L'"';
- for (int i = 1; bufferLength < 0 || i < bufferLength; ++i) {
- switch (buffer[i]) {
- case L'\0':
- return i;
- case L' ':
- case L'\t':
- if (!quoted) {
- return i;
- }
- break;
- case L'"':
- if (quoted) {
- return i + 1;
- }
- break;
- }
- }
- return bufferLength;
-}
-
-
-const wchar_t *
-findArgv0End(const wchar_t *buffer, int bufferLength)
-{
- return &buffer[findArgv0Length(buffer, bufferLength)];
-}
-
-
-/******************************************************************************\
- *** COMMAND-LINE PARSING ***
-\******************************************************************************/
-
-// Adapted from https://stackoverflow.com/a/65583702
-typedef struct AppExecLinkFile { // For tag IO_REPARSE_TAG_APPEXECLINK
- DWORD reparseTag;
- WORD reparseDataLength;
- WORD reserved;
- ULONG version;
- wchar_t stringList[MAX_PATH * 4]; // Multistring (Consecutive UTF-16 strings each ending with a NUL)
- /* There are normally 4 strings here. Ex:
- Package ID: L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"
- Entry Point: L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe!PythonRedirector"
- Executable: L"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.17.106910_x64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe"
- Applic. Type: L"0" // Integer as ASCII. "0" = Desktop bridge application; Else sandboxed UWP application
- */
-} AppExecLinkFile;
-
-
-int
-parseCommandLine(SearchInfo *search)
-{
- if (!search || !search->originalCmdLine) {
- return RC_NO_COMMANDLINE;
- }
-
- const wchar_t *argv0End = findArgv0End(search->originalCmdLine, -1);
- const wchar_t *tail = argv0End; // will be start of the executable name
- const wchar_t *end = argv0End; // will be end of the executable name
- search->restOfCmdLine = argv0End; // will be first space after argv0
- while (--tail != search->originalCmdLine) {
- if (*tail == L'"' && end == argv0End) {
- // Move the "end" up to the quote, so we also allow moving for
- // a period later on.
- end = argv0End = tail;
- } else if (*tail == L'.' && end == argv0End) {
- end = tail;
- } else if (*tail == L'\\' || *tail == L'/') {
- ++tail;
- break;
- }
- }
- if (tail == search->originalCmdLine && tail[0] == L'"') {
- ++tail;
- }
- // Without special cases, we can now fill in the search struct
- int tailLen = (int)(end ? (end - tail) : wcsnlen_s(tail, MAXLEN));
- search->executableLength = -1;
-
- // Our special cases are as follows
-#define MATCHES(s) (0 == _comparePath(tail, tailLen, (s), -1))
-#define STARTSWITH(s) _startsWith(tail, tailLen, (s), -1)
- if (MATCHES(L"py")) {
- search->executable = L"python.exe";
- search->allowExecutableOverride = true;
- search->allowDefaults = true;
- } else if (MATCHES(L"pyw")) {
- search->executable = L"pythonw.exe";
- search->allowExecutableOverride = true;
- search->allowDefaults = true;
- search->windowed = true;
- } else if (MATCHES(L"py_d")) {
- search->executable = L"python_d.exe";
- search->allowExecutableOverride = true;
- search->allowDefaults = true;
- } else if (MATCHES(L"pyw_d")) {
- search->executable = L"pythonw_d.exe";
- search->allowExecutableOverride = true;
- search->allowDefaults = true;
- search->windowed = true;
- } else if (STARTSWITH(L"python3")) {
- search->executable = L"python.exe";
- search->tag = &tail[6];
- search->tagLength = tailLen - 6;
- search->allowExecutableOverride = true;
- search->oldStyleTag = true;
- search->allowPyvenvCfg = true;
- } else if (STARTSWITH(L"pythonw3")) {
- search->executable = L"pythonw.exe";
- search->tag = &tail[7];
- search->tagLength = tailLen - 7;
- search->allowExecutableOverride = true;
- search->oldStyleTag = true;
- search->allowPyvenvCfg = true;
- search->windowed = true;
- } else {
- search->executable = tail;
- search->executableLength = tailLen;
- search->allowPyvenvCfg = true;
- }
-#undef STARTSWITH
-#undef MATCHES
-
- // First argument might be one of our options. If so, consume it,
- // update flags and then set restOfCmdLine.
- const wchar_t *arg = search->restOfCmdLine;
- while(*arg && isspace(*arg)) { ++arg; }
-#define MATCHES(s) (0 == _compareArgument(arg, argLen, (s), -1))
-#define STARTSWITH(s) _startsWithArgument(arg, argLen, (s), -1)
- if (*arg && *arg == L'-' && *++arg) {
- tail = arg;
- while (*tail && !isspace(*tail)) { ++tail; }
- int argLen = (int)(tail - arg);
- if (argLen > 0) {
- if (STARTSWITH(L"2") || STARTSWITH(L"3")) {
- // All arguments starting with 2 or 3 are assumed to be version tags
- search->tag = arg;
- search->tagLength = argLen;
- search->oldStyleTag = true;
- search->restOfCmdLine = tail;
- } else if (STARTSWITH(L"V:") || STARTSWITH(L"-version:")) {
- // Arguments starting with 'V:' specify company and/or tag
- const wchar_t *argStart = wcschr(arg, L':') + 1;
- const wchar_t *tagStart = wcschr(argStart, L'/') ;
- if (tagStart) {
- search->company = argStart;
- search->companyLength = (int)(tagStart - argStart);
- search->tag = tagStart + 1;
- } else {
- search->tag = argStart;
- }
- search->tagLength = (int)(tail - search->tag);
- search->allowDefaults = false;
- search->restOfCmdLine = tail;
- } else if (MATCHES(L"0") || MATCHES(L"-list")) {
- search->list = true;
- search->restOfCmdLine = tail;
- } else if (MATCHES(L"0p") || MATCHES(L"-list-paths")) {
- search->listPaths = true;
- search->restOfCmdLine = tail;
- } else if (MATCHES(L"h") || MATCHES(L"-help")) {
- search->help = true;
- // Do not update restOfCmdLine so that we trigger the help
- // message from whichever interpreter we select
- }
- }
- }
-#undef STARTSWITH
-#undef MATCHES
-
- // Might have a script filename. If it looks like a filename, add
- // it to the SearchInfo struct for later reference.
- arg = search->restOfCmdLine;
- while(*arg && isspace(*arg)) { ++arg; }
- if (*arg && *arg != L'-') {
- search->scriptFile = arg;
- if (*arg == L'"') {
- ++search->scriptFile;
- while (*++arg && *arg != L'"') { }
- } else {
- while (*arg && !isspace(*arg)) { ++arg; }
- }
- search->scriptFileLength = (int)(arg - search->scriptFile);
- }
-
- return 0;
-}
-
-
-int
-_decodeShebang(SearchInfo *search, const char *buffer, int bufferLength, bool onlyUtf8, wchar_t **decoded, int *decodedLength)
-{
- DWORD cp = CP_UTF8;
- int wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0);
- if (!wideLen) {
- cp = CP_ACP;
- wideLen = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, buffer, bufferLength, NULL, 0);
- if (!wideLen) {
- debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError());
- return RC_BAD_VIRTUAL_PATH;
- }
- }
- wchar_t *b = allocSearchInfoBuffer(search, wideLen + 1);
- if (!b) {
- return RC_NO_MEMORY;
- }
- wideLen = MultiByteToWideChar(cp, 0, buffer, bufferLength, b, wideLen + 1);
- if (!wideLen) {
- debug(L"# Failed to decode shebang line (0x%08X)\n", GetLastError());
- return RC_BAD_VIRTUAL_PATH;
- }
- b[wideLen] = L'\0';
- *decoded = b;
- *decodedLength = wideLen;
- return 0;
-}
-
-
-bool
-_shebangStartsWith(const wchar_t *buffer, int bufferLength, const wchar_t *prefix, const wchar_t **rest, int *firstArgumentLength)
-{
- int prefixLength = (int)wcsnlen_s(prefix, MAXLEN);
- if (bufferLength < prefixLength || !_startsWithArgument(buffer, bufferLength, prefix, prefixLength)) {
- return false;
- }
- if (rest) {
- *rest = &buffer[prefixLength];
- }
- if (firstArgumentLength) {
- int i = prefixLength;
- while (i < bufferLength && !isspace(buffer[i])) {
- i += 1;
- }
- *firstArgumentLength = i - prefixLength;
- }
- return true;
-}
-
-
-int
-ensure_no_redirector_stub(wchar_t* filename, wchar_t* buffer)
-{
- // Make sure we didn't find a reparse point that will open the Microsoft Store
- // If we did, pretend there was no shebang and let normal handling take over
- WIN32_FIND_DATAW findData;
- HANDLE hFind = FindFirstFileW(buffer, &findData);
- if (!hFind) {
- // Let normal handling take over
- debug(L"# Did not find %s on PATH\n", filename);
- return RC_NO_SHEBANG;
- }
-
- FindClose(hFind);
-
- if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
- findData.dwReserved0 & IO_REPARSE_TAG_APPEXECLINK)) {
- return 0;
- }
-
- HANDLE hReparsePoint = CreateFileW(buffer, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL);
- if (!hReparsePoint) {
- // Let normal handling take over
- debug(L"# Did not find %s on PATH\n", filename);
- return RC_NO_SHEBANG;
- }
-
- AppExecLinkFile appExecLink;
-
- if (!DeviceIoControl(hReparsePoint, FSCTL_GET_REPARSE_POINT, NULL, 0, &appExecLink, sizeof(appExecLink), NULL, NULL)) {
- // Let normal handling take over
- debug(L"# Did not find %s on PATH\n", filename);
- CloseHandle(hReparsePoint);
- return RC_NO_SHEBANG;
- }
-
- CloseHandle(hReparsePoint);
-
- const wchar_t* redirectorPackageId = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe";
-
- if (0 == wcscmp(appExecLink.stringList, redirectorPackageId)) {
- debug(L"# ignoring redirector that would launch store\n");
- return RC_NO_SHEBANG;
- }
-
- return 0;
-}
-
-
-int
-searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength)
-{
- if (isEnvVarSet(L"PYLAUNCHER_NO_SEARCH_PATH")) {
- return RC_NO_SHEBANG;
- }
-
- wchar_t *command;
- int commandLength;
- if (!_shebangStartsWith(shebang, shebangLength, L"/usr/bin/env ", &command, &commandLength)) {
- return RC_NO_SHEBANG;
- }
-
- if (!commandLength || commandLength == MAXLEN) {
- return RC_BAD_VIRTUAL_PATH;
- }
-
- int lastDot = commandLength;
- while (lastDot > 0 && command[lastDot] != L'.') {
- lastDot -= 1;
- }
- if (!lastDot) {
- lastDot = commandLength;
- }
-
- wchar_t filename[MAXLEN];
- if (wcsncpy_s(filename, MAXLEN, command, commandLength)) {
- return RC_BAD_VIRTUAL_PATH;
- }
-
- const wchar_t *ext = L".exe";
- // If the command already has an extension, we do not want to add it again
- if (!lastDot || _comparePath(&filename[lastDot], -1, ext, -1)) {
- if (wcscat_s(filename, MAXLEN, L".exe")) {
- return RC_BAD_VIRTUAL_PATH;
- }
- }
-
- debug(L"# Search PATH for %s\n", filename);
-
- wchar_t pathVariable[MAXLEN];
- int n = GetEnvironmentVariableW(L"PATH", pathVariable, MAXLEN);
- if (!n) {
- if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
- return RC_NO_SHEBANG;
- }
- winerror(0, L"Failed to read PATH\n", filename);
- return RC_INTERNAL_ERROR;
- }
-
- wchar_t buffer[MAXLEN];
- n = SearchPathW(pathVariable, filename, NULL, MAXLEN, buffer, NULL);
- if (!n) {
- if (GetLastError() == ERROR_FILE_NOT_FOUND) {
- debug(L"# Did not find %s on PATH\n", filename);
- // If we didn't find it on PATH, let normal handling take over
- return RC_NO_SHEBANG;
- }
- // Other errors should cause us to break
- winerror(0, L"Failed to find %s on PATH\n", filename);
- return RC_BAD_VIRTUAL_PATH;
- }
-
- int result = ensure_no_redirector_stub(filename, buffer);
- if (result) {
- return result;
- }
-
- // Check that we aren't going to call ourselves again
- // If we are, pretend there was no shebang and let normal handling take over
- if (GetModuleFileNameW(NULL, filename, MAXLEN) &&
- 0 == _comparePath(filename, -1, buffer, -1)) {
- debug(L"# ignoring recursive shebang command\n");
- return RC_RECURSIVE_SHEBANG;
- }
-
- wchar_t *buf = allocSearchInfoBuffer(search, n + 1);
- if (!buf || wcscpy_s(buf, n + 1, buffer)) {
- return RC_NO_MEMORY;
- }
-
- search->executablePath = buf;
- search->executableArgs = &command[commandLength];
- search->executableArgsLength = shebangLength - commandLength;
- debug(L"# Found %s on PATH\n", buf);
-
- return 0;
-}
-
-
-int
-_readIni(const wchar_t *section, const wchar_t *settingName, wchar_t *buffer, int bufferLength)
-{
- wchar_t iniPath[MAXLEN];
- int n;
- // Check for _PYLAUNCHER_INIDIR override (used for test isolation)
- DWORD len = GetEnvironmentVariableW(L"_PYLAUNCHER_INIDIR", iniPath, MAXLEN);
- if (len && len < MAXLEN) {
- if (join(iniPath, MAXLEN, L"py.ini")) {
- debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName);
- n = GetPrivateProfileStringW(section, settingName, NULL, buffer, bufferLength, iniPath);
- if (n) {
- debug(L"# Found %s in %s\n", settingName, iniPath);
- return n;
- }
- }
- // When _PYLAUNCHER_INIDIR is set, skip the default locations
- return 0;
- }
- if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, iniPath)) &&
- join(iniPath, MAXLEN, L"py.ini")) {
- debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName);
- n = GetPrivateProfileStringW(section, settingName, NULL, buffer, bufferLength, iniPath);
- if (n) {
- debug(L"# Found %s in %s\n", settingName, iniPath);
- return n;
- } else if (GetLastError() == ERROR_FILE_NOT_FOUND) {
- debug(L"# Did not find file %s\n", iniPath);
- } else {
- winerror(0, L"Failed to read from %s\n", iniPath);
- }
- }
- if (GetModuleFileNameW(NULL, iniPath, MAXLEN) &&
- SUCCEEDED(PathCchRemoveFileSpec(iniPath, MAXLEN)) &&
- join(iniPath, MAXLEN, L"py.ini")) {
- debug(L"# Reading from %s for %s/%s\n", iniPath, section, settingName);
- n = GetPrivateProfileStringW(section, settingName, NULL, buffer, MAXLEN, iniPath);
- if (n) {
- debug(L"# Found %s in %s\n", settingName, iniPath);
- return n;
- } else if (GetLastError() == ERROR_FILE_NOT_FOUND) {
- debug(L"# Did not find file %s\n", iniPath);
- } else {
- winerror(0, L"Failed to read from %s\n", iniPath);
- }
- }
- return 0;
-}
-
-
-bool
-_findCommand(SearchInfo *search, const wchar_t *command, int commandLength)
-{
- wchar_t commandBuffer[MAXLEN];
- wchar_t buffer[MAXLEN];
- wcsncpy_s(commandBuffer, MAXLEN, command, commandLength);
- int n = _readIni(L"commands", commandBuffer, buffer, MAXLEN);
- if (!n) {
- return false;
- }
- wchar_t *path = allocSearchInfoBuffer(search, n + 1);
- if (!path) {
- return false;
- }
- wcscpy_s(path, n + 1, buffer);
- search->executablePath = path;
- return true;
-}
-
-
-int
-_useShebangAsExecutable(SearchInfo *search, const wchar_t *shebang, int shebangLength)
-{
- wchar_t buffer[MAXLEN];
- wchar_t script[MAXLEN];
- wchar_t command[MAXLEN];
-
- int commandLength = 0;
- int inQuote = 0;
-
- if (!shebang || !shebangLength) {
- return 0;
- }
-
- wchar_t *pC = command;
- for (int i = 0; i < shebangLength; ++i) {
- wchar_t c = shebang[i];
- if (isspace(c) && !inQuote) {
- commandLength = i;
- break;
- } else if (c == L'"') {
- inQuote = !inQuote;
- } else if (c == L'/' || c == L'\\') {
- *pC++ = L'\\';
- } else {
- *pC++ = c;
- }
- }
- *pC = L'\0';
-
- if (!GetCurrentDirectoryW(MAXLEN, buffer) ||
- wcsncpy_s(script, MAXLEN, search->scriptFile, search->scriptFileLength) ||
- FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, script,
- PATHCCH_ALLOW_LONG_PATHS)) ||
- FAILED(PathCchRemoveFileSpec(buffer, MAXLEN)) ||
- FAILED(PathCchCombineEx(buffer, MAXLEN, buffer, command,
- PATHCCH_ALLOW_LONG_PATHS))
- ) {
- return RC_NO_MEMORY;
- }
-
- int n = (int)wcsnlen(buffer, MAXLEN);
- wchar_t *path = allocSearchInfoBuffer(search, n + 1);
- if (!path) {
- return RC_NO_MEMORY;
- }
- wcscpy_s(path, n + 1, buffer);
- search->executablePath = path;
- if (commandLength) {
- search->executableArgs = &shebang[commandLength];
- search->executableArgsLength = shebangLength - commandLength;
- }
- return 0;
-}
-
-
-int
-checkShebang(SearchInfo *search)
-{
- // Do not check shebang if a tag was provided or if no script file
- // was found on the command line.
- if (search->tag || !search->scriptFile) {
- return 0;
- }
-
- if (search->scriptFileLength < 0) {
- search->scriptFileLength = (int)wcsnlen_s(search->scriptFile, MAXLEN);
- }
-
- wchar_t *scriptFile = (wchar_t*)malloc(sizeof(wchar_t) * (search->scriptFileLength + 1));
- if (!scriptFile) {
- return RC_NO_MEMORY;
- }
-
- wcsncpy_s(scriptFile, search->scriptFileLength + 1,
- search->scriptFile, search->scriptFileLength);
-
- HANDLE hFile = CreateFileW(scriptFile, GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- NULL, OPEN_EXISTING, 0, NULL);
-
- if (hFile == INVALID_HANDLE_VALUE) {
- debug(L"# Failed to open %s for shebang parsing (0x%08X)\n",
- scriptFile, GetLastError());
- free(scriptFile);
- return RC_NO_SCRIPT;
- }
-
- DWORD bytesRead = 0;
- unsigned char buffer[4096];
- if (!ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL)) {
- debug(L"# Failed to read %s for shebang parsing (0x%08X)\n",
- scriptFile, GetLastError());
- free(scriptFile);
- return 0;
- }
-
- CloseHandle(hFile);
- debug(L"# Read %d bytes from %s to find shebang line\n", bytesRead, scriptFile);
- free(scriptFile);
-
-
- unsigned char *b = buffer;
- bool onlyUtf8 = false;
- if (bytesRead > 3 && *b == 0xEF) {
- if (*++b == 0xBB && *++b == 0xBF) {
- // Allow a UTF-8 BOM
- ++b;
- bytesRead -= 3;
- onlyUtf8 = true;
- } else {
- debug(L"# Invalid BOM in shebang line");
- return 0;
- }
- }
- if (bytesRead <= 2 || b[0] != '#' || b[1] != '!') {
- // No shebang (#!) at start of line
- debug(L"# No valid shebang line");
- return 0;
- }
- ++b;
- --bytesRead;
- while (--bytesRead > 0 && isspace(*++b)) { }
- const unsigned char *start = b;
- while (--bytesRead > 0 && *++b != '\r' && *b != '\n') { }
- wchar_t *shebang;
- int shebangLength;
- // We add 1 when bytesRead==0, as in that case we hit EOF and b points
- // to the last character in the file, not the newline
- int exitCode = _decodeShebang(search, (const char*)start, (int)(b - start + (bytesRead == 0)), onlyUtf8, &shebang, &shebangLength);
- if (exitCode) {
- return exitCode;
- }
- debug(L"Shebang: %s\n", shebang);
-
- // Handle shebangs that we should search PATH for
- int executablePathWasSetByUsrBinEnv = 0;
- exitCode = searchPath(search, shebang, shebangLength);
- if (exitCode == 0) {
- executablePathWasSetByUsrBinEnv = 1;
- } else if (exitCode != RC_NO_SHEBANG) {
- return exitCode;
- }
-
- // Handle some known, case-sensitive shebangs
- const wchar_t *command;
- int commandLength;
- // Each template must end with "python"
- static const wchar_t *shebangTemplates[] = {
- L"/usr/bin/env python",
- L"/usr/bin/python",
- L"/usr/local/bin/python",
- L"python",
- NULL
- };
-
- for (const wchar_t **tmpl = shebangTemplates; *tmpl; ++tmpl) {
- // Just to make sure we don't mess this up in the future
- assert(0 == wcscmp(L"python", (*tmpl) + wcslen(*tmpl) - 6));
-
- if (_shebangStartsWith(shebang, shebangLength, *tmpl, &command, &commandLength)) {
- // Search for "python{command}" overrides. All templates end with
- // "python", so we prepend it by jumping back 6 characters
- if (_findCommand(search, &command[-6], commandLength + 6)) {
- search->executableArgs = &command[commandLength];
- search->executableArgsLength = shebangLength - commandLength;
- debug(L"# Treating shebang command '%.*s' as %s\n",
- commandLength + 6, &command[-6], search->executablePath);
- return 0;
- }
-
- search->tag = command;
- search->tagLength = commandLength;
- // If we had 'python3.12.exe' then we want to strip the suffix
- // off of the tag
- if (search->tagLength >= 4) {
- const wchar_t *suffix = &search->tag[search->tagLength - 4];
- if (0 == _comparePath(suffix, 4, L".exe", -1)) {
- search->tagLength -= 4;
- }
- }
- // If we had 'python3_d' then we want to strip the '_d' (any
- // '.exe' is already gone)
- if (search->tagLength >= 2) {
- const wchar_t *suffix = &search->tag[search->tagLength - 2];
- if (0 == _comparePath(suffix, 2, L"_d", -1)) {
- search->tagLength -= 2;
- }
- }
- search->oldStyleTag = true;
- search->lowPriorityTag = true;
- search->executableArgs = &command[commandLength];
- search->executableArgsLength = shebangLength - commandLength;
- if (search->tag && search->tagLength) {
- debug(L"# Treating shebang command '%.*s' as 'py -%.*s'\n",
- commandLength, command, search->tagLength, search->tag);
- } else {
- debug(L"# Treating shebang command '%.*s' as 'py'\n",
- commandLength, command);
- }
- return 0;
- }
- }
-
- // Didn't match a template, but we found it on PATH
- if (executablePathWasSetByUsrBinEnv) {
- return 0;
- }
-
- // Unrecognised executables are first tried as command aliases
- commandLength = 0;
- while (commandLength < shebangLength && !isspace(shebang[commandLength])) {
- commandLength += 1;
- }
- if (_findCommand(search, shebang, commandLength)) {
- search->executableArgs = &shebang[commandLength];
- search->executableArgsLength = shebangLength - commandLength;
- debug(L"# Treating shebang command '%.*s' as %s\n",
- commandLength, shebang, search->executablePath);
- return 0;
- }
-
- // Unrecognised commands are joined to the script's directory and treated
- // as the executable path
- return _useShebangAsExecutable(search, shebang, shebangLength);
-}
-
-
-int
-checkDefaults(SearchInfo *search)
-{
- if (!search->allowDefaults) {
- return 0;
- }
-
- // Only resolve old-style (or absent) tags to defaults
- if (search->tag && search->tagLength && !search->oldStyleTag) {
- return 0;
- }
-
- // If tag is only a major version number, expand it from the environment
- // or an ini file
- const wchar_t *iniSettingName = NULL;
- const wchar_t *envSettingName = NULL;
- if (!search->tag || !search->tagLength) {
- iniSettingName = L"python";
- envSettingName = L"py_python";
- } else if (0 == wcsncmp(search->tag, L"3", search->tagLength)) {
- iniSettingName = L"python3";
- envSettingName = L"py_python3";
- } else if (0 == wcsncmp(search->tag, L"2", search->tagLength)) {
- iniSettingName = L"python2";
- envSettingName = L"py_python2";
- } else {
- debug(L"# Cannot select defaults for tag '%.*s'\n", search->tagLength, search->tag);
- return 0;
- }
-
- // First, try to read an environment variable
- wchar_t buffer[MAXLEN];
- int n = GetEnvironmentVariableW(envSettingName, buffer, MAXLEN);
-
- // If none found, check in our two .ini files instead
- if (!n) {
- n = _readIni(L"defaults", iniSettingName, buffer, MAXLEN);
- }
-
- if (n) {
- wchar_t *tag = allocSearchInfoBuffer(search, n + 1);
- if (!tag) {
- return RC_NO_MEMORY;
- }
- wcscpy_s(tag, n + 1, buffer);
- wchar_t *slash = wcschr(tag, L'/');
- if (!slash) {
- search->tag = tag;
- search->tagLength = n;
- search->oldStyleTag = true;
- } else {
- search->company = tag;
- search->companyLength = (int)(slash - tag);
- search->tag = slash + 1;
- search->tagLength = n - (search->companyLength + 1);
- search->oldStyleTag = false;
- }
- // gh-92817: allow a high priority env to be selected even if it
- // doesn't match the tag
- search->lowPriorityTag = true;
- }
-
- return 0;
-}
-
-/******************************************************************************\
- *** ENVIRONMENT SEARCH ***
-\******************************************************************************/
-
-typedef struct EnvironmentInfo {
- /* We use a binary tree and sort on insert */
- struct EnvironmentInfo *prev;
- struct EnvironmentInfo *next;
- /* parent is only used when constructing */
- struct EnvironmentInfo *parent;
- const wchar_t *company;
- const wchar_t *tag;
- int internalSortKey;
- const wchar_t *installDir;
- const wchar_t *executablePath;
- const wchar_t *executableArgs;
- const wchar_t *architecture;
- const wchar_t *displayName;
- bool highPriority;
-} EnvironmentInfo;
-
-
-int
-copyWstr(const wchar_t **dest, const wchar_t *src)
-{
- if (!dest) {
- return RC_NO_MEMORY;
- }
- if (!src) {
- *dest = NULL;
- return 0;
- }
- size_t n = wcsnlen_s(src, MAXLEN - 1) + 1;
- wchar_t *buffer = (wchar_t*)malloc(n * sizeof(wchar_t));
- if (!buffer) {
- return RC_NO_MEMORY;
- }
- wcsncpy_s(buffer, n, src, n - 1);
- *dest = (const wchar_t*)buffer;
- return 0;
-}
-
-
-EnvironmentInfo *
-newEnvironmentInfo(const wchar_t *company, const wchar_t *tag)
-{
- EnvironmentInfo *env = (EnvironmentInfo *)malloc(sizeof(EnvironmentInfo));
- if (!env) {
- return NULL;
- }
- memset(env, 0, sizeof(EnvironmentInfo));
- int exitCode = copyWstr(&env->company, company);
- if (exitCode) {
- free((void *)env);
- return NULL;
- }
- exitCode = copyWstr(&env->tag, tag);
- if (exitCode) {
- free((void *)env->company);
- free((void *)env);
- return NULL;
- }
- return env;
-}
-
-
-void
-freeEnvironmentInfo(EnvironmentInfo *env)
-{
- if (env) {
- free((void *)env->company);
- free((void *)env->tag);
- free((void *)env->installDir);
- free((void *)env->executablePath);
- free((void *)env->executableArgs);
- free((void *)env->displayName);
- freeEnvironmentInfo(env->prev);
- env->prev = NULL;
- freeEnvironmentInfo(env->next);
- env->next = NULL;
- free((void *)env);
- }
-}
-
-
-/* Specific string comparisons for sorting the tree */
-
-int
-_compareCompany(const wchar_t *x, const wchar_t *y)
-{
- if (!x && !y) {
- return 0;
- } else if (!x) {
- return -1;
- } else if (!y) {
- return 1;
- }
-
- bool coreX = 0 == _compare(x, -1, L"PythonCore", -1);
- bool coreY = 0 == _compare(y, -1, L"PythonCore", -1);
- if (coreX) {
- return coreY ? 0 : -1;
- } else if (coreY) {
- return 1;
- }
- return _compare(x, -1, y, -1);
-}
-
-
-int
-_compareTag(const wchar_t *x, const wchar_t *y)
-{
- if (!x && !y) {
- return 0;
- } else if (!x) {
- return -1;
- } else if (!y) {
- return 1;
- }
-
- // Compare up to the first dash. If not equal, that's our sort order
- const wchar_t *xDash = wcschr(x, L'-');
- const wchar_t *yDash = wcschr(y, L'-');
- int xToDash = xDash ? (int)(xDash - x) : -1;
- int yToDash = yDash ? (int)(yDash - y) : -1;
- int r = _compare(x, xToDash, y, yToDash);
- if (r) {
- return r;
- }
- // If we're equal up to the first dash, we want to sort one with
- // no dash *after* one with a dash. Otherwise, a reversed compare.
- // This works out because environments are sorted in descending tag
- // order, so that higher versions (probably) come first.
- // For PythonCore, our "X.Y" structure ensures that higher versions
- // come first. Everyone else will just have to deal with it.
- if (xDash && yDash) {
- return _compare(yDash, -1, xDash, -1);
- } else if (xDash) {
- return -1;
- } else if (yDash) {
- return 1;
- }
- return 0;
-}
-
-
-int
-addEnvironmentInfo(EnvironmentInfo **root, EnvironmentInfo* parent, EnvironmentInfo *node)
-{
- EnvironmentInfo *r = *root;
- if (!r) {
- *root = node;
- node->parent = parent;
- return 0;
- }
- // Sort by company name
- switch (_compareCompany(node->company, r->company)) {
- case -1:
- return addEnvironmentInfo(&r->prev, r, node);
- case 1:
- return addEnvironmentInfo(&r->next, r, node);
- case 0:
- break;
- }
- // Then by tag (descending)
- switch (_compareTag(node->tag, r->tag)) {
- case -1:
- return addEnvironmentInfo(&r->next, r, node);
- case 1:
- return addEnvironmentInfo(&r->prev, r, node);
- case 0:
- break;
- }
- // Then keep the one with the lowest internal sort key
- if (node->internalSortKey < r->internalSortKey) {
- // Replace the current node
- node->parent = r->parent;
- if (node->parent) {
- if (node->parent->prev == r) {
- node->parent->prev = node;
- } else if (node->parent->next == r) {
- node->parent->next = node;
- } else {
- debug(L"# Inconsistent parent value in tree\n");
- freeEnvironmentInfo(node);
- return RC_INTERNAL_ERROR;
- }
- } else {
- // If node has no parent, then it is the root.
- *root = node;
- }
-
- node->next = r->next;
- node->prev = r->prev;
-
- debug(L"# replaced %s/%s/%i in tree\n", node->company, node->tag, node->internalSortKey);
- freeEnvironmentInfo(r);
- } else {
- debug(L"# not adding %s/%s/%i to tree\n", node->company, node->tag, node->internalSortKey);
- return RC_DUPLICATE_ITEM;
- }
- return 0;
-}
-
-
-/******************************************************************************\
- *** REGISTRY SEARCH ***
-\******************************************************************************/
-
-
-int
-_registryReadString(const wchar_t **dest, HKEY root, const wchar_t *subkey, const wchar_t *value)
-{
- // Note that this is bytes (hence 'cb'), not characters ('cch')
- DWORD cbData = 0;
- DWORD flags = RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ;
-
- if (ERROR_SUCCESS != RegGetValueW(root, subkey, value, flags, NULL, NULL, &cbData)) {
- return 0;
- }
-
- wchar_t *buffer = (wchar_t*)malloc(cbData);
- if (!buffer) {
- return RC_NO_MEMORY;
- }
-
- if (ERROR_SUCCESS == RegGetValueW(root, subkey, value, flags, NULL, buffer, &cbData)) {
- *dest = buffer;
- } else {
- free((void *)buffer);
- }
- return 0;
-}
-
-
-int
-_combineWithInstallDir(const wchar_t **dest, const wchar_t *installDir, const wchar_t *fragment, int fragmentLength)
-{
- wchar_t buffer[MAXLEN];
- wchar_t fragmentBuffer[MAXLEN];
- if (wcsncpy_s(fragmentBuffer, MAXLEN, fragment, fragmentLength)) {
- return RC_NO_MEMORY;
- }
-
- if (FAILED(PathCchCombineEx(buffer, MAXLEN, installDir, fragmentBuffer, PATHCCH_ALLOW_LONG_PATHS))) {
- return RC_NO_MEMORY;
- }
-
- return copyWstr(dest, buffer);
-}
-
-
-bool
-_isLegacyVersion(EnvironmentInfo *env)
-{
- // Check if backwards-compatibility is required.
- // Specifically PythonCore versions 2.X and 3.0 - 3.5 do not implement PEP 514.
- if (0 != _compare(env->company, -1, L"PythonCore", -1)) {
- return false;
- }
-
- int versionMajor, versionMinor;
- int n = swscanf_s(env->tag, L"%d.%d", &versionMajor, &versionMinor);
- if (n != 2) {
- debug(L"# %s/%s has an invalid version tag\n", env->company, env->tag);
- return false;
- }
-
- return versionMajor == 2
- || (versionMajor == 3 && versionMinor >= 0 && versionMinor <= 5);
-}
-
-int
-_registryReadLegacyEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch)
-{
- // Backwards-compatibility for PythonCore versions which do not implement PEP 514.
- int exitCode = _combineWithInstallDir(
- &env->executablePath,
- env->installDir,
- search->executable,
- search->executableLength
- );
- if (exitCode) {
- return exitCode;
- }
-
- if (search->windowed) {
- exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments");
- }
- else {
- exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments");
- }
- if (exitCode) {
- return exitCode;
- }
-
- if (fallbackArch) {
- copyWstr(&env->architecture, fallbackArch);
- } else {
- DWORD binaryType;
- BOOL success = GetBinaryTypeW(env->executablePath, &binaryType);
- if (!success) {
- return RC_NO_PYTHON;
- }
-
- switch (binaryType) {
- case SCS_32BIT_BINARY:
- copyWstr(&env->architecture, L"32bit");
- break;
- case SCS_64BIT_BINARY:
- copyWstr(&env->architecture, L"64bit");
- break;
- default:
- return RC_NO_PYTHON;
- }
- }
-
- if (0 == _compare(env->architecture, -1, L"32bit", -1)) {
- size_t tagLength = wcslen(env->tag);
- if (tagLength <= 3 || 0 != _compare(&env->tag[tagLength - 3], 3, L"-32", 3)) {
- const wchar_t *rawTag = env->tag;
- wchar_t *realTag = (wchar_t*) malloc(sizeof(wchar_t) * (tagLength + 4));
- if (!realTag) {
- return RC_NO_MEMORY;
- }
-
- int count = swprintf_s(realTag, tagLength + 4, L"%s-32", env->tag);
- if (count == -1) {
- debug(L"# Failed to generate 32bit tag\n");
- free(realTag);
- return RC_INTERNAL_ERROR;
- }
-
- env->tag = realTag;
- free((void*)rawTag);
- }
- }
-
- wchar_t buffer[MAXLEN];
- if (swprintf_s(buffer, MAXLEN, L"Python %s", env->tag)) {
- copyWstr(&env->displayName, buffer);
- }
-
- return 0;
-}
-
-
-int
-_registryReadEnvironment(const SearchInfo *search, HKEY root, EnvironmentInfo *env, const wchar_t *fallbackArch)
-{
- int exitCode = _registryReadString(&env->installDir, root, L"InstallPath", NULL);
- if (exitCode) {
- return exitCode;
- }
- if (!env->installDir) {
- return RC_NO_PYTHON;
- }
-
- if (_isLegacyVersion(env)) {
- return _registryReadLegacyEnvironment(search, root, env, fallbackArch);
- }
-
- // If pythonw.exe requested, check specific value
- if (search->windowed) {
- exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"WindowedExecutablePath");
- if (!exitCode && env->executablePath) {
- exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"WindowedExecutableArguments");
- }
- }
- if (exitCode) {
- return exitCode;
- }
-
- // Missing windowed path or non-windowed request means we use ExecutablePath
- if (!env->executablePath) {
- exitCode = _registryReadString(&env->executablePath, root, L"InstallPath", L"ExecutablePath");
- if (!exitCode && env->executablePath) {
- exitCode = _registryReadString(&env->executableArgs, root, L"InstallPath", L"ExecutableArguments");
- }
- }
- if (exitCode) {
- return exitCode;
- }
-
- if (!env->executablePath) {
- debug(L"# %s/%s has no executable path\n", env->company, env->tag);
- return RC_NO_PYTHON;
- }
-
- exitCode = _registryReadString(&env->architecture, root, NULL, L"SysArchitecture");
- if (exitCode) {
- return exitCode;
- }
-
- exitCode = _registryReadString(&env->displayName, root, NULL, L"DisplayName");
- if (exitCode) {
- return exitCode;
- }
-
- return 0;
-}
-
-int
-_registrySearchTags(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *company, const wchar_t *fallbackArch)
-{
- wchar_t buffer[256];
- int err = 0;
- int exitCode = 0;
- for (int i = 0; exitCode == 0; ++i) {
- DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]);
- err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL);
- if (err) {
- if (err != ERROR_NO_MORE_ITEMS) {
- winerror(0, L"Failed to read installs (tags) from the registry");
- }
- break;
- }
- HKEY subkey;
- if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) {
- EnvironmentInfo *env = newEnvironmentInfo(company, buffer);
- env->internalSortKey = sortKey;
- exitCode = _registryReadEnvironment(search, subkey, env, fallbackArch);
- RegCloseKey(subkey);
- if (exitCode == RC_NO_PYTHON) {
- freeEnvironmentInfo(env);
- exitCode = 0;
- } else if (!exitCode) {
- exitCode = addEnvironmentInfo(result, NULL, env);
- if (exitCode) {
- freeEnvironmentInfo(env);
- if (exitCode == RC_DUPLICATE_ITEM) {
- exitCode = 0;
- }
- }
- }
- }
- }
- return exitCode;
-}
-
-
-int
-registrySearch(const SearchInfo *search, EnvironmentInfo **result, HKEY root, int sortKey, const wchar_t *fallbackArch)
-{
- wchar_t buffer[256];
- int err = 0;
- int exitCode = 0;
- for (int i = 0; exitCode == 0; ++i) {
- DWORD cchBuffer = sizeof(buffer) / sizeof(buffer[0]);
- err = RegEnumKeyExW(root, i, buffer, &cchBuffer, NULL, NULL, NULL, NULL);
- if (err) {
- if (err != ERROR_NO_MORE_ITEMS) {
- winerror(0, L"Failed to read distributors (company) from the registry");
- }
- break;
- }
- if (search->limitToCompany && 0 != _compare(search->limitToCompany, -1, buffer, cchBuffer)) {
- debug(L"# Skipping %s due to PYLAUNCHER_LIMIT_TO_COMPANY\n", buffer);
- continue;
- }
- HKEY subkey;
- if (ERROR_SUCCESS == RegOpenKeyExW(root, buffer, 0, KEY_READ, &subkey)) {
- exitCode = _registrySearchTags(search, result, subkey, sortKey, buffer, fallbackArch);
- RegCloseKey(subkey);
- }
- }
- return exitCode;
-}
-
-
-/******************************************************************************\
- *** APP PACKAGE SEARCH ***
-\******************************************************************************/
-
-int
-appxSearch(const SearchInfo *search, EnvironmentInfo **result, const wchar_t *packageFamilyName, const wchar_t *tag, int sortKey)
-{
- wchar_t realTag[32];
- wchar_t buffer[MAXLEN];
- const wchar_t *exeName = search->executable;
- if (!exeName || search->allowExecutableOverride) {
- exeName = search->windowed ? L"pythonw.exe" : L"python.exe";
- }
-
- // Failure to get LocalAppData may just mean we're running as a user who
- // doesn't have a profile directory.
- // In this case, return "not found", but don't fail.
- // Chances are they can't launch Store installs anyway.
- if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buffer))) {
- return RC_NO_PYTHON;
- }
-
- if (!join(buffer, MAXLEN, L"Microsoft\\WindowsApps") ||
- !join(buffer, MAXLEN, packageFamilyName) ||
- !join(buffer, MAXLEN, exeName)) {
- debug(L"# Failed to construct App Execution Alias path\n");
- return RC_INTERNAL_ERROR;
- }
-
- if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer)) {
- return RC_NO_PYTHON;
- }
-
- // Assume packages are native architecture, which means we need to append
- // the '-arm64' on ARM64 host.
- wcscpy_s(realTag, 32, tag);
- if (isARM64Host()) {
- wcscat_s(realTag, 32, L"-arm64");
- }
-
- EnvironmentInfo *env = newEnvironmentInfo(L"PythonCore", realTag);
- if (!env) {
- return RC_NO_MEMORY;
- }
- env->internalSortKey = sortKey;
- if (isAMD64Host()) {
- copyWstr(&env->architecture, L"64bit");
- } else if (isARM64Host()) {
- copyWstr(&env->architecture, L"ARM64");
- }
-
- copyWstr(&env->executablePath, buffer);
-
- if (swprintf_s(buffer, MAXLEN, L"Python %s (Store)", tag)) {
- copyWstr(&env->displayName, buffer);
- }
-
- int exitCode = addEnvironmentInfo(result, NULL, env);
- if (exitCode) {
- freeEnvironmentInfo(env);
- if (exitCode == RC_DUPLICATE_ITEM) {
- exitCode = 0;
- }
- }
-
-
- return exitCode;
-}
-
-
-/******************************************************************************\
- *** OVERRIDDEN EXECUTABLE PATH ***
-\******************************************************************************/
-
-
-int
-explicitOverrideSearch(const SearchInfo *search, EnvironmentInfo **result)
-{
- if (!search->executablePath) {
- return 0;
- }
-
- EnvironmentInfo *env = newEnvironmentInfo(NULL, NULL);
- if (!env) {
- return RC_NO_MEMORY;
- }
- env->internalSortKey = 10;
- int exitCode = copyWstr(&env->executablePath, search->executablePath);
- if (exitCode) {
- goto abort;
- }
- exitCode = copyWstr(&env->displayName, L"Explicit override");
- if (exitCode) {
- goto abort;
- }
- exitCode = addEnvironmentInfo(result, NULL, env);
- if (exitCode) {
- goto abort;
- }
- return 0;
-
-abort:
- freeEnvironmentInfo(env);
- if (exitCode == RC_DUPLICATE_ITEM) {
- exitCode = 0;
- }
- return exitCode;
-}
-
-
-/******************************************************************************\
- *** ACTIVE VIRTUAL ENVIRONMENT SEARCH ***
-\******************************************************************************/
-
-int
-virtualenvSearch(const SearchInfo *search, EnvironmentInfo **result)
-{
- int exitCode = 0;
- EnvironmentInfo *env = NULL;
- wchar_t buffer[MAXLEN];
- int n = GetEnvironmentVariableW(L"VIRTUAL_ENV", buffer, MAXLEN);
- if (!n || !join(buffer, MAXLEN, L"Scripts") || !join(buffer, MAXLEN, search->executable)) {
- return 0;
- }
-
- DWORD attr = GetFileAttributesW(buffer);
- if (INVALID_FILE_ATTRIBUTES == attr && search->lowPriorityTag) {
- if (!split_parent(buffer, MAXLEN) || !join(buffer, MAXLEN, L"python.exe")) {
- return 0;
- }
- attr = GetFileAttributesW(buffer);
- }
-
- if (INVALID_FILE_ATTRIBUTES == attr) {
- debug(L"Python executable %s missing from virtual env\n", buffer);
- return 0;
- }
-
- env = newEnvironmentInfo(NULL, NULL);
- if (!env) {
- return RC_NO_MEMORY;
- }
- env->highPriority = true;
- env->internalSortKey = 20;
- exitCode = copyWstr(&env->displayName, L"Active venv");
- if (exitCode) {
- goto abort;
- }
- exitCode = copyWstr(&env->executablePath, buffer);
- if (exitCode) {
- goto abort;
- }
- exitCode = addEnvironmentInfo(result, NULL, env);
- if (exitCode) {
- goto abort;
- }
- return 0;
-
-abort:
- freeEnvironmentInfo(env);
- if (exitCode == RC_DUPLICATE_ITEM) {
- return 0;
- }
- return exitCode;
-}
-
-/******************************************************************************\
- *** COLLECT ENVIRONMENTS ***
-\******************************************************************************/
-
-
-struct RegistrySearchInfo {
- // Registry subkey to search
- const wchar_t *subkey;
- // Registry hive to search
- HKEY hive;
- // Flags to use when opening the subkey
- DWORD flags;
- // Internal sort key to select between "identical" environments discovered
- // through different methods
- int sortKey;
- // Fallback value to assume for PythonCore entries missing a SysArchitecture value
- const wchar_t *fallbackArch;
-};
-
-
-struct RegistrySearchInfo REGISTRY_SEARCH[] = {
- {
- L"Software\\Python",
- HKEY_CURRENT_USER,
- KEY_READ,
- 1,
- NULL
- },
- {
- L"Software\\Python",
- HKEY_LOCAL_MACHINE,
- KEY_READ | KEY_WOW64_64KEY,
- 3,
- L"64bit"
- },
- {
- L"Software\\Python",
- HKEY_LOCAL_MACHINE,
- KEY_READ | KEY_WOW64_32KEY,
- 4,
- L"32bit"
- },
- { NULL, 0, 0, 0, NULL }
-};
-
-
-struct AppxSearchInfo {
- // The package family name. Can be found for an installed package using the
- // Powershell "Get-AppxPackage" cmdlet
- const wchar_t *familyName;
- // The tag to treat the installation as
- const wchar_t *tag;
- // Internal sort key to select between "identical" environments discovered
- // through different methods
- int sortKey;
-};
-
-
-struct AppxSearchInfo APPX_SEARCH[] = {
- // Releases made through the Store
- { L"PythonSoftwareFoundation.Python.3.14_qbz5n2kfra8p0", L"3.14", 10 },
- { L"PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0", L"3.13", 10 },
- { L"PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0", L"3.12", 10 },
- { L"PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0", L"3.11", 10 },
- { L"PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0", L"3.10", 10 },
- { L"PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0", L"3.9", 10 },
- { L"PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0", L"3.8", 10 },
-
- // Side-loadable releases. Note that the publisher ID changes whenever we
- // change our code signing certificate subject, so the newer IDs have higher
- // priorities (lower sortKey)
- { L"PythonSoftwareFoundation.Python.3.14_3847v3x7pw1km", L"3.14", 11 },
- { L"PythonSoftwareFoundation.Python.3.13_3847v3x7pw1km", L"3.13", 11 },
- { L"PythonSoftwareFoundation.Python.3.12_3847v3x7pw1km", L"3.12", 11 },
- { L"PythonSoftwareFoundation.Python.3.11_3847v3x7pw1km", L"3.11", 11 },
- { L"PythonSoftwareFoundation.Python.3.11_hd69rhyc2wevp", L"3.11", 12 },
- { L"PythonSoftwareFoundation.Python.3.10_3847v3x7pw1km", L"3.10", 11 },
- { L"PythonSoftwareFoundation.Python.3.10_hd69rhyc2wevp", L"3.10", 12 },
- { L"PythonSoftwareFoundation.Python.3.9_3847v3x7pw1km", L"3.9", 11 },
- { L"PythonSoftwareFoundation.Python.3.9_hd69rhyc2wevp", L"3.9", 12 },
- { L"PythonSoftwareFoundation.Python.3.8_hd69rhyc2wevp", L"3.8", 12 },
- { NULL, NULL, 0 }
-};
-
-
-int
-collectEnvironments(const SearchInfo *search, EnvironmentInfo **result)
-{
- int exitCode = 0;
- HKEY root;
- EnvironmentInfo *env = NULL;
-
- if (!result) {
- debug(L"# collectEnvironments() was passed a NULL result\n");
- return RC_INTERNAL_ERROR;
- }
- *result = NULL;
-
- exitCode = explicitOverrideSearch(search, result);
- if (exitCode) {
- return exitCode;
- }
-
- exitCode = virtualenvSearch(search, result);
- if (exitCode) {
- return exitCode;
- }
-
- // If we aren't collecting all items to list them, we can exit now.
- if (env && !(search->list || search->listPaths)) {
- return 0;
- }
-
- for (struct RegistrySearchInfo *info = REGISTRY_SEARCH; info->subkey; ++info) {
- if (ERROR_SUCCESS == RegOpenKeyExW(info->hive, info->subkey, 0, info->flags, &root)) {
- exitCode = registrySearch(search, result, root, info->sortKey, info->fallbackArch);
- RegCloseKey(root);
- }
- if (exitCode) {
- return exitCode;
- }
- }
-
- if (search->limitToCompany) {
- debug(L"# Skipping APPX search due to PYLAUNCHER_LIMIT_TO_COMPANY\n");
- return 0;
- }
-
- for (struct AppxSearchInfo *info = APPX_SEARCH; info->familyName; ++info) {
- exitCode = appxSearch(search, result, info->familyName, info->tag, info->sortKey);
- if (exitCode && exitCode != RC_NO_PYTHON) {
- return exitCode;
- }
- }
-
- return 0;
-}
-
-
-/******************************************************************************\
- *** INSTALL ON DEMAND ***
-\******************************************************************************/
-
-struct StoreSearchInfo {
- // The tag a user is looking for
- const wchar_t *tag;
- // The Store ID for a package if it can be installed from the Microsoft
- // Store. These are obtained from the dashboard at
- // https://partner.microsoft.com/dashboard
- const wchar_t *storeId;
-};
-
-
-struct StoreSearchInfo STORE_SEARCH[] = {
- { L"3", /* 3.13 */ L"9PNRBTZXMB4Z" },
- { L"3.14", L"9NTRHQCBBPR8" },
- { L"3.13", L"9PNRBTZXMB4Z" },
- { L"3.12", L"9NCVDN91XZQP" },
- { L"3.11", L"9NRWMJP3717K" },
- { L"3.10", L"9PJPW5LDXLZ5" },
- { L"3.9", L"9P7QFQMJRFP7" },
- { L"3.8", L"9MSSZTT1N39L" },
- { NULL, NULL }
-};
-
-
-int
-_installEnvironment(const wchar_t *command, const wchar_t *arguments)
-{
- SHELLEXECUTEINFOW siw = {
- sizeof(SHELLEXECUTEINFOW),
- SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
- NULL, NULL,
- command, arguments, NULL,
- SW_SHOWNORMAL
- };
-
- debug(L"# Installing with %s %s\n", command, arguments);
- if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) {
- debug(L"# Exiting due to PYLAUNCHER_DRYRUN\n");
- fflush(stdout);
- int mode = _setmode(_fileno(stdout), _O_U8TEXT);
- if (arguments) {
- fwprintf_s(stdout, L"\"%s\" %s\n", command, arguments);
- } else {
- fwprintf_s(stdout, L"\"%s\"\n", command);
- }
- fflush(stdout);
- if (mode >= 0) {
- _setmode(_fileno(stdout), mode);
- }
- return RC_INSTALLING;
- }
-
- if (!ShellExecuteExW(&siw)) {
- return RC_NO_PYTHON;
- }
-
- if (!siw.hProcess) {
- return RC_INSTALLING;
- }
-
- WaitForSingleObjectEx(siw.hProcess, INFINITE, FALSE);
- DWORD exitCode = 0;
- if (GetExitCodeProcess(siw.hProcess, &exitCode) && exitCode == 0) {
- return 0;
- }
- return RC_INSTALLING;
-}
-
-
-const wchar_t *WINGET_COMMAND = L"Microsoft\\WindowsApps\\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\winget.exe";
-const wchar_t *WINGET_ARGUMENTS = L"install -q %s --exact --accept-package-agreements --source msstore";
-
-const wchar_t *MSSTORE_COMMAND = L"ms-windows-store://pdp/?productid=%s";
-
-int
-installEnvironment(const SearchInfo *search)
-{
- // No tag? No installing
- if (!search->tag || !search->tagLength) {
- debug(L"# Cannot install Python with no tag specified\n");
- return RC_NO_PYTHON;
- }
-
- // PEP 514 tag but not PythonCore? No installing
- if (!search->oldStyleTag &&
- search->company && search->companyLength &&
- 0 != _compare(search->company, search->companyLength, L"PythonCore", -1)) {
- debug(L"# Cannot install for company %.*s\n", search->companyLength, search->company);
- return RC_NO_PYTHON;
- }
-
- const wchar_t *storeId = NULL;
- for (struct StoreSearchInfo *info = STORE_SEARCH; info->tag; ++info) {
- if (0 == _compare(search->tag, search->tagLength, info->tag, -1)) {
- storeId = info->storeId;
- break;
- }
- }
-
- if (!storeId) {
- return RC_NO_PYTHON;
- }
-
- int exitCode;
- wchar_t command[MAXLEN];
- wchar_t arguments[MAXLEN];
- if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, command)) &&
- join(command, MAXLEN, WINGET_COMMAND) &&
- swprintf_s(arguments, MAXLEN, WINGET_ARGUMENTS, storeId)) {
- if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(command)) {
- formatWinerror(GetLastError(), arguments, MAXLEN);
- debug(L"# Skipping %s: %s\n", command, arguments);
- } else {
- fputws(L"Launching winget to install Python. The following output is from the install process\n\
-***********************************************************************\n", stdout);
- exitCode = _installEnvironment(command, arguments);
- if (exitCode == RC_INSTALLING) {
- fputws(L"***********************************************************************\n\
-Please check the install status and run your command again.", stderr);
- return exitCode;
- } else if (exitCode) {
- return exitCode;
- }
- fputws(L"***********************************************************************\n\
-Install appears to have succeeded. Searching for new matching installs.\n", stdout);
- return 0;
- }
- }
-
- if (swprintf_s(command, MAXLEN, MSSTORE_COMMAND, storeId)) {
- fputws(L"Opening the Microsoft Store to install Python. After installation, "
- L"please run your command again.\n", stderr);
- exitCode = _installEnvironment(command, NULL);
- if (exitCode) {
- return exitCode;
- }
- return 0;
- }
-
- return RC_NO_PYTHON;
-}
-
-/******************************************************************************\
- *** ENVIRONMENT SELECT ***
-\******************************************************************************/
-
-bool
-_companyMatches(const SearchInfo *search, const EnvironmentInfo *env)
-{
- if (!search->company || !search->companyLength) {
- return true;
- }
- return 0 == _compare(env->company, -1, search->company, search->companyLength);
-}
-
-
-bool
-_tagMatches(const SearchInfo *search, const EnvironmentInfo *env, int searchTagLength)
-{
- if (searchTagLength < 0) {
- searchTagLength = search->tagLength;
- }
- if (!search->tag || !searchTagLength) {
- return true;
- }
- return _startsWithSeparated(env->tag, -1, search->tag, searchTagLength, L".-");
-}
-
-
-bool
-_is32Bit(const EnvironmentInfo *env)
-{
- if (env->architecture) {
- return 0 == _compare(env->architecture, -1, L"32bit", -1);
- }
- return false;
-}
-
-
-int
-_selectEnvironment(const SearchInfo *search, EnvironmentInfo *env, EnvironmentInfo **best)
-{
- int exitCode = 0;
- while (env) {
- exitCode = _selectEnvironment(search, env->prev, best);
-
- if (exitCode && exitCode != RC_NO_PYTHON) {
- return exitCode;
- } else if (!exitCode && *best) {
- return 0;
- }
-
- if (env->highPriority && search->lowPriorityTag) {
- // This environment is marked high priority, and the search allows
- // it to be selected even though a tag is specified, so select it
- // gh-92817: this allows an active venv to be selected even when a
- // default tag has been found in py.ini or the environment
- *best = env;
- return 0;
- }
-
- if (!search->oldStyleTag) {
- if (_companyMatches(search, env) && _tagMatches(search, env, -1)) {
- // Because of how our sort tree is set up, we will walk up the
- // "prev" side and implicitly select the "best" best. By
- // returning straight after a match, we skip the entire "next"
- // branch and won't ever select a "worse" best.
- *best = env;
- return 0;
- }
- } else if (0 == _compare(env->company, -1, L"PythonCore", -1)) {
- // Old-style tags can only match PythonCore entries
-
- // If the tag ends with -64, we want to exclude 32-bit runtimes
- // (If the tag ends with -32, it will be filtered later)
- int tagLength = search->tagLength;
- bool exclude32Bit = false, only32Bit = false;
- if (tagLength > 3) {
- if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-64", 3)) {
- tagLength -= 3;
- exclude32Bit = true;
- } else if (0 == _compareArgument(&search->tag[tagLength - 3], 3, L"-32", 3)) {
- tagLength -= 3;
- only32Bit = true;
- }
- }
-
- if (_tagMatches(search, env, tagLength)) {
- if (exclude32Bit && _is32Bit(env)) {
- debug(L"# Excluding %s/%s because it looks like 32bit\n", env->company, env->tag);
- } else if (only32Bit && !_is32Bit(env)) {
- debug(L"# Excluding %s/%s because it doesn't look 32bit\n", env->company, env->tag);
- } else {
- *best = env;
- return 0;
- }
- }
- }
-
- env = env->next;
- }
- return RC_NO_PYTHON;
-}
-
-int
-selectEnvironment(const SearchInfo *search, EnvironmentInfo *root, EnvironmentInfo **best)
-{
- if (!best) {
- debug(L"# selectEnvironment() was passed a NULL best\n");
- return RC_INTERNAL_ERROR;
- }
- if (!root) {
- *best = NULL;
- return RC_NO_PYTHON_AT_ALL;
- }
-
- EnvironmentInfo *result = NULL;
- int exitCode = _selectEnvironment(search, root, &result);
- if (!exitCode) {
- *best = result;
- }
-
- return exitCode;
-}
-
-
-/******************************************************************************\
- *** LIST ENVIRONMENTS ***
-\******************************************************************************/
-
-#define TAGWIDTH 16
-
-int
-_printEnvironment(const EnvironmentInfo *env, FILE *out, bool showPath, const wchar_t *argument)
-{
- if (showPath) {
- if (env->executablePath && env->executablePath[0]) {
- if (env->executableArgs && env->executableArgs[0]) {
- fwprintf(out, L" %-*s %s %s\n", TAGWIDTH, argument, env->executablePath, env->executableArgs);
- } else {
- fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->executablePath);
- }
- } else if (env->installDir && env->installDir[0]) {
- fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->installDir);
- } else {
- fwprintf(out, L" %s\n", argument);
- }
- } else if (env->displayName) {
- fwprintf(out, L" %-*s %s\n", TAGWIDTH, argument, env->displayName);
- } else {
- fwprintf(out, L" %s\n", argument);
- }
- return 0;
-}
-
-
-int
-_listAllEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv)
-{
- wchar_t buffer[256];
- const int bufferSize = 256;
- while (env) {
- int exitCode = _listAllEnvironments(env->prev, out, showPath, defaultEnv);
- if (exitCode) {
- return exitCode;
- }
-
- if (!env->company || !env->tag) {
- buffer[0] = L'\0';
- } else if (0 == _compare(env->company, -1, L"PythonCore", -1)) {
- swprintf_s(buffer, bufferSize, L"-V:%s", env->tag);
- } else {
- swprintf_s(buffer, bufferSize, L"-V:%s/%s", env->company, env->tag);
- }
-
- if (env == defaultEnv) {
- wcscat_s(buffer, bufferSize, L" *");
- }
-
- if (buffer[0]) {
- exitCode = _printEnvironment(env, out, showPath, buffer);
- if (exitCode) {
- return exitCode;
- }
- }
-
- env = env->next;
- }
- return 0;
-}
-
-
-int
-listEnvironments(EnvironmentInfo *env, FILE * out, bool showPath, EnvironmentInfo *defaultEnv)
-{
- if (!env) {
- fwprintf_s(stdout, L"No installed Pythons found!\n");
- return 0;
- }
-
- /* TODO: Do we want to display these?
- In favour, helps users see that '-3' is a good option
- Against, repeats the next line of output
- SearchInfo majorSearch;
- EnvironmentInfo *major;
- int exitCode;
-
- if (showPath) {
- memset(&majorSearch, 0, sizeof(majorSearch));
- majorSearch.company = L"PythonCore";
- majorSearch.companyLength = -1;
- majorSearch.tag = L"3";
- majorSearch.tagLength = -1;
- majorSearch.oldStyleTag = true;
- major = NULL;
- exitCode = selectEnvironment(&majorSearch, env, &major);
- if (!exitCode && major) {
- exitCode = _printEnvironment(major, out, showPath, L"-3 *");
- isDefault = false;
- if (exitCode) {
- return exitCode;
- }
- }
- majorSearch.tag = L"2";
- major = NULL;
- exitCode = selectEnvironment(&majorSearch, env, &major);
- if (!exitCode && major) {
- exitCode = _printEnvironment(major, out, showPath, L"-2");
- if (exitCode) {
- return exitCode;
- }
- }
- }
- */
-
- int mode = _setmode(_fileno(out), _O_U8TEXT);
- int exitCode = _listAllEnvironments(env, out, showPath, defaultEnv);
- fflush(out);
- if (mode >= 0) {
- _setmode(_fileno(out), mode);
- }
- return exitCode;
-}
-
-
-/******************************************************************************\
- *** INTERPRETER LAUNCH ***
-\******************************************************************************/
-
-
-int
-calculateCommandLine(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *buffer, int bufferLength)
-{
- int exitCode = 0;
- const wchar_t *executablePath = NULL;
-
- // Construct command line from a search override, or else the selected
- // environment's executablePath
- if (search->executablePath) {
- executablePath = search->executablePath;
- } else if (launch && launch->executablePath) {
- executablePath = launch->executablePath;
- }
-
- // If we have an executable path, put it at the start of the command, but
- // only if the search allowed an override.
- // Otherwise, use the environment's installDir and the search's default
- // executable name.
- if (executablePath && search->allowExecutableOverride) {
- if (wcschr(executablePath, L' ') && executablePath[0] != L'"') {
- buffer[0] = L'"';
- exitCode = wcscpy_s(&buffer[1], bufferLength - 1, executablePath);
- if (!exitCode) {
- exitCode = wcscat_s(buffer, bufferLength, L"\"");
- }
- } else {
- exitCode = wcscpy_s(buffer, bufferLength, executablePath);
- }
- } else if (launch) {
- if (!launch->installDir) {
- fwprintf_s(stderr, L"Cannot launch %s %s because no install directory was specified",
- launch->company, launch->tag);
- exitCode = RC_NO_PYTHON;
- } else if (!search->executable || !search->executableLength) {
- fwprintf_s(stderr, L"Cannot launch %s %s because no executable name is available",
- launch->company, launch->tag);
- exitCode = RC_NO_PYTHON;
- } else {
- wchar_t executable[256];
- wcsncpy_s(executable, 256, search->executable, search->executableLength);
- if ((wcschr(launch->installDir, L' ') && launch->installDir[0] != L'"') ||
- (wcschr(executable, L' ') && executable[0] != L'"')) {
- buffer[0] = L'"';
- exitCode = wcscpy_s(&buffer[1], bufferLength - 1, launch->installDir);
- if (!exitCode) {
- exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY;
- }
- if (!exitCode) {
- exitCode = wcscat_s(buffer, bufferLength, L"\"");
- }
- } else {
- exitCode = wcscpy_s(buffer, bufferLength, launch->installDir);
- if (!exitCode) {
- exitCode = join(buffer, bufferLength, executable) ? 0 : RC_NO_MEMORY;
- }
- }
- }
- } else {
- exitCode = RC_NO_PYTHON;
- }
-
- if (!exitCode && launch && launch->executableArgs) {
- exitCode = wcscat_s(buffer, bufferLength, L" ");
- if (!exitCode) {
- exitCode = wcscat_s(buffer, bufferLength, launch->executableArgs);
- }
- }
-
- if (!exitCode && search->executableArgs) {
- if (search->executableArgsLength < 0) {
- exitCode = wcscat_s(buffer, bufferLength, search->executableArgs);
- } else if (search->executableArgsLength > 0) {
- int end = (int)wcsnlen_s(buffer, MAXLEN);
- if (end < bufferLength - (search->executableArgsLength + 1)) {
- exitCode = wcsncpy_s(&buffer[end], bufferLength - end,
- search->executableArgs, search->executableArgsLength);
- }
- }
- }
-
- if (!exitCode && search->restOfCmdLine) {
- exitCode = wcscat_s(buffer, bufferLength, search->restOfCmdLine);
- }
-
- return exitCode;
-}
-
-
-
-BOOL
-_safeDuplicateHandle(HANDLE in, HANDLE * pout, const wchar_t *nameForError)
-{
- BOOL ok;
- HANDLE process = GetCurrentProcess();
- DWORD rc;
-
- *pout = NULL;
- ok = DuplicateHandle(process, in, process, pout, 0, TRUE,
- DUPLICATE_SAME_ACCESS);
- if (!ok) {
- rc = GetLastError();
- if (rc == ERROR_INVALID_HANDLE) {
- debug(L"DuplicateHandle returned ERROR_INVALID_HANDLE\n");
- ok = TRUE;
- }
- else {
- winerror(0, L"Failed to duplicate %s handle", nameForError);
- }
- }
- return ok;
-}
-
-BOOL WINAPI
-ctrl_c_handler(DWORD code)
-{
- return TRUE; /* We just ignore all control events. */
-}
-
-
-int
-launchEnvironment(const SearchInfo *search, const EnvironmentInfo *launch, wchar_t *launchCommand)
-{
- HANDLE job;
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
- DWORD rc;
- BOOL ok;
- STARTUPINFOW si;
- PROCESS_INFORMATION pi;
-
- // If this is a dryrun, do not actually launch
- if (isEnvVarSet(L"PYLAUNCHER_DRYRUN")) {
- debug(L"LaunchCommand: %s\n", launchCommand);
- debug(L"# Exiting due to PYLAUNCHER_DRYRUN variable\n");
- fflush(stdout);
- int mode = _setmode(_fileno(stdout), _O_U8TEXT);
- fwprintf(stdout, L"%s\n", launchCommand);
- fflush(stdout);
- if (mode >= 0) {
- _setmode(_fileno(stdout), mode);
- }
- return 0;
- }
-
-#if defined(_WINDOWS)
- /*
- When explorer launches a Windows (GUI) application, it displays
- the "app starting" (the "pointer + hourglass") cursor for a number
- of seconds, or until the app does something UI-ish (eg, creating a
- window, or fetching a message). As this launcher doesn't do this
- directly, that cursor remains even after the child process does these
- things. We avoid that by doing a simple post+get message.
- See http://bugs.python.org/issue17290
- */
- MSG msg;
-
- PostMessage(0, 0, 0, 0);
- GetMessage(&msg, 0, 0, 0);
-#endif
-
- debug(L"# about to run: %s\n", launchCommand);
- job = CreateJobObject(NULL, NULL);
- ok = QueryInformationJobObject(job, JobObjectExtendedLimitInformation,
- &info, sizeof(info), &rc);
- if (!ok || (rc != sizeof(info)) || !job) {
- winerror(0, L"Failed to query job information");
- return RC_CREATE_PROCESS;
- }
- info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
- JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
- ok = SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info,
- sizeof(info));
- if (!ok) {
- winerror(0, L"Failed to update job information");
- return RC_CREATE_PROCESS;
- }
- memset(&si, 0, sizeof(si));
- GetStartupInfoW(&si);
- if (!_safeDuplicateHandle(GetStdHandle(STD_INPUT_HANDLE), &si.hStdInput, L"stdin") ||
- !_safeDuplicateHandle(GetStdHandle(STD_OUTPUT_HANDLE), &si.hStdOutput, L"stdout") ||
- !_safeDuplicateHandle(GetStdHandle(STD_ERROR_HANDLE), &si.hStdError, L"stderr")) {
- return RC_NO_STD_HANDLES;
- }
-
- ok = SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
- if (!ok) {
- winerror(0, L"Failed to update Control-C handler");
- return RC_NO_STD_HANDLES;
- }
-
- si.dwFlags = STARTF_USESTDHANDLES;
- ok = CreateProcessW(NULL, launchCommand, NULL, NULL, TRUE,
- 0, NULL, NULL, &si, &pi);
- if (!ok) {
- winerror(0, L"Unable to create process using '%s'", launchCommand);
- return RC_CREATE_PROCESS;
- }
- AssignProcessToJobObject(job, pi.hProcess);
- CloseHandle(pi.hThread);
- WaitForSingleObjectEx(pi.hProcess, INFINITE, FALSE);
- ok = GetExitCodeProcess(pi.hProcess, &rc);
- if (!ok) {
- winerror(0, L"Failed to get exit code of process");
- return RC_CREATE_PROCESS;
- }
- debug(L"child process exit code: %d\n", rc);
- return rc;
-}
-
-
-/******************************************************************************\
- *** PROCESS CONTROLLER ***
-\******************************************************************************/
-
-
-int
-performSearch(SearchInfo *search, EnvironmentInfo **envs)
-{
- // First parse the command line for options
- int exitCode = parseCommandLine(search);
- if (exitCode) {
- return exitCode;
- }
-
- // Check for a shebang line in our script file
- // (or return quickly if no script file was specified)
- exitCode = checkShebang(search);
- switch (exitCode) {
- case 0:
- case RC_NO_SHEBANG:
- case RC_RECURSIVE_SHEBANG:
- break;
- case RC_NO_SCRIPT:
- if (!_comparePath(search->scriptFile, search->scriptFileLength, L"install", -1) ||
- !_comparePath(search->scriptFile, search->scriptFileLength, L"uninstall", -1) ||
- !_comparePath(search->scriptFile, search->scriptFileLength, L"list", -1) ||
- !_comparePath(search->scriptFile, search->scriptFileLength, L"help", -1)) {
- fprintf(
- stderr,
- "WARNING: The '%.*ls' command is unavailable because this is the legacy py.exe command.\n"
- "If you have already installed the Python install manager, open Installed Apps and "
- "remove 'Python Launcher' to enable the new py.exe command.\n",
- search->scriptFileLength,
- search->scriptFile
- );
- }
- break;
- default:
- return exitCode;
- }
-
- // Resolve old-style tags (possibly from a shebang) against py.ini entries
- // and environment variables.
- exitCode = checkDefaults(search);
- if (exitCode) {
- return exitCode;
- }
-
- // If debugging is enabled, list our search criteria
- dumpSearchInfo(search);
-
- // Find all matching environments
- exitCode = collectEnvironments(search, envs);
- if (exitCode) {
- return exitCode;
- }
-
- return 0;
-}
-
-
-int
-process(int argc, wchar_t ** argv)
-{
- int exitCode = 0;
- int searchExitCode = 0;
- SearchInfo search = {0};
- EnvironmentInfo *envs = NULL;
- EnvironmentInfo *env = NULL;
- wchar_t launchCommand[MAXLEN];
-
- memset(launchCommand, 0, sizeof(launchCommand));
-
- if (isEnvVarSet(L"PYLAUNCHER_DEBUG")) {
- setvbuf(stderr, (char *)NULL, _IONBF, 0);
- log_fp = stderr;
- debug(L"argv0: %s\nversion: %S\n", argv[0], PY_VERSION);
- }
-
- DWORD len = GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", NULL, 0);
- if (len > 1) {
- wchar_t *limitToCompany = allocSearchInfoBuffer(&search, len);
- if (!limitToCompany) {
- exitCode = RC_NO_MEMORY;
- winerror(0, L"Failed to allocate internal buffer");
- goto abort;
- }
- search.limitToCompany = limitToCompany;
- if (0 == GetEnvironmentVariableW(L"PYLAUNCHER_LIMIT_TO_COMPANY", limitToCompany, len)) {
- exitCode = RC_INTERNAL_ERROR;
- winerror(0, L"Failed to read PYLAUNCHER_LIMIT_TO_COMPANY variable");
- goto abort;
- }
- }
-
- search.originalCmdLine = GetCommandLineW();
-
- exitCode = performSearch(&search, &envs);
- if (exitCode) {
- goto abort;
- }
-
- // Display the help text, but only exit on error
- if (search.help) {
- exitCode = showHelpText(argv);
- if (exitCode) {
- goto abort;
- }
- }
-
- // Select best environment
- // This is early so that we can show the default when listing, but all
- // responses to any errors occur later.
- searchExitCode = selectEnvironment(&search, envs, &env);
-
- // List all environments, then exit
- if (search.list || search.listPaths) {
- exitCode = listEnvironments(envs, stdout, search.listPaths, env);
- goto abort;
- }
-
- // When debugging, list all discovered environments anyway
- if (log_fp) {
- exitCode = listEnvironments(envs, log_fp, true, NULL);
- if (exitCode) {
- goto abort;
- }
- }
-
- // We searched earlier, so if we didn't find anything, now we react
- exitCode = searchExitCode;
- // If none found, and if permitted, install it
- if (((exitCode == RC_NO_PYTHON) && isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL")) ||
- isEnvVarSet(L"PYLAUNCHER_ALWAYS_INSTALL")) {
- exitCode = installEnvironment(&search);
- if (!exitCode) {
- // Successful install, so we need to re-scan and select again
- env = NULL;
- exitCode = performSearch(&search, &envs);
- if (exitCode) {
- goto abort;
- }
- exitCode = selectEnvironment(&search, envs, &env);
- }
- }
- if (exitCode == RC_NO_PYTHON) {
- fputws(L"No suitable Python runtime found\n", stderr);
- fputws(L"Pass --list (-0) to see all detected environments on your machine\n", stderr);
- if (!isEnvVarSet(L"PYLAUNCHER_ALLOW_INSTALL") && search.oldStyleTag) {
- fputws(L"or set environment variable PYLAUNCHER_ALLOW_INSTALL to use winget\n"
- L"or open the Microsoft Store to the requested version.\n", stderr);
- }
- goto abort;
- }
- if (exitCode == RC_NO_PYTHON_AT_ALL) {
- fputws(L"No installed Python found!\n", stderr);
- goto abort;
- }
- if (exitCode) {
- goto abort;
- }
-
- if (env) {
- debug(L"env.company: %s\nenv.tag: %s\n", env->company, env->tag);
- } else {
- debug(L"env.company: (null)\nenv.tag: (null)\n");
- }
-
- exitCode = calculateCommandLine(&search, env, launchCommand, sizeof(launchCommand) / sizeof(launchCommand[0]));
- if (exitCode) {
- goto abort;
- }
-
- // Launch selected runtime
- exitCode = launchEnvironment(&search, env, launchCommand);
-
-abort:
- freeSearchInfo(&search);
- freeEnvironmentInfo(envs);
- return exitCode;
-}
-
-
-#if defined(_WINDOWS)
-
-int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
- LPWSTR lpstrCmd, int nShow)
-{
- return process(__argc, __wargv);
-}
-
-#else
-
-int cdecl wmain(int argc, wchar_t ** argv)
-{
- return process(argc, argv);
-}
-
-#endif
diff --git a/PC/layout/README.md b/PC/layout/README.md
new file mode 100644
index 000000000000000..086cc23194b1b96
--- /dev/null
+++ b/PC/layout/README.md
@@ -0,0 +1,341 @@
+# PC/layout script
+
+This document provides an overview of the `PC/layout` script, which is used to
+convert from a build tree to a ready-to-run directory of files. It's the
+equivalent of `make install` when using the Makefile. We use it for official
+binary releases, but it also supports a lot of configuration and can be used for
+whatever your custom scenario should look like.
+
+The script never builds anything. It reads files from an existing build tree
+(`-b`) and the source tree (`-s`), then copies or zips them into a layout. It
+does not require the Python being laid out to be runnable, and it can target a
+different version from the one running the script (see [Build source
+options](#build-source-options)).
+
+## Quick start
+
+Run the script as a directory with any Python 3 interpreter. From the
+repository root:
+
+```
+python PC/layout --help
+```
+
+A useful invocation names an output directory and a preset. The build
+directory is inferred from the running interpreter (`-b` overrides it), but a
+`--preset-` (or explicit `--include-` options) is required;
+otherwise the result is a bare, minimal layout that is rarely useful.
+
+```
+python PC/layout --copy dist --preset-default
+```
+
+### Output options
+
+The script produces one or both of the following outputs. At least one is
+required for the run to do anything useful.
+
+* `--copy ` copies the layout into a directory.
+* `--zip ` writes the layout into a ZIP file.
+
+Additional output-related options:
+
+* `--catalog ` writes a `.cdf` catalog definition for the non-binary
+ files, used later to produce a signed catalog. Compilation and signing are
+ separate steps.
+* `--log ` writes all operations to a log file.
+* `-t`, `--temp ` sets the temporary working directory used for generated
+ files (ZIP library, `._pth`, extracted pip, generated JSON). When omitted, a
+ temporary directory is created automatically. If `--copy` targets a Dev
+ Drive, an adjacent temp directory on the same drive is used for speed.
+* `-v` increases verbosity. Repeat for more detail.
+
+### Layout options
+
+Optional components are selected with `--include-` flags. Presets group
+these into the configurations we ship (see [Layouts](#layouts)). For a quick
+throwaway layout, `-a`/`--include-all` turns on every option (except those
+noted below as not affected by `--include-all`); it is intended for debugging
+rather than for producing a real distribution, since it mixes components that
+are never shipped together. The main component flags are:
+
+* `--include-stable` the stable ABI DLL (`python3.dll`).
+* `--include-pip` pip, extracted from the bundled wheels in
+ `Lib/ensurepip/_bundled`.
+* `--include-pip-user` a `pip.ini` defaulting to `--user` installs.
+* `--include-tcltk` Tcl, Tk and tkinter.
+* `--include-idle` IDLE. Implies `--include-tcltk`.
+* `--include-tests` the test suite.
+* `--include-tools` scripts from `Tools`.
+* `--include-venv` venv and ensurepip, plus the venv launcher executables.
+* `--include-dev` headers and `.lib` files.
+* `--include-symbols` `.pdb` symbol files.
+* `--include-underpth` a `python._pth` file. Not affected by
+ `--include-all`.
+* `--include-launchers` extra launcher executables (used with
+ `--include-appxmanifest`).
+* `--include-appxmanifest` an APPX/MSIX manifest.
+* `--include-props` a `python.props` MSBuild file.
+* `--include-nuspec` a `python.nuspec` NuGet spec.
+* `--include-chm` the CHM documentation.
+* `--include-html-doc` the HTML documentation.
+* `--include-freethreaded` free-threaded binaries. Not affected by
+ `--include-all`.
+* `--include-alias`, `--include-alias3`, `--include-alias3x` aliased
+ entry-point executables (`python.exe`, `python3.exe`, `python3.x.exe` and
+ their `w` variants).
+* `--include-install-json`, `--include-install-embed-json`,
+ `--include-install-test-json` a PyManager `__install__.json` for the
+ respective distribution.
+* `--include-builddetails-json` a [PEP 739](https://peps.python.org/pep-0739/)
+ `build-details.json`.
+
+Layout behavior flags:
+
+* `-d`, `--debug` lays out the debug build (files with the `_d` suffix). The
+ debug binaries must already have been built (for example with
+ `PCbuild/build.bat -d`); the script never builds them.
+* `-p`, `--precompile` includes `.pyc` files (at optimization levels 0, 1 and
+ 2) instead of, or in addition to, `.py` files. The bytecode is compiled
+ during layout, in-process, using the `py_compile` module of the interpreter
+ running the script. It does not launch the laid-out runtime and does not check
+ its version, so it assumes the running interpreter matches the target version;
+ compiling with a mismatched version produces `.pyc` files the target cannot
+ use.
+* `-z`, `--zip-lib` puts the standard library into `python.zip` rather than
+ a `Lib` directory.
+* `--flat-dlls` places extension modules and DLLs next to the executables
+ rather than in a `DLLs` directory.
+* `--include-cat ` includes an already-built `.cat` catalog file. This
+ only copies the named file into the layout; it does not build or sign the
+ catalog and requires no external tooling. Producing the `.cat` from a `.cdf`
+ (see `--catalog`) is a separate step.
+
+### Source options
+
+These options tell the script where to read from and what it is laying out.
+
+* `-s`, `--source ` the repository root. Defaults to the repository
+ containing the script.
+* `-b`, `--build ` the build directory containing the compiled binaries.
+ Defaults to the directory containing the running interpreter.
+* `--arch ` the target architecture. When omitted, it
+ is inferred by reading the PE header of a binary in the build directory.
+* `--doc-build ` the documentation build directory. Defaults to
+ `/Doc/build`.
+
+The following environment variables are read when set:
+
+* `PYTHON_HEXVERSION` / `PYTHONINCLUDE` override version detection when the
+ target version cannot be inferred by reading `Include/patchlevel.h` from the
+ source tree. Set one of these when laying out a version other than the one
+ running the script (see below).
+* `TCL_LIBRARY` locates the Tcl library when `--include-tcltk` is set. If
+ unset, the script falls back to `TCL_LIBRARY.env` in the build directory.
+* `PYTHON_NUSPEC_VERSION`, `PYTHON_PROPS_PLATFORM` fill in the generated
+ `python.nuspec` and `python.props` files. When unset, the version falls back
+ to the detected version number and the platform falls back to a value derived
+ from the target architecture.
+* `APPX_DATA_PUBLISHER`, `APPX_DATA_WINVER`, `APPX_DATA_SHA256` fill in the
+ generated APPX manifest. When unset, they fall back to defaults: a placeholder
+ publisher certificate name, a `MaxVersionTested` of `10.0.22000.0`, and no
+ SCCD hash. These defaults are fine for local use but are not suitable for a
+ signed, Store-ready package.
+
+## Layouts
+
+This section lists the common presets, which reflect our supported
+configurations. Each is selected with `--preset-` and expands to a fixed
+set of `--include-` options. They should help you understand which to use
+for your scenario, and they are useful for figuring out where to put a new file
+that you've just added to CPython.
+
+A preset can be combined with additional `--include-` options to add
+components on top of it; the options are cumulative.
+
+### Development Kit (`--preset-default`)
+
+```
+python PC/layout --copy dist --preset-default
+```
+
+The development kit layout. This is the closest equivalent to a full install
+and contains the interpreter, the standard library in `Lib`, extension modules
+in `DLLs`, headers and import libraries (`--include-dev`), symbols, pip, tcltk,
+IDLE, venv, the test suite, HTML docs, the stable ABI DLL, aliases and a
+`build-details.json`.
+
+### Python install manager (`--preset-pymanager`, `--preset-pymanager-test`)
+
+Layouts for the [Python install manager](https://github.com/python/pymanager).
+This is now the primary way upstream CPython is distributed on Windows. Both
+resemble the development kit but add a PyManager `__install__.json`.
+
+`pymanager-test` additionally includes the test suite and symbols;
+`--preset-pymanager-test` is what produces the `py install PythonTest\`
+packages.
+
+### Embeddable package (`--preset-embed`)
+
+```
+python PC/layout --zip embed.zip --preset-embed
+```
+
+The embeddable package: a minimal, self-contained runtime intended to be
+bundled inside another application. The standard library is placed in a ZIP
+(`--zip-lib`), DLLs are flattened next to the executables (`--flat-dlls`), the
+library is precompiled, and a `._pth` file (`--underpth`) is included to fix the
+search path and isolate the runtime from user site directories. It omits pip,
+tcltk, IDLE, the tests and development files.
+
+`--preset-embed --include-install-embed-json` is what produces the
+`py install PythonEmbed\` packages.
+
+### NuGet package (`--preset-nuget`)
+
+The NuGet package layout. Includes development files, pip, the stable ABI,
+venv, aliases, a `build-details.json`, and the `python.props` and
+`python.nuspec` files that describe the package to MSBuild and NuGet.
+
+### APPX package (`--preset-appx`)
+
+The APPX/MSIX package layout for the Windows Store distribution. Includes the
+stable ABI, pip, tcltk, IDLE, venv, development files, launchers, aliases and an
+APPX manifest. This is no longer used for upstream CPython releases.
+
+### Windows IoT Core (`--preset-iot`)
+
+A minimal layout for Windows IoT Core: aliases, the stable ABI and pip. This is
+not used for upstream CPython releases.
+
+## Build source options
+
+The `-b` and `-s` options let you lay out a build that is separate from the
+interpreter running the script, including a different Python version.
+
+* `-b`/`--build` points at the build tree with the compiled binaries. It does
+ not need to match the interpreter running the script.
+* `-s`/`--source` points at the source tree that supplies `Lib`, `Include`,
+ `Tools` and version information.
+
+The target version is normally read from `Include/patchlevel.h` in the source
+tree. When laying out a version that differs from the running interpreter and
+the source tree is not the matching one, set `PYTHON_HEXVERSION` (a hex version
+such as `0x030f00a0`) or `PYTHONINCLUDE` (a directory containing
+`patchlevel.h`) so the correct DLL names, suffixes and version numbers are used.
+The script validates the inferred version against `patchlevel.h` and stops with
+an error if they disagree.
+
+## Tool structure
+
+The script is a package under `PC/layout`. `main.py` drives argument parsing,
+builds the file list (`get_layout`), generates derived files
+(`generate_source_files`) and copies or zips the result (`copy_files`). Support
+modules live in `PC/layout/support`:
+
+* `options.py` defines the `OPTIONS` and `PRESETS` tables and generates the
+ `--include-*` and `--preset-*` command-line arguments. Add new options and
+ presets here.
+* `constants.py` derives version numbers, DLL names and related constants from
+ `patchlevel.h` or the version environment variables.
+* `filesets.py` provides the file-matching helpers (`FileStemSet`,
+ `FileNameSet`, `FileSuffixSet`) and the `rglob` globbing helper used
+ throughout `main.py`.
+* `arch.py` infers the target architecture from a binary's PE header.
+* `logging.py` configures logging and tracks whether errors occurred.
+* `pip.py` extracts pip from the bundled wheels and lists the pip files.
+* `catalog.py` generates the `.cdf`/`.cat` catalog definitions for signing.
+* `props.py` generates `python.props` (NuGet/MSBuild).
+* `nuspec.py` generates `python.nuspec` (NuGet).
+* `appxmanifest.py` generates APPX/MSIX manifests.
+* `pymanager.py` generates the PyManager `__install__.json`.
+* `builddetails.py` generates the PEP 739 `build-details.json`.
+
+### Classifying new files
+
+`main.py` collects files by globbing shared directories -- the build output
+directory (`-b`) for binaries and the source `Lib`, `Include` and `Tools`
+directories -- and then filters them. A new file added to any of these is
+usually picked up with no change here: build extension modules (`*.pyd`) land in
+`DLLs` (or alongside the executables under `--flat-dlls`), other build DLLs land
+next to them, and files under `Lib` are mirrored into the layout's `Lib`.
+
+Changes are only needed when a new file belongs to an optional component, so it
+must be routed to the right `--include-` option rather than always
+copied. The routing is driven by a set of file-matching tables near the top of
+`main.py` (built from the `FileStemSet`, `FileNameSet` and `FileSuffixSet`
+helpers in `support/filesets.py`). To classify a new file, add its name or
+pattern to the matching table.
+
+Tables that gate files on an optional component (the file is only included when
+the corresponding option is set):
+
+* `TEST_PYDS_ONLY`, `TEST_DLLS_ONLY`, `TEST_DIRS_ONLY` -- test-only extension
+ modules, DLLs and `Lib` directories; gated on `--include-tests`. Extension
+ module names starting with `_test` are already matched; add any other
+ test-only build module here (see also the note in `PCbuild/readme.txt`).
+* `TCLTK_PYDS_ONLY`, `TCLTK_DLLS_ONLY`, `TCLTK_DIRS_ONLY`, `TCLTK_FILES_ONLY` --
+ Tcl/Tk/tkinter extension modules (`_tkinter`), DLLs (`tcl*`, `tk*`, `zlib1`),
+ `Lib` directories (`tkinter`, `turtledemo`) and files (`turtle.py`); gated on
+ `--include-tcltk`.
+* `IDLE_DIRS_ONLY` -- IDLE's `Lib` directory (`idlelib`); gated on
+ `--include-idle`.
+* `VENV_DIRS_ONLY` -- `venv` and `ensurepip` `Lib` directories; gated on
+ `--include-venv`.
+
+Tables that exclude files outright:
+
+* `EXCLUDE_FROM_DLLS` -- build DLLs that are handled elsewhere and must not be
+ copied by the generic DLL glob (`python*`, `pyshellext`, `vcruntime*`).
+* `EXCLUDE_FROM_LIB` -- never copied into `Lib` (`*.pyc`, `__pycache__`,
+ `*.pickle`).
+* `EXCLUDE_FROM_COMPILE` -- `Lib` files that must not be precompiled
+ (`badsyntax_*`, `bad_*`).
+* `EXCLUDE_FROM_CATALOG` -- suffixes omitted from the signing catalog because
+ they are signed separately (`.exe`, `.pyd`, `.dll`).
+
+Special cases in the debug and binary handling:
+
+* `REQUIRED_DLLS` (`libcrypto*`, `libssl*`, `libffi*`) -- always copied from the
+ build directory even for a release layout. Most build DLLs are filtered by
+ their `_d` suffix to match the debug or release build being laid out; these
+ dependencies do not follow that naming and would otherwise be dropped, so they
+ are matched here to force inclusion. Add a new external dependency DLL here if
+ it does not use the `_d` debug-suffix convention.
+
+When deciding where a new file goes, match it to the component it belongs to and
+add it to the corresponding table. If it belongs in every layout and follows the
+normal conventions, no change is needed.
+
+
+## Contributing guidelines
+
+Contributions are welcome. The script supports a broad range of scenarios, and
+extending it for a new one is encouraged. Because the same code produces the
+official releases across every supported version and architecture, additions
+have to respect a few constraints so that existing configurations keep working.
+
+The script runs in an unusual context, and new code must hold to the following
+assumptions:
+
+* Do not assume the Python being laid out can be launched. The script must run
+ correctly using only the interpreter running it, which may differ from the
+ target.
+* Do not assume the layout version matches the version running the script.
+ Derive version-dependent behavior from the detected version rather than from
+ `sys.version` of the running interpreter. (Precompilation with `--precompile`
+ is the one deliberate exception, and it assumes a matching interpreter.)
+* The script may be used to lay out an older version, so behavior changes have
+ to be preserved, either behind an option or gated on a version check, rather
+ than replaced outright.
+* The script never builds anything. Everything it emits must already exist in
+ the build or source tree.
+
+When adding features, keep the surface area aligned with upstream:
+
+* New `--include-` options for further customization are fine.
+* Do not add presets beyond those used by upstream. Presets describe the
+ configurations we ship, so a new one implies a new supported release
+ artifact.
+* Route new files through the classification tables (see [Classifying new
+ files](#classifying-new-files)) rather than adding one-off special cases.
diff --git a/PC/layout/support/appxmanifest.py b/PC/layout/support/appxmanifest.py
index 53977beb8af8346..ba7f1335c7348d5 100644
--- a/PC/layout/support/appxmanifest.py
+++ b/PC/layout/support/appxmanifest.py
@@ -151,6 +151,8 @@
"""
+# This file is no longer included in our repo, but we keep the logic here for
+# building with older releases. We always .is_file() before using it.
SCCD_FILENAME = "PC/classicAppCompat.sccd"
SPECIAL_LOOKUP = object()
diff --git a/PC/pylauncher.rc b/PC/pylauncher.rc
deleted file mode 100644
index 11862643aa69893..000000000000000
--- a/PC/pylauncher.rc
+++ /dev/null
@@ -1,67 +0,0 @@
-#include
-
-#include "python_ver_rc.h"
-
-#ifndef RT_MANIFEST
-// bpo-45220: Cannot reliably #include RT_MANIFEST from
-// anywhere, so we hardcode it
-#define RT_MANIFEST 24
-#endif
-// Include the manifest file that indicates we support all
-// current versions of Windows.
-1 RT_MANIFEST "python.manifest"
-
-#if defined(PY_ICON)
-1 ICON DISCARDABLE "icons\python.ico"
-#elif defined(PYW_ICON)
-1 ICON DISCARDABLE "icons\pythonw.ico"
-#else
-1 ICON DISCARDABLE "icons\launcher.ico"
-2 ICON DISCARDABLE "icons\py.ico"
-3 ICON DISCARDABLE "icons\pyc.ico"
-4 ICON DISCARDABLE "icons\pyd.ico"
-5 ICON DISCARDABLE "icons\python.ico"
-6 ICON DISCARDABLE "icons\pythonw.ico"
-7 ICON DISCARDABLE "icons\setup.ico"
-#endif
-
-1 USAGE "launcher-usage.txt"
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Version
-//
-
-VS_VERSION_INFO VERSIONINFO
- FILEVERSION PYVERSION64
- PRODUCTVERSION PYVERSION64
- FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
- FILEFLAGS VS_FF_DEBUG
-#else
- FILEFLAGS 0x0L
-#endif
- FILEOS VOS__WINDOWS32
- FILETYPE VFT_APP
- FILESUBTYPE 0x0L
-BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "000004b0"
- BEGIN
- VALUE "CompanyName", PYTHON_COMPANY "\0"
- VALUE "FileDescription", "Python\0"
- VALUE "FileVersion", PYTHON_VERSION
- VALUE "InternalName", "Python Launcher\0"
- VALUE "LegalCopyright", PYTHON_COPYRIGHT "\0"
- VALUE "OriginalFilename", "py" PYTHON_DEBUG_EXT ".exe\0"
- VALUE "ProductName", "Python\0"
- VALUE "ProductVersion", PYTHON_VERSION
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x0, 1200
- END
-END
\ No newline at end of file
diff --git a/PC/pyshellext.cpp b/PC/pyshellext.cpp
deleted file mode 100644
index d16de0b717151e5..000000000000000
--- a/PC/pyshellext.cpp
+++ /dev/null
@@ -1,603 +0,0 @@
-// Support back to Vista
-#define _WIN32_WINNT _WIN32_WINNT_VISTA
-#include
-
-// Use WRL to define a classic COM class
-#define __WRL_CLASSIC_COM__
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-#define DDWM_UPDATEWINDOW (WM_USER+3)
-
-static HINSTANCE hModule;
-static CLIPFORMAT cfDropDescription;
-static CLIPFORMAT cfDragWindow;
-
-#define CLASS_GUID "{BEA218D2-6950-497B-9434-61683EC065FE}"
-static const LPCWSTR CLASS_SUBKEY = L"Software\\Classes\\CLSID\\" CLASS_GUID;
-static const LPCWSTR DRAG_MESSAGE = L"Open with %1";
-
-using namespace Microsoft::WRL;
-
-HRESULT FilenameListCchLengthA(LPCSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) {
- HRESULT hr = S_OK;
- size_t count = 0;
- size_t length = 0;
-
- while (pszSource && pszSource[0]) {
- size_t oneLength;
- hr = StringCchLengthA(pszSource, cchMax - length, &oneLength);
- if (FAILED(hr)) {
- return hr;
- }
- count += 1;
- length += oneLength + (strchr(pszSource, ' ') ? 3 : 1);
- pszSource = &pszSource[oneLength + 1];
- }
-
- *pcchCount = count;
- *pcchLength = length;
- return hr;
-}
-
-HRESULT FilenameListCchLengthW(LPCWSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) {
- HRESULT hr = S_OK;
- size_t count = 0;
- size_t length = 0;
-
- while (pszSource && pszSource[0]) {
- size_t oneLength;
- hr = StringCchLengthW(pszSource, cchMax - length, &oneLength);
- if (FAILED(hr)) {
- return hr;
- }
- count += 1;
- length += oneLength + (wcschr(pszSource, ' ') ? 3 : 1);
- pszSource = &pszSource[oneLength + 1];
- }
-
- *pcchCount = count;
- *pcchLength = length;
- return hr;
-}
-
-HRESULT FilenameListCchCopyA(STRSAFE_LPSTR pszDest, size_t cchDest, LPCSTR pszSource, LPCSTR pszSeparator) {
- HRESULT hr = S_OK;
-
- while (pszSource[0]) {
- STRSAFE_LPSTR newDest;
-
- hr = StringCchCopyExA(pszDest, cchDest, pszSource, &newDest, &cchDest, 0);
- if (FAILED(hr)) {
- return hr;
- }
- pszSource += (newDest - pszDest) + 1;
- pszDest = PathQuoteSpacesA(pszDest) ? newDest + 2 : newDest;
-
- if (pszSource[0]) {
- hr = StringCchCopyExA(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0);
- if (FAILED(hr)) {
- return hr;
- }
- pszDest = newDest;
- }
- }
-
- return hr;
-}
-
-HRESULT FilenameListCchCopyW(STRSAFE_LPWSTR pszDest, size_t cchDest, LPCWSTR pszSource, LPCWSTR pszSeparator) {
- HRESULT hr = S_OK;
-
- while (pszSource[0]) {
- STRSAFE_LPWSTR newDest;
-
- hr = StringCchCopyExW(pszDest, cchDest, pszSource, &newDest, &cchDest, 0);
- if (FAILED(hr)) {
- return hr;
- }
- pszSource += (newDest - pszDest) + 1;
- pszDest = PathQuoteSpacesW(pszDest) ? newDest + 2 : newDest;
-
- if (pszSource[0]) {
- hr = StringCchCopyExW(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0);
- if (FAILED(hr)) {
- return hr;
- }
- pszDest = newDest;
- }
- }
-
- return hr;
-}
-
-class DECLSPEC_UUID(CLASS_GUID) PyShellExt : public RuntimeClass<
- RuntimeClassFlags,
- IDropTarget,
- IPersistFile
->
-{
- LPOLESTR target, target_dir;
- DWORD target_mode;
-
- IDataObject *data_obj;
-
-public:
- PyShellExt() : target(NULL), target_dir(NULL), target_mode(0), data_obj(NULL) {
- OutputDebugString(L"PyShellExt::PyShellExt");
- }
-
- ~PyShellExt() {
- if (target) {
- CoTaskMemFree(target);
- }
- if (target_dir) {
- CoTaskMemFree(target_dir);
- }
- if (data_obj) {
- data_obj->Release();
- }
- }
-
-private:
- HRESULT UpdateDropDescription(IDataObject *pDataObj) {
- STGMEDIUM medium;
- FORMATETC fmt = {
- cfDropDescription,
- NULL,
- DVASPECT_CONTENT,
- -1,
- TYMED_HGLOBAL
- };
-
- auto hr = pDataObj->GetData(&fmt, &medium);
- if (FAILED(hr)) {
- OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to get DROPDESCRIPTION format");
- return hr;
- }
- if (!medium.hGlobal) {
- OutputDebugString(L"PyShellExt::UpdateDropDescription - DROPDESCRIPTION format had NULL hGlobal");
- ReleaseStgMedium(&medium);
- return E_FAIL;
- }
- auto dd = (DROPDESCRIPTION*)GlobalLock(medium.hGlobal);
- if (!dd) {
- OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to lock DROPDESCRIPTION hGlobal");
- ReleaseStgMedium(&medium);
- return E_FAIL;
- }
- StringCchCopy(dd->szMessage, sizeof(dd->szMessage) / sizeof(dd->szMessage[0]), DRAG_MESSAGE);
- StringCchCopy(dd->szInsert, sizeof(dd->szInsert) / sizeof(dd->szInsert[0]), PathFindFileNameW(target));
- dd->type = DROPIMAGE_MOVE;
-
- GlobalUnlock(medium.hGlobal);
- ReleaseStgMedium(&medium);
-
- return S_OK;
- }
-
- HRESULT GetDragWindow(IDataObject *pDataObj, HWND *phWnd) {
- HRESULT hr;
- HWND *pMem;
- STGMEDIUM medium;
- FORMATETC fmt = {
- cfDragWindow,
- NULL,
- DVASPECT_CONTENT,
- -1,
- TYMED_HGLOBAL
- };
-
- hr = pDataObj->GetData(&fmt, &medium);
- if (FAILED(hr)) {
- OutputDebugString(L"PyShellExt::GetDragWindow - failed to get DragWindow format");
- return hr;
- }
- if (!medium.hGlobal) {
- OutputDebugString(L"PyShellExt::GetDragWindow - DragWindow format had NULL hGlobal");
- ReleaseStgMedium(&medium);
- return E_FAIL;
- }
-
- pMem = (HWND*)GlobalLock(medium.hGlobal);
- if (!pMem) {
- OutputDebugString(L"PyShellExt::GetDragWindow - failed to lock DragWindow hGlobal");
- ReleaseStgMedium(&medium);
- return E_FAIL;
- }
-
- *phWnd = *pMem;
-
- GlobalUnlock(medium.hGlobal);
- ReleaseStgMedium(&medium);
-
- return S_OK;
- }
-
- HRESULT GetArguments(IDataObject *pDataObj, LPCWSTR *pArguments) {
- HRESULT hr;
- DROPFILES *pdropfiles;
-
- STGMEDIUM medium;
- FORMATETC fmt = {
- CF_HDROP,
- NULL,
- DVASPECT_CONTENT,
- -1,
- TYMED_HGLOBAL
- };
-
- hr = pDataObj->GetData(&fmt, &medium);
- if (FAILED(hr)) {
- OutputDebugString(L"PyShellExt::GetArguments - failed to get CF_HDROP format");
- return hr;
- }
- if (!medium.hGlobal) {
- OutputDebugString(L"PyShellExt::GetArguments - CF_HDROP format had NULL hGlobal");
- ReleaseStgMedium(&medium);
- return E_FAIL;
- }
-
- pdropfiles = (DROPFILES*)GlobalLock(medium.hGlobal);
- if (!pdropfiles) {
- OutputDebugString(L"PyShellExt::GetArguments - failed to lock CF_HDROP hGlobal");
- ReleaseStgMedium(&medium);
- return E_FAIL;
- }
-
- if (pdropfiles->fWide) {
- LPCWSTR files = (LPCWSTR)((char*)pdropfiles + pdropfiles->pFiles);
- size_t len, count;
- hr = FilenameListCchLengthW(files, 32767, &len, &count);
- if (SUCCEEDED(hr)) {
- LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
- if (args) {
- hr = FilenameListCchCopyW(args, 32767, files, L" ");
- if (SUCCEEDED(hr)) {
- *pArguments = args;
- } else {
- CoTaskMemFree(args);
- }
- } else {
- hr = E_OUTOFMEMORY;
- }
- }
- } else {
- LPCSTR files = (LPCSTR)((char*)pdropfiles + pdropfiles->pFiles);
- size_t len, count;
- hr = FilenameListCchLengthA(files, 32767, &len, &count);
- if (SUCCEEDED(hr)) {
- LPSTR temp = (LPSTR)CoTaskMemAlloc(sizeof(CHAR) * (len + 1));
- if (temp) {
- hr = FilenameListCchCopyA(temp, 32767, files, " ");
- if (SUCCEEDED(hr)) {
- int wlen = MultiByteToWideChar(CP_ACP, 0, temp, (int)len, NULL, 0);
- if (wlen) {
- LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (wlen + 1));
- if (MultiByteToWideChar(CP_ACP, 0, temp, (int)len, args, wlen + 1)) {
- *pArguments = args;
- } else {
- OutputDebugString(L"PyShellExt::GetArguments - failed to convert multi-byte to wide-char path");
- CoTaskMemFree(args);
- hr = E_FAIL;
- }
- } else {
- OutputDebugString(L"PyShellExt::GetArguments - failed to get length of wide-char path");
- hr = E_FAIL;
- }
- }
- CoTaskMemFree(temp);
- } else {
- hr = E_OUTOFMEMORY;
- }
- }
- }
-
- GlobalUnlock(medium.hGlobal);
- ReleaseStgMedium(&medium);
-
- return hr;
- }
-
- HRESULT NotifyDragWindow(HWND hwnd) {
- LRESULT res;
-
- if (!hwnd) {
- return S_FALSE;
- }
-
- res = SendMessage(hwnd, DDWM_UPDATEWINDOW, 0, NULL);
-
- if (res) {
- OutputDebugString(L"PyShellExt::NotifyDragWindow - failed to post DDWM_UPDATEWINDOW");
- return E_FAIL;
- }
-
- return S_OK;
- }
-
-public:
- // IDropTarget implementation
-
- STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
- HWND hwnd;
-
- OutputDebugString(L"PyShellExt::DragEnter");
-
- pDataObj->AddRef();
- data_obj = pDataObj;
-
- *pdwEffect = DROPEFFECT_MOVE;
-
- if (FAILED(UpdateDropDescription(data_obj))) {
- OutputDebugString(L"PyShellExt::DragEnter - failed to update drop description");
- }
- if (FAILED(GetDragWindow(data_obj, &hwnd))) {
- OutputDebugString(L"PyShellExt::DragEnter - failed to get drag window");
- }
- if (FAILED(NotifyDragWindow(hwnd))) {
- OutputDebugString(L"PyShellExt::DragEnter - failed to notify drag window");
- }
-
- return S_OK;
- }
-
- STDMETHODIMP DragLeave() {
- return S_OK;
- }
-
- STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
- return S_OK;
- }
-
- STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
- LPCWSTR args;
-
- OutputDebugString(L"PyShellExt::Drop");
- *pdwEffect = DROPEFFECT_NONE;
-
- if (pDataObj != data_obj) {
- OutputDebugString(L"PyShellExt::Drop - unexpected data object");
- return E_FAIL;
- }
-
- data_obj->Release();
- data_obj = NULL;
-
- if (SUCCEEDED(GetArguments(pDataObj, &args))) {
- OutputDebugString(args);
- ShellExecute(NULL, NULL, target, args, target_dir, SW_NORMAL);
-
- CoTaskMemFree((LPVOID)args);
- } else {
- OutputDebugString(L"PyShellExt::Drop - failed to get launch arguments");
- }
-
- return S_OK;
- }
-
- // IPersistFile implementation
-
- STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName) {
- HRESULT hr;
- size_t len;
-
- if (!ppszFileName) {
- return E_POINTER;
- }
-
- hr = StringCchLength(target, STRSAFE_MAX_CCH - 1, &len);
- if (FAILED(hr)) {
- return E_FAIL;
- }
-
- *ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
- if (!*ppszFileName) {
- return E_OUTOFMEMORY;
- }
-
- hr = StringCchCopy(*ppszFileName, len + 1, target);
- if (FAILED(hr)) {
- CoTaskMemFree(*ppszFileName);
- *ppszFileName = NULL;
- return E_FAIL;
- }
-
- return S_OK;
- }
-
- STDMETHODIMP IsDirty() {
- return S_FALSE;
- }
-
- STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) {
- HRESULT hr;
- size_t len;
-
- OutputDebugString(L"PyShellExt::Load");
- OutputDebugString(pszFileName);
-
- hr = StringCchLength(pszFileName, STRSAFE_MAX_CCH - 1, &len);
- if (FAILED(hr)) {
- OutputDebugString(L"PyShellExt::Load - failed to get string length");
- return hr;
- }
-
- if (target) {
- CoTaskMemFree(target);
- }
- if (target_dir) {
- CoTaskMemFree(target_dir);
- }
-
- target = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
- if (!target) {
- OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
- return E_OUTOFMEMORY;
- }
- target_dir = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
- if (!target_dir) {
- OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
- return E_OUTOFMEMORY;
- }
-
- hr = StringCchCopy(target, len + 1, pszFileName);
- if (FAILED(hr)) {
- OutputDebugString(L"PyShellExt::Load - failed to copy string");
- return hr;
- }
-
- hr = StringCchCopy(target_dir, len + 1, pszFileName);
- if (FAILED(hr)) {
- OutputDebugString(L"PyShellExt::Load - failed to copy string");
- return hr;
- }
- if (!PathRemoveFileSpecW(target_dir)) {
- OutputDebugStringW(L"PyShellExt::Load - failed to remove filespec from target");
- return E_FAIL;
- }
-
- OutputDebugString(target);
- target_mode = dwMode;
- OutputDebugString(L"PyShellExt::Load - S_OK");
- return S_OK;
- }
-
- STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember) {
- return E_NOTIMPL;
- }
-
- STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName) {
- return E_NOTIMPL;
- }
-
- STDMETHODIMP GetClassID(CLSID *pClassID) {
- *pClassID = __uuidof(PyShellExt);
- return S_OK;
- }
-};
-
-CoCreatableClass(PyShellExt);
-
-STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) {
- return Module::GetModule().GetClassObject(rclsid, riid, ppv);
-}
-
-STDAPI DllCanUnloadNow() {
- return Module::GetModule().Terminate() ? S_OK : S_FALSE;
-}
-
-STDAPI DllRegisterServer() {
- LONG res;
- LPSECURITY_ATTRIBUTES psecattr = NULL;
- HKEY key, ipsKey;
- WCHAR modname[MAX_PATH];
- DWORD modname_len;
-
- OutputDebugString(L"PyShellExt::DllRegisterServer");
- if (!hModule) {
- OutputDebugString(L"PyShellExt::DllRegisterServer - module handle was not set");
- return SELFREG_E_CLASS;
- }
- modname_len = GetModuleFileName(hModule, modname, MAX_PATH);
- if (modname_len == 0 ||
- (modname_len == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
- OutputDebugString(L"PyShellExt::DllRegisterServer - failed to get module file name");
- return SELFREG_E_CLASS;
- }
-
- DWORD disp;
- res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, CLASS_SUBKEY, 0, NULL, 0,
- KEY_ALL_ACCESS, psecattr, &key, &disp);
- if (res == ERROR_ACCESS_DENIED) {
- OutputDebugString(L"PyShellExt::DllRegisterServer - failed to write per-machine registration. Attempting per-user instead.");
- res = RegCreateKeyEx(HKEY_CURRENT_USER, CLASS_SUBKEY, 0, NULL, 0,
- KEY_ALL_ACCESS, psecattr, &key, &disp);
- }
- if (res != ERROR_SUCCESS) {
- OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create class key");
- return SELFREG_E_CLASS;
- }
-
- res = RegCreateKeyEx(key, L"InProcServer32", 0, NULL, 0,
- KEY_ALL_ACCESS, psecattr, &ipsKey, NULL);
- if (res != ERROR_SUCCESS) {
- RegCloseKey(key);
- OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create InProcServer32 key");
- return SELFREG_E_CLASS;
- }
-
- res = RegSetValueEx(ipsKey, NULL, 0,
- REG_SZ, (LPBYTE)modname, modname_len * sizeof(modname[0]));
-
- if (res != ERROR_SUCCESS) {
- RegCloseKey(ipsKey);
- RegCloseKey(key);
- OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set server path");
- return SELFREG_E_CLASS;
- }
-
- res = RegSetValueEx(ipsKey, L"ThreadingModel", 0,
- REG_SZ, (LPBYTE)(L"Apartment"), sizeof(L"Apartment"));
-
- RegCloseKey(ipsKey);
- RegCloseKey(key);
- if (res != ERROR_SUCCESS) {
- OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set threading model");
- return SELFREG_E_CLASS;
- }
-
- SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
-
- OutputDebugString(L"PyShellExt::DllRegisterServer - S_OK");
- return S_OK;
-}
-
-STDAPI DllUnregisterServer() {
- LONG res_lm, res_cu;
-
- res_lm = RegDeleteTree(HKEY_LOCAL_MACHINE, CLASS_SUBKEY);
- if (res_lm != ERROR_SUCCESS && res_lm != ERROR_FILE_NOT_FOUND) {
- OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-machine registration");
- return SELFREG_E_CLASS;
- }
-
- res_cu = RegDeleteTree(HKEY_CURRENT_USER, CLASS_SUBKEY);
- if (res_cu != ERROR_SUCCESS && res_cu != ERROR_FILE_NOT_FOUND) {
- OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-user registration");
- return SELFREG_E_CLASS;
- }
-
- if (res_lm == ERROR_FILE_NOT_FOUND && res_cu == ERROR_FILE_NOT_FOUND) {
- OutputDebugString(L"PyShellExt::DllUnregisterServer - extension was not registered");
- return SELFREG_E_CLASS;
- }
-
- SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
-
- OutputDebugString(L"PyShellExt::DllUnregisterServer - S_OK");
- return S_OK;
-}
-
-STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) {
- if (reason == DLL_PROCESS_ATTACH) {
- hModule = hinst;
-
- cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION);
- if (!cfDropDescription) {
- OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format");
- }
- cfDragWindow = RegisterClipboardFormat(L"DragWindow");
- if (!cfDragWindow) {
- OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format");
- }
-
- DisableThreadLibraryCalls(hinst);
- }
- return TRUE;
-}
diff --git a/PC/pyshellext.def b/PC/pyshellext.def
deleted file mode 100644
index 288a9adf982f197..000000000000000
--- a/PC/pyshellext.def
+++ /dev/null
@@ -1,5 +0,0 @@
-EXPORTS
- DllRegisterServer PRIVATE
- DllUnregisterServer PRIVATE
- DllGetClassObject PRIVATE
- DllCanUnloadNow PRIVATE
diff --git a/PC/pyshellext.rc b/PC/pyshellext.rc
deleted file mode 100644
index af797ce95d5077e..000000000000000
--- a/PC/pyshellext.rc
+++ /dev/null
@@ -1,51 +0,0 @@
-#include
-
-#include "python_ver_rc.h"
-
-#ifndef RT_MANIFEST
-// bpo-45220: Cannot reliably #include RT_MANIFEST from
-// anywhere, so we hardcode it
-#define RT_MANIFEST 24
-#endif
-
-// Include the manifest file that indicates we support all
-// current versions of Windows.
-1 RT_MANIFEST "python.manifest"
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Version
-//
-
-VS_VERSION_INFO VERSIONINFO
- FILEVERSION PYVERSION64
- PRODUCTVERSION PYVERSION64
- FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
- FILEFLAGS VS_FF_DEBUG
-#else
- FILEFLAGS 0x0L
-#endif
- FILEOS VOS__WINDOWS32
- FILETYPE VFT_APP
- FILESUBTYPE 0x0L
-BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "000004b0"
- BEGIN
- VALUE "CompanyName", PYTHON_COMPANY "\0"
- VALUE "FileDescription", "Python\0"
- VALUE "FileVersion", PYTHON_VERSION
- VALUE "InternalName", "Python Launcher Shell Extension\0"
- VALUE "LegalCopyright", PYTHON_COPYRIGHT "\0"
- VALUE "OriginalFilename", "pyshellext" PYTHON_DEBUG_EXT ".dll\0"
- VALUE "ProductName", "Python\0"
- VALUE "ProductVersion", PYTHON_VERSION
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x0, 1200
- END
-END
\ No newline at end of file
diff --git a/PC/store_info.txt b/PC/store_info.txt
deleted file mode 100644
index d150ba17cbe62dd..000000000000000
--- a/PC/store_info.txt
+++ /dev/null
@@ -1,156 +0,0 @@
-# Overview
-
-NOTE: This file requires more content.
-
-Since Python 3.7.2, releases have been made through the Microsoft Store
-to allow easy installation on Windows 10.0.17763.0 and later.
-
-# Building
-
-To build the store package, the PC/layout script should be used.
-Execute the directory with the build of Python to package, and pass
-"-h" for full command-line options.
-
-To sideload test builds, you will need a local certificate.
-Instructions are available at
-https://docs.microsoft.com/windows/uwp/packaging/create-certificate-package-signing.
-
-After exporting your certificate, you will need the subject name and
-SHA256 hash. The `certutil -dump ` command will display this
-information.
-
-To build for sideloading, use these commands in PowerShell:
-
-```
-$env:APPX_DATA_PUBLISHER=
-$env:APPX_DATA_SHA256=
-$env:SigningCertificateFile=
-
-python PC/layout --copy --include-appxmanifest
-Tools/msi/make_appx.ps1 python.msix -sign
-
-Add-AppxPackage python.msix
-```
-
-(Note that only the last command requires PowerShell, and the others
-can be used from Command Prompt. You can also double-click to install
-the final package.)
-
-To build for publishing to the Store, use these commands:
-
-```
-$env:APPX_DATA_PUBLISHER = $null
-$env:APPX_DATA_SHA256 = $null
-
-python PC/layout --copy --preset-appxmanifest --precompile
-Tools/msi/make_appx.ps1 python.msix
-```
-
-Note that this package cannot be installed locally. It may only be
-added to a submission for the store.
-
-
-# Submission Metadata
-
-This file contains the text that we use to fill out the store listing
-for the Microsoft Store. It needs to be entered manually when creating
-a new submission via the dashboard at
-https://partner.microsoft.com/dashboard.
-
-We keep it here for convenience and to allow it to be updated via pull
-requests.
-
-When submitting a new app, the HeadlessAppBypass waiver will be needed.
-To request this, send an email to PartnerOps@microsoft.com with the app
-ID (12 character token available from the dashboard). The waiver needs
-to be applied *before* uploading the package (as of November 2019).
-
-Ensure that the new app is named "Python.3.X", where X is the minor
-version of the release. If the name provided initially does not match
-the name used when building the package, the upload will fail. The
-display name shown to users can be set later.
-
-## Title
-
-Python 3.9
-
-## Short Title
-
-Python
-
-## Description
-
-Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python’s elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms.
-
-The Python interpreter and the extensive standard library are freely available in source or binary form for all major platforms from the Python web site, https://www.python.org/, and may be freely distributed. The same site also contains distributions of and pointers to many free third party Python modules, programs and tools, and additional documentation.
-
-The Python interpreter is easily extended with new functions and data types implemented in C or C++ (or other languages callable from C). Python is also suitable as an extension language for customizable applications.
-
-## ShortDescription
-
-The Python 3.9 interpreter and runtime.
-
-## Copyright Trademark Information
-
-(c) Python Software Foundation
-
-## Additional License Terms
-
-Visit https://docs.python.org/3.9/license.html for latest license terms.
-
-PSF LICENSE AGREEMENT FOR PYTHON 3.9
-
-1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
- the Individual or Organization ("Licensee") accessing and otherwise using Python
- 3.9 software in source or binary form and its associated documentation.
-
-2. Subject to the terms and conditions of this License Agreement, PSF hereby
- grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
- analyze, test, perform and/or display publicly, prepare derivative works,
- distribute, and otherwise use Python 3.9 alone or in any derivative
- version, provided, however, that PSF's License Agreement and PSF's notice of
- copyright, i.e., "Copyright © 2001 Python Software Foundation; All Rights
- Reserved" are retained in Python 3.9 alone or in any derivative version
- prepared by Licensee.
-
-3. In the event Licensee prepares a derivative work that is based on or
- incorporates Python 3.9 or any part thereof, and wants to make the
- derivative work available to others as provided herein, then Licensee hereby
- agrees to include in any such work a brief summary of the changes made to Python
- 3.9.
-
-4. PSF is making Python 3.9 available to Licensee on an "AS IS" basis.
- PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF
- EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
- WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
- USE OF PYTHON 3.9 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.9
- FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
- MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.9, OR ANY DERIVATIVE
- THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material breach of
- its terms and conditions.
-
-7. Nothing in this License Agreement shall be deemed to create any relationship
- of agency, partnership, or joint venture between PSF and Licensee. This License
- Agreement does not grant permission to use PSF trademarks or trade name in a
- trademark sense to endorse or promote products or services of Licensee, or any
- third party.
-
-8. By copying, installing or otherwise using Python 3.9, Licensee agrees
- to be bound by the terms and conditions of this License Agreement.
-
-## Features
-
-* Easy to install Python runtime
-* Supported by core CPython team
-* Find Python, Pip and Idle on PATH
-
-## Search Terms
-
-* Python
-* Scripting
-* Interpreter
-
diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj
index 9d077bbd3f0ba27..53aec0276beaee2 100644
--- a/PCbuild/pcbuild.proj
+++ b/PCbuild/pcbuild.proj
@@ -63,10 +63,6 @@
-
-
-
-
diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln
index 09a989d38648df4..e2cac3158831eed 100644
--- a/PCbuild/pcbuild.sln
+++ b/PCbuild/pcbuild.sln
@@ -111,16 +111,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xxlimited", "xxlimited.vcxp
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testbuffer", "_testbuffer.vcxproj", "{A2697BD3-28C1-4AEC-9106-8B748639FD16}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pylauncher", "pylauncher.vcxproj", "{7B2727B5-5A3F-40EE-A866-43A13CD31446}"
- ProjectSection(ProjectDependencies) = postProject
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782} = {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pywlauncher", "pywlauncher.vcxproj", "{1D4B18D3-7C12-4ECB-9179-8531FF876CE6}"
- ProjectSection(ProjectDependencies) = postProject
- {7B2727B5-5A3F-40EE-A866-43A13CD31446} = {7B2727B5-5A3F-40EE-A866-43A13CD31446}
- EndProjectSection
-EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_freeze_module", "_freeze_module.vcxproj", "{19C0C13F-47CA-4432-AFF3-799A296A4DDC}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_overlapped", "_overlapped.vcxproj", "{EB6E69DD-04BF-4543-9B92-49FAABCEAC2E}"
@@ -131,8 +121,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testmultiphase", "_testmul
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testsinglephase", "_testsinglephase.vcxproj", "{2097F1C1-597C-4167-93E3-656A7D6339B2}"
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pyshellext", "pyshellext.vcxproj", "{0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}"
-EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testconsole", "_testconsole.vcxproj", "{B244E787-C445-441C-BDF4-5A4F1A3A1E51}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_asyncio", "_asyncio.vcxproj", "{384C224A-7474-476E-A01B-750EA7DE918C}"
@@ -1079,70 +1067,6 @@ Global
{A2697BD3-28C1-4AEC-9106-8B748639FD16}.Release|Win32.Build.0 = Release|Win32
{A2697BD3-28C1-4AEC-9106-8B748639FD16}.Release|x64.ActiveCfg = Release|x64
{A2697BD3-28C1-4AEC-9106-8B748639FD16}.Release|x64.Build.0 = Release|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM.ActiveCfg = Debug|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM.Build.0 = Debug|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|ARM64.Build.0 = Debug|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|Win32.ActiveCfg = Debug|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|Win32.Build.0 = Debug|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|x64.ActiveCfg = Debug|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Debug|x64.Build.0 = Debug|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM.Build.0 = PGInstrument|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|Win32.ActiveCfg = Release|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|Win32.Build.0 = Release|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|x64.ActiveCfg = Release|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGInstrument|x64.Build.0 = Release|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM.Build.0 = PGUpdate|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|Win32.ActiveCfg = Release|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|Win32.Build.0 = Release|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|x64.ActiveCfg = Release|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.PGUpdate|x64.Build.0 = Release|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM.ActiveCfg = Release|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM.Build.0 = Release|ARM
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM64.ActiveCfg = Release|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|ARM64.Build.0 = Release|ARM64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|Win32.ActiveCfg = Release|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|Win32.Build.0 = Release|Win32
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|x64.ActiveCfg = Release|x64
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}.Release|x64.Build.0 = Release|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM.ActiveCfg = Debug|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM.Build.0 = Debug|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|ARM64.Build.0 = Debug|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|Win32.ActiveCfg = Debug|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|Win32.Build.0 = Debug|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|x64.ActiveCfg = Debug|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Debug|x64.Build.0 = Debug|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM.Build.0 = PGInstrument|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|Win32.ActiveCfg = Release|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|Win32.Build.0 = Release|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|x64.ActiveCfg = Release|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGInstrument|x64.Build.0 = Release|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM.Build.0 = PGUpdate|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|Win32.ActiveCfg = Release|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|Win32.Build.0 = Release|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|x64.ActiveCfg = Release|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.PGUpdate|x64.Build.0 = Release|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM.ActiveCfg = Release|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM.Build.0 = Release|ARM
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM64.ActiveCfg = Release|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|ARM64.Build.0 = Release|ARM64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|Win32.ActiveCfg = Release|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|Win32.Build.0 = Release|Win32
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|x64.ActiveCfg = Release|x64
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}.Release|x64.Build.0 = Release|x64
{19C0C13F-47CA-4432-AFF3-799A296A4DDC}.Debug|ARM.ActiveCfg = Debug|ARM
{19C0C13F-47CA-4432-AFF3-799A296A4DDC}.Debug|ARM.Build.0 = Debug|ARM
{19C0C13F-47CA-4432-AFF3-799A296A4DDC}.Debug|ARM64.ActiveCfg = Debug|ARM64
@@ -1293,38 +1217,6 @@ Global
{2097F1C1-597C-4167-93E3-656A7D6339B2}.Release|Win32.Build.0 = Release|Win32
{2097F1C1-597C-4167-93E3-656A7D6339B2}.Release|x64.ActiveCfg = Release|x64
{2097F1C1-597C-4167-93E3-656A7D6339B2}.Release|x64.Build.0 = Release|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM.ActiveCfg = Debug|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM.Build.0 = Debug|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|ARM64.Build.0 = Debug|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|Win32.ActiveCfg = Debug|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|Win32.Build.0 = Debug|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|x64.ActiveCfg = Debug|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Debug|x64.Build.0 = Debug|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM.Build.0 = PGInstrument|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|Win32.Build.0 = PGInstrument|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|x64.ActiveCfg = PGInstrument|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGInstrument|x64.Build.0 = PGInstrument|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM.Build.0 = PGUpdate|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|Win32.Build.0 = PGUpdate|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|x64.ActiveCfg = PGUpdate|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.PGUpdate|x64.Build.0 = PGUpdate|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM.ActiveCfg = Release|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM.Build.0 = Release|ARM
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM64.ActiveCfg = Release|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|ARM64.Build.0 = Release|ARM64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|Win32.ActiveCfg = Release|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|Win32.Build.0 = Release|Win32
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|x64.ActiveCfg = Release|x64
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}.Release|x64.Build.0 = Release|x64
{B244E787-C445-441C-BDF4-5A4F1A3A1E51}.Debug|ARM.ActiveCfg = Debug|ARM
{B244E787-C445-441C-BDF4-5A4F1A3A1E51}.Debug|ARM.Build.0 = Debug|ARM
{B244E787-C445-441C-BDF4-5A4F1A3A1E51}.Debug|ARM64.ActiveCfg = Debug|ARM64
diff --git a/PCbuild/pylauncher.vcxproj b/PCbuild/pylauncher.vcxproj
deleted file mode 100644
index ca422ef4d02e932..000000000000000
--- a/PCbuild/pylauncher.vcxproj
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
-
- Debug
- ARM
-
-
- Debug
- ARM64
-
-
- Debug
- Win32
-
-
- Debug
- x64
-
-
- PGInstrument
- ARM
-
-
- PGInstrument
- ARM64
-
-
- PGInstrument
- Win32
-
-
- PGInstrument
- x64
-
-
- PGUpdate
- ARM
-
-
- PGUpdate
- ARM64
-
-
- PGUpdate
- Win32
-
-
- PGUpdate
- x64
-
-
- Release
- ARM
-
-
- Release
- ARM64
-
-
- Release
- Win32
-
-
- Release
- x64
-
-
-
- {7B2727B5-5A3F-40EE-A866-43A13CD31446}
- pylauncher
- py
- false
-
-
-
-
- Application
- Unicode
-
-
-
-
-
- ClCompile
-
-
-
-
-
-
-
-
- _CONSOLE;%(PreprocessorDefinitions)
- MultiThreaded
-
-
- shell32.lib;pathcch.lib;%(AdditionalDependencies)
- Console
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/PCbuild/pylauncher.vcxproj.filters b/PCbuild/pylauncher.vcxproj.filters
deleted file mode 100644
index f10f6d5669e283c..000000000000000
--- a/PCbuild/pylauncher.vcxproj.filters
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
-
-
-
-
- Source Files
-
-
-
-
- Resource Files
-
-
-
-
- Resource Files
-
-
-
\ No newline at end of file
diff --git a/PCbuild/pyshellext.vcxproj b/PCbuild/pyshellext.vcxproj
deleted file mode 100644
index ea432d6bc9a3f33..000000000000000
--- a/PCbuild/pyshellext.vcxproj
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
- Debug
- ARM
-
-
- Debug
- ARM64
-
-
- Debug
- Win32
-
-
- Debug
- x64
-
-
- PGInstrument
- ARM
-
-
- PGInstrument
- ARM64
-
-
- PGInstrument
- Win32
-
-
- PGInstrument
- x64
-
-
- PGUpdate
- ARM
-
-
- PGUpdate
- ARM64
-
-
- PGUpdate
- Win32
-
-
- PGUpdate
- x64
-
-
- Release
- ARM
-
-
- Release
- ARM64
-
-
- Release
- Win32
-
-
- Release
- x64
-
-
-
- {0F6EE4A4-C75F-4578-B4B3-2D64F4B9B782}
- pyshellext
- pyshellext
- false
-
-
-
-
- DynamicLibrary
- Unicode
-
-
-
-
-
- ClCompile
-
-
-
-
-
-
-
-
- _CONSOLE;%(PreprocessorDefinitions)
-
-
- version.lib;shlwapi.lib;%(AdditionalDependencies)
- Console
- ..\PC\pyshellext.def
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/PCbuild/pyshellext.vcxproj.filters b/PCbuild/pyshellext.vcxproj.filters
deleted file mode 100644
index 77cd3060857a80b..000000000000000
--- a/PCbuild/pyshellext.vcxproj.filters
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
-
-
-
-
- Source Files
-
-
-
-
- Resource Files
-
-
-
-
- Source Files
-
-
-
\ No newline at end of file
diff --git a/PCbuild/pywlauncher.vcxproj b/PCbuild/pywlauncher.vcxproj
deleted file mode 100644
index 1694548935a1653..000000000000000
--- a/PCbuild/pywlauncher.vcxproj
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
-
- Debug
- ARM
-
-
- Debug
- ARM64
-
-
- Debug
- Win32
-
-
- Debug
- x64
-
-
- PGInstrument
- ARM
-
-
- PGInstrument
- ARM64
-
-
- PGInstrument
- Win32
-
-
- PGInstrument
- x64
-
-
- PGUpdate
- ARM
-
-
- PGUpdate
- ARM64
-
-
- PGUpdate
- Win32
-
-
- PGUpdate
- x64
-
-
- Release
- ARM
-
-
- Release
- ARM64
-
-
- Release
- Win32
-
-
- Release
- x64
-
-
-
- {1D4B18D3-7C12-4ECB-9179-8531FF876CE6}
- pywlauncher
- pyw
- false
-
-
-
-
- Application
- Unicode
-
-
-
-
-
- ClCompile
-
-
-
-
-
-
-
-
- _WINDOWS;%(PreprocessorDefinitions)
- MultiThreaded
-
-
- shell32.lib;pathcch.lib;%(AdditionalDependencies)
- Windows
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/PCbuild/pywlauncher.vcxproj.filters b/PCbuild/pywlauncher.vcxproj.filters
deleted file mode 100644
index f10f6d5669e283c..000000000000000
--- a/PCbuild/pywlauncher.vcxproj.filters
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
-
-
-
-
- Source Files
-
-
-
-
- Resource Files
-
-
-
-
- Resource Files
-
-
-
\ No newline at end of file
diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt
index 7c5eab2eb75a1de..e9fa9680c368ad1 100644
--- a/PCbuild/readme.txt
+++ b/PCbuild/readme.txt
@@ -23,7 +23,7 @@ external dependencies. To build, simply run the "build.bat" script without
any arguments. After this succeeds, you can open the "pcbuild.sln"
solution in Visual Studio to continue development.
-To build an installer package, refer to the README in the Tools/msi folder.
+To build an installer package, refer to PC/layout.
The solution currently supports two platforms. The Win32 platform is
used to build standard x86-compatible 32-bit binaries, output into the
@@ -140,12 +140,6 @@ CPython in different ways:
pythonw
pythonw.exe, a variant of python.exe that doesn't open a Command
Prompt window
-pylauncher
- py.exe, the Python Launcher for Windows, see
- https://docs.python.org/3/using/windows.html#launcher
-pywlauncher
- pyw.exe, a variant of py.exe that doesn't open a Command Prompt
- window
_testembed
_testembed.exe, a small program that embeds Python for testing
purposes, used by test_capi.py
@@ -156,8 +150,6 @@ _freeze_module
_freeze_module.exe, used to regenerate frozen modules in Python
after changes have been made to the corresponding source files
(e.g. Lib\importlib\_bootstrap.py).
-pyshellext
- pyshellext.dll, the shell extension deployed with the launcher
python3dll
python3.dll, the PEP 384 Stable ABI dll
(not installed on free-threaded builds)
@@ -420,8 +412,6 @@ _testclinic_limited extension, the file Modules/_testclinic_limited.c:
* Save and exit Visual Studio.
* Add `;_testclinic_limited` to `` in
PCbuild\pcbuild.proj.
-* Update "exts" in Tools\msi\lib\lib_files.wxs file or in
- Tools\msi\test\test_files.wxs file (for tests).
* PC\layout\main.py needs updating if you add a test-only extension whose name
doesn't start with "_test".
* Add the extension to PCbuild\readme.txt (this file).
diff --git a/PCbuild/venvlauncher.vcxproj b/PCbuild/venvlauncher.vcxproj
index a2e8ffa82b10eb7..7afe923406b1d18 100644
--- a/PCbuild/venvlauncher.vcxproj
+++ b/PCbuild/venvlauncher.vcxproj
@@ -110,10 +110,7 @@
-
-
-
-
+
diff --git a/PCbuild/venvlauncher.vcxproj.filters b/PCbuild/venvlauncher.vcxproj.filters
index bc98687a6da0d3e..62839fd6b8e863f 100644
--- a/PCbuild/venvlauncher.vcxproj.filters
+++ b/PCbuild/venvlauncher.vcxproj.filters
@@ -9,12 +9,7 @@
-
- Resource Files
-
-
-
-
+
Resource Files
diff --git a/PCbuild/venvwlauncher.vcxproj b/PCbuild/venvwlauncher.vcxproj
index f2aaf83fe2b3785..3aa0bb2cdefeaa8 100644
--- a/PCbuild/venvwlauncher.vcxproj
+++ b/PCbuild/venvwlauncher.vcxproj
@@ -110,10 +110,7 @@
-
-
-
-
+
diff --git a/PCbuild/venvwlauncher.vcxproj.filters b/PCbuild/venvwlauncher.vcxproj.filters
index 5193c38b12d53d5..babded59633f1f1 100644
--- a/PCbuild/venvwlauncher.vcxproj.filters
+++ b/PCbuild/venvwlauncher.vcxproj.filters
@@ -14,12 +14,7 @@
-
- Resource Files
-
-
-
-
+
Resource Files
diff --git a/README.rst b/README.rst
index 48f86cdb86ed1d2..8505ec0a1e89dc7 100644
--- a/README.rst
+++ b/README.rst
@@ -76,8 +76,8 @@ to macOS framework and universal builds. Refer to `Mac/README.rst
On Windows, see `PCbuild/readme.txt
`_.
-To build Windows installer, see `Tools/msi/README.txt
-`_.
+To build Windows packages, see `PC/layout/README.md
+`_.
If you wish, you can create a subdirectory and invoke configure from there.
For example::
diff --git a/Tools/README b/Tools/README
index 90acb2614820ee4..3984bce6b9b6768 100644
--- a/Tools/README
+++ b/Tools/README
@@ -34,10 +34,6 @@ jit Tooling for building the JIT.
lockbench Benchmarks for PyMutex and critical sections.
-msi Support for packaging Python as an MSI package on Windows.
-
-nuget Files for the NuGet package manager for .NET.
-
patchcheck Tools for checking and applying patches to the Python source code
and verifying the integrity of patch files.
diff --git a/Tools/build/compute-changes.py b/Tools/build/compute-changes.py
index 53d7b8fe32f89e6..4d491cc79ad0c8e 100644
--- a/Tools/build/compute-changes.py
+++ b/Tools/build/compute-changes.py
@@ -117,7 +117,6 @@ class Outputs:
run_tests: bool = False
run_ubuntu: bool = False
run_wasi: bool = False
- run_windows_msi: bool = False
run_windows_tests: bool = False
@@ -160,9 +159,6 @@ def compute_changes() -> None:
if outputs.run_docs:
print("Build documentation")
- if outputs.run_windows_msi:
- print("Build Windows MSI")
-
print(outputs)
write_github_output(outputs)
@@ -223,7 +219,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
run_ci_fuzz_stdlib = False
run_docs = False
run_windows_tests = False
- run_windows_msi = False
platforms_changed = set()
has_platform_specific_change = True
@@ -245,9 +240,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
run_tests = True
run_windows_tests = True
continue
- if file.name == "reusable-windows-msi.yml":
- run_windows_msi = True
- continue
if file.name == "reusable-macos.yml":
run_tests = True
platforms_changed.add("macos")
@@ -288,10 +280,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
if doc_file:
run_docs = True
- # Check for changed MSI installer-related files
- if file.parts[:2] == ("Tools", "msi"):
- run_windows_msi = True
-
# Check which platform specific tests to run
if run_tests:
if not has_platform_specific_change or not platforms_changed:
@@ -327,7 +315,6 @@ def process_changed_files(changed_files: Set[Path]) -> Outputs:
run_tests=run_tests,
run_ubuntu=run_ubuntu,
run_wasi=run_wasi,
- run_windows_msi=run_windows_msi,
run_windows_tests=run_windows_tests,
)
@@ -342,7 +329,6 @@ def process_target_branch(outputs: Outputs, git_branch: str) -> Outputs:
if os.environ.get("GITHUB_EVENT_NAME", "").lower() == "workflow_dispatch":
outputs.run_docs = True
- outputs.run_windows_msi = True
return outputs
diff --git a/Tools/buildbot/buildmsi.bat b/Tools/buildbot/buildmsi.bat
index 6804d794799509b..b0e196df2649be6 100644
--- a/Tools/buildbot/buildmsi.bat
+++ b/Tools/buildbot/buildmsi.bat
@@ -3,7 +3,9 @@ setlocal
pushd
-@rem build both snapshot MSIs
-call "%~dp0..\msi\build.bat" -x86 -x64
+@rem The MSI project is gone, so instead we will just
+@rem build and then do a default layout.
+call "%~dp0..\..\PCbuild\build.bat" -e -k -v %*
+call "%~dp0..\..\python.exe" "%~dp0..\..\PC\layout" --preset-default -o "%~dp0..\..\PCbuild\output"
popd
\ No newline at end of file
diff --git a/Tools/msi/README.txt b/Tools/msi/README.txt
deleted file mode 100644
index 4b3de9e82f6f1ac..000000000000000
--- a/Tools/msi/README.txt
+++ /dev/null
@@ -1,530 +0,0 @@
-Quick Build Info
-================
-
-For testing, the installer should be built with the Tools/msi/build.bat
-script:
-
- build.bat [-x86] [-x64] [-ARM64] [--doc]
-
-For an official release, the installer should be built with the
-Tools/msi/buildrelease.bat script and environment variables:
-
- set PYTHON=
- set SPHINXBUILD=
- set PATH=;%PATH%
-
- buildrelease.bat [-x86] [-x64] [-ARM64] [-D] [-B]
- [-o