[3.13] Backport PyManager support to PC/layout script (GH-135096)

This commit is contained in:
Steve Dower 2025-06-03 21:21:11 +01:00 committed by GitHub
parent b21d15fa95
commit b679b74132
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 316 additions and 2 deletions

View file

@ -1,7 +1,7 @@
import sys
try:
import layout
import layout # noqa: F401
except ImportError:
# Failed to import our package, which likely means we were started directly
# Add the additional search path needed to locate our module.

View file

@ -8,6 +8,7 @@ __author__ = "Steve Dower <steve.dower@python.org>"
__version__ = "3.8"
import argparse
import json
import os
import shutil
import sys
@ -28,6 +29,7 @@ from .support.logging import *
from .support.options import *
from .support.pip import *
from .support.props import *
from .support.pymanager import *
from .support.nuspec import *
TEST_PYDS_ONLY = FileStemSet("xxlimited", "xxlimited_35", "_ctypes_test", "_test*")
@ -265,7 +267,12 @@ def get_layout(ns):
if ns.include_dev:
for dest, src in rglob(ns.source / "Include", "**/*.h"):
yield "include/{}".format(dest), src
yield "include/pyconfig.h", ns.build / "pyconfig.h"
# Support for layout of new and old releases.
pc = ns.source / "PC"
if (pc / "pyconfig.h.in").is_file():
yield "include/pyconfig.h", ns.build / "pyconfig.h"
else:
yield "include/pyconfig.h", pc / "pyconfig.h"
for dest, src in get_tcltk_lib(ns):
yield dest, src
@ -303,6 +310,9 @@ def get_layout(ns):
else:
yield "DLLs/{}".format(ns.include_cat.name), ns.include_cat
if ns.include_install_json or ns.include_install_embed_json or ns.include_install_test_json:
yield "__install__.json", ns.temp / "__install__.json"
def _compile_one_py(src, dest, name, optimize, checked=True):
import py_compile
@ -394,6 +404,22 @@ def generate_source_files(ns):
log_info("Extracting pip")
extract_pip_files(ns)
if ns.include_install_json:
log_info("Generating __install__.json in {}", ns.temp)
ns.temp.mkdir(parents=True, exist_ok=True)
with open(ns.temp / "__install__.json", "w", encoding="utf-8") as f:
json.dump(calculate_install_json(ns), f, indent=2)
elif ns.include_install_embed_json:
log_info("Generating embeddable __install__.json in {}", ns.temp)
ns.temp.mkdir(parents=True, exist_ok=True)
with open(ns.temp / "__install__.json", "w", encoding="utf-8") as f:
json.dump(calculate_install_json(ns, for_embed=True), f, indent=2)
elif ns.include_install_test_json:
log_info("Generating test __install__.json in {}", ns.temp)
ns.temp.mkdir(parents=True, exist_ok=True)
with open(ns.temp / "__install__.json", "w", encoding="utf-8") as f:
json.dump(calculate_install_json(ns, for_test=True), f, indent=2)
def _create_zip_file(ns):
if not ns.zip:
@ -627,6 +653,7 @@ def main():
if ns.include_cat and not ns.include_cat.is_absolute():
ns.include_cat = (Path.cwd() / ns.include_cat).resolve()
if not ns.arch:
# TODO: Calculate arch from files in ns.build instead
if sys.winver.endswith("-arm64"):
ns.arch = "arm64"
elif sys.winver.endswith("-32"):

View file

@ -36,6 +36,9 @@ OPTIONS = {
"alias": {"help": "aliased python.exe entry-point binaries"},
"alias3": {"help": "aliased python3.exe entry-point binaries"},
"alias3x": {"help": "aliased python3.x.exe entry-point binaries"},
"install-json": {"help": "a PyManager __install__.json file"},
"install-embed-json": {"help": "a PyManager __install__.json file for embeddable distro"},
"install-test-json": {"help": "a PyManager __install__.json for the test distro"},
}
@ -95,6 +98,34 @@ PRESETS = {
"precompile",
],
},
"pymanager": {
"help": "PyManager package",
"options": [
"stable",
"pip",
"tcltk",
"idle",
"venv",
"dev",
"html-doc",
"install-json",
],
},
"pymanager-test": {
"help": "PyManager test package",
"options": [
"stable",
"pip",
"tcltk",
"idle",
"venv",
"dev",
"html-doc",
"symbols",
"tests",
"install-test-json",
],
},
}

View file

