__version__ = "3.8"
import argparse
+import json
import os
import shutil
import sys
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*")
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
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
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:
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"):
--- /dev/null
+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