@ -0,0 +1,256 @@
from .constants import *
URL_BASE = "https://www.python.org/ftp/python/"
XYZ_VERSION = f"{VER_MAJOR}.{VER_MINOR}.{VER_MICRO}"
WIN32_VERSION = f"{VER_MAJOR}.{VER_MINOR}.{VER_MICRO}.{VER_FIELD4}"
FULL_VERSION = f"{VER_MAJOR}.{VER_MINOR}.{VER_MICRO}{VER_SUFFIX}"
def _not_empty(n, key=None):
result = []
for i in n:
if key:
i_l = i[key]
else:
i_l = i
if not i_l:
continue
result.append(i)
return result
def calculate_install_json(ns, *, for_embed=False, for_test=False):
TARGET = "python.exe"
TARGETW = "pythonw.exe"
SYS_ARCH = {
"win32": "32bit",
"amd64": "64bit",
"arm64": "64bit", # Unfortunate, but this is how it's spec'd
}[ns.arch]
TAG_ARCH = {
"win32": "-32",
"amd64": "-64",
"arm64": "-arm64",
}[ns.arch]
COMPANY = "PythonCore"
DISPLAY_NAME = "Python"
TAG_SUFFIX = ""
ALIAS_PREFIX = "python"
ALIAS_WPREFIX = "pythonw"
FILE_PREFIX = "python-"
FILE_SUFFIX = f"-{ns.arch}"
DISPLAY_TAGS = [{
"win32": "32-bit",
"amd64": "",
"arm64": "ARM64",
}[ns.arch]]
if for_test:
# Packages with the test suite come under a different Company
COMPANY = "PythonTest"
DISPLAY_TAGS.append("with tests")
FILE_SUFFIX = f"-test-{ns.arch}"
if for_embed:
# Embeddable distro comes under a different Company
COMPANY = "PythonEmbed"
TARGETW = None
ALIAS_PREFIX = None
ALIAS_WPREFIX = None
DISPLAY_TAGS.append("embeddable")
# Deliberately name the file differently from the existing distro
# so we can republish old versions without replacing files.
FILE_SUFFIX = f"-embeddable-{ns.arch}"
if ns.include_freethreaded:
# Free-threaded distro comes with a tag suffix
TAG_SUFFIX = "t"
TARGET = f"python{VER_MAJOR}.{VER_MINOR}t.exe"
TARGETW = f"pythonw{VER_MAJOR}.{VER_MINOR}t.exe"
DISPLAY_TAGS.append("free-threaded")
FILE_SUFFIX = f"t-{ns.arch}"
FULL_TAG = f"{VER_MAJOR}.{VER_MINOR}.{VER_MICRO}{VER_SUFFIX}{TAG_SUFFIX}"
FULL_ARCH_TAG = f"{FULL_TAG}{TAG_ARCH}"
XY_TAG = f"{VER_MAJOR}.{VER_MINOR}{TAG_SUFFIX}"
XY_ARCH_TAG = f"{XY_TAG}{TAG_ARCH}"
X_TAG = f"{VER_MAJOR}{TAG_SUFFIX}"
X_ARCH_TAG = f"{X_TAG}{TAG_ARCH}"
# Tag used in runtime ID (for side-by-side install/updates)
ID_TAG = XY_ARCH_TAG
# Tag shown in 'py list' output
DISPLAY_TAG = f"{XY_TAG}-dev{TAG_ARCH}" if VER_SUFFIX else XY_ARCH_TAG
# Tag used for PEP 514 registration
SYS_WINVER = XY_TAG + (TAG_ARCH if TAG_ARCH != '-64' else '')
DISPLAY_SUFFIX = ", ".join(i for i in DISPLAY_TAGS if i)
if DISPLAY_SUFFIX:
DISPLAY_SUFFIX = f" ({DISPLAY_SUFFIX})"
DISPLAY_VERSION = f"{XYZ_VERSION}{VER_SUFFIX}{DISPLAY_SUFFIX}"
STD_RUN_FOR = []
STD_ALIAS = []
STD_PEP514 = []
STD_START = []
STD_UNINSTALL = []
# The list of 'py install <TAG>' tags that will match this runtime.
# Architecture should always be included here because PyManager will add it.
INSTALL_TAGS = [
FULL_ARCH_TAG,
XY_ARCH_TAG,
X_ARCH_TAG,
# X_TAG and XY_TAG doesn't include VER_SUFFIX, so create -dev versions
f"{XY_TAG}-dev{TAG_ARCH}" if XY_TAG and VER_SUFFIX else "",
f"{X_TAG}-dev{TAG_ARCH}" if X_TAG and VER_SUFFIX else "",
]
# Generate run-for entries for each target.
# Again, include architecture because PyManager will add it.
for base in [
{"target": TARGET},
{"target": TARGETW, "windowed": 1},
]:
if not base["target"]:
continue
STD_RUN_FOR.append({**base, "tag": FULL_ARCH_TAG})
if XY_TAG:
STD_RUN_FOR.append({**base, "tag": XY_ARCH_TAG})
if X_TAG:
STD_RUN_FOR.append({**base, "tag": X_ARCH_TAG})
if VER_SUFFIX:
STD_RUN_FOR.extend([
{**base, "tag": f"{XY_TAG}-dev{TAG_ARCH}" if XY_TAG else ""},
{**base, "tag": f"{X_TAG}-dev{TAG_ARCH}" if X_TAG else ""},
])
# Generate alias entries for each target. We need both arch and non-arch
# versions as well as windowed/non-windowed versions to make sure that all
# necessary aliases are created.
for prefix, base in (
(ALIAS_PREFIX, {"target": TARGET}),
(ALIAS_WPREFIX, {"target": TARGETW, "windowed": 1}),
):
if not prefix:
continue
if not base["target"]:
continue
if XY_TAG:
STD_ALIAS.extend([
{**base, "name": f"{prefix}{XY_TAG}.exe"},
{**base, "name": f"{prefix}{XY_ARCH_TAG}.exe"},
])
if X_TAG:
STD_ALIAS.extend([
{**base, "name": f"{prefix}{X_TAG}.exe"},
{**base, "name": f"{prefix}{X_ARCH_TAG}.exe"},
])
if SYS_WINVER:
STD_PEP514.append({
"kind": "pep514",
"Key": rf"{COMPANY}\{SYS_WINVER}",
"DisplayName": f"{DISPLAY_NAME} {DISPLAY_VERSION}",
"SupportUrl": "https://www.python.org/",
"SysArchitecture": SYS_ARCH,
"SysVersion": VER_DOT,
"Version": FULL_VERSION,
"InstallPath": {
"_": "%PREFIX%",
"ExecutablePath": f"%PREFIX%{TARGET}",
# WindowedExecutablePath is added below
},
"Help": {
"Online Python Documentation": {
"_": f"https://docs.python.org/{VER_DOT}/"
},
},
})
STD_START.append({
"kind": "start",
"Name": f"{DISPLAY_NAME} {VER_DOT}{DISPLAY_SUFFIX}",
"Items": [
{
"Name": f"{DISPLAY_NAME} {VER_DOT}{DISPLAY_SUFFIX}",
"Target": f"%PREFIX%{TARGET}",
"Icon": f"%PREFIX%{TARGET}",
},
{
"Name": f"{DISPLAY_NAME} {VER_DOT} Online Documentation",
"Icon": r"%SystemRoot%\System32\SHELL32.dll",
"IconIndex": 13,
"Target": f"https://docs.python.org/{VER_DOT}/",
},
# IDLE and local documentation items are added below
],
})
if TARGETW and STD_PEP514:
STD_PEP514[0]["InstallPath"]["WindowedExecutablePath"] = f"%PREFIX%{TARGETW}"
if ns.include_idle:
STD_START[0]["Items"].append({
"Name": f"IDLE (Python {VER_DOT}{DISPLAY_SUFFIX})",
"Target": f"%PREFIX%{TARGETW or TARGET}",
"Arguments": r'"%PREFIX%Lib\idlelib\idle.pyw"',
"Icon": r"%PREFIX%Lib\idlelib\Icons\idle.ico",
"IconIndex": 0,
})
STD_START[0]["Items"].append({
"Name": f"PyDoc (Python {VER_DOT}{DISPLAY_SUFFIX})",
"Target": f"%PREFIX%{TARGET}",
"Arguments": "-m pydoc -b",
"Icon": r"%PREFIX%Lib\idlelib\Icons\idle.ico",
"IconIndex": 0,
})
if STD_PEP514:
STD_PEP514[0]["InstallPath"]["IdlePath"] = f"%PREFIX%Lib\\idlelib\\idle.pyw"
if ns.include_html_doc:
STD_PEP514[0]["Help"]["Main Python Documentation"] = {
"_": rf"%PREFIX%Doc\html\index.html",
}
STD_START[0]["Items"].append({
"Name": f"{DISPLAY_NAME} {VER_DOT} Manuals{DISPLAY_SUFFIX}",
"Target": r"%PREFIX%Doc\html\index.html",
})
elif ns.include_chm:
STD_PEP514[0]["Help"]["Main Python Documentation"] = {
"_": rf"%PREFIX%Doc\{PYTHON_CHM_NAME}",
}
STD_START[0]["Items"].append({
"Name": f"{DISPLAY_NAME} {VER_DOT} Manuals{DISPLAY_SUFFIX}",
"Target": "%WINDIR%hhc.exe",
"Arguments": rf"%PREFIX%Doc\{PYTHON_CHM_NAME}",
})
STD_UNINSTALL.append({
"kind": "uninstall",
# Other settings will pick up sensible defaults
"Publisher": "Python Software Foundation",
"HelpLink": f"https://docs.python.org/{VER_DOT}/",
})
data = {
"schema": 1,
"id": f"{COMPANY.lower()}-{ID_TAG}",
"sort-version": FULL_VERSION,
"company": COMPANY,
"tag": DISPLAY_TAG,
"install-for": _not_empty(INSTALL_TAGS),
"run-for": _not_empty(STD_RUN_FOR, "tag"),
"alias": _not_empty(STD_ALIAS, "name"),
"shortcuts": [
*STD_PEP514,
*STD_START,
*STD_UNINSTALL,
],
"display-name": f"{DISPLAY_NAME} {DISPLAY_VERSION}",
"executable": rf".\{TARGET}",
"url": f"{URL_BASE}{XYZ_VERSION}/{FILE_PREFIX}{FULL_VERSION}{FILE_SUFFIX}.zip"
}
return data