]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-138489: Add build-details.json generation to PC/layout (GH-149153)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 15 May 2026 13:19:39 +0000 (15:19 +0200)
committerGitHub <noreply@github.com>
Fri, 15 May 2026 13:19:39 +0000 (13:19 +0000)
(cherry picked from commit 4aa296f9c4a85a7badc09bf7ca6ede36cd8cd14c)

Co-authored-by: Steve Dower <steve.dower@python.org>
Misc/NEWS.d/next/Windows/2026-04-29-14-44-51.gh-issue-138489.234aj6.rst [new file with mode: 0644]
PC/layout/main.py
PC/layout/support/builddetails.py [new file with mode: 0644]
PC/layout/support/constants.py
PC/layout/support/options.py

diff --git a/Misc/NEWS.d/next/Windows/2026-04-29-14-44-51.gh-issue-138489.234aj6.rst b/Misc/NEWS.d/next/Windows/2026-04-29-14-44-51.gh-issue-138489.234aj6.rst
new file mode 100644 (file)
index 0000000..4afb8f7
--- /dev/null
@@ -0,0 +1,4 @@
+Windows distributions now include a :file:`build-details.json` file (see
+:pep:`739`). The legacy installer does not install it, but all other
+distributions from python.org and all preset configurations in the
+``PC\layout`` script will include one.
index 3a62ea91420c9e2020e4ac464be7a0c72cb4d796..f70a26b2b296591b20cc9e022aff626ad16f755b 100644 (file)
@@ -22,6 +22,7 @@ if __name__ == "__main__":
     __path__ = [str(Path(__file__).resolve().parent)]
 
 from .support.appxmanifest import *
+from .support.builddetails import *
 from .support.catalog import *
 from .support.constants import *
 from .support.filesets import *
@@ -317,6 +318,9 @@ def get_layout(ns):
     for dest, src in get_appx_layout(ns):
         yield dest, src
 
+    for dest, src in get_builddetails(ns):
+        yield dest, src
+
     if ns.include_cat:
         if ns.flat_dlls:
             yield ns.include_cat.name, ns.include_cat
diff --git a/PC/layout/support/builddetails.py b/PC/layout/support/builddetails.py
new file mode 100644 (file)
index 0000000..6ef860e
--- /dev/null
@@ -0,0 +1,119 @@
+import io
+import json
+from . import constants
+
+_LEVELS = {
+    0xA0: "alpha",
+    0xB0: "beta",
+    0xC0: "candidate",
+    0xF0: "final",
+}
+
+
+_TEMPLATE = {
+    "schema_version": "1.0",
+    "base_prefix": ".",
+    "base_interpreter": "python.exe",
+    "platform": None,   # Set later
+    "language": {
+        "version": f"{constants.VER_MAJOR}.{constants.VER_MINOR}",
+        "version_info": {
+            "major": constants.VER_MAJOR,
+            "minor": constants.VER_MINOR,
+            "micro": constants.VER_MICRO,
+            "releaselevel": _LEVELS.get(constants.VER_FIELD4 & 0xF0, "final"),
+            "serial": constants.VER_FIELD4 & 0x0F,
+        },
+    },
+    "implementation": {
+        "name": "cpython",
+        "cache_tag": f"cpython-{constants.VER_MAJOR}{constants.VER_MINOR}",
+        "version": {
+            "major": constants.VER_MAJOR,
+            "minor": constants.VER_MINOR,
+            "micro": constants.VER_MICRO,
+            "releaselevel": _LEVELS.get(constants.VER_FIELD4 & 0xF0, "final"),
+            "serial": constants.VER_FIELD4 & 0x0F,
+        },
+        "hexversion": constants.VER_HEXVERSION,
+    },
+    "abi": {
+        "flags": [],
+        "extension_suffix": ".pyd",
+        "stable_abi_suffix": ".pyd",
+    },
+    "suffixes": {
+        "source": [".py", ".pyw"],
+        "bytecode": [".pyc"],
+        "extensions": [".pyd"],
+    },
+    "libpython": {
+        "dynamic": constants.PYTHON_DLL_NAME,
+        "dynamic_stableabi": constants.PYTHON_STABLE_DLL_NAME,
+        "link_extensions": True,
+    },
+    "c_api": {
+    },
+}
+
+
+def _with_d(path):
+    pre, sep, post = path.partition(".")
+    return pre + "_d" + sep + post
+
+
+def _add_d(data, *args):
+    for a in args[:-1]:
+        data = data[a]
+    a = args[-1]
+    v = data[a]
+    if isinstance(v, list):
+        data[a] = [_with_d(i) for i in data[a]]
+    else:
+        data[a] = _with_d(data[a])
+
+
+def get_builddetails(ns):
+    if not ns.include_builddetails_json:
+        return
+
+    details = dict(_TEMPLATE)
+
+    plat = {
+        "win32": "win32",
+        "amd64": "win-amd64",
+        "arm64": "win-arm64",
+    }.get(ns.arch, ns.arch)
+
+    pyd_abi_flags = ""
+    if ns.include_freethreaded:
+        details["abi"]["flags"].append("t")
+        pyd_abi_flags += "t"
+    if ns.debug:
+        details["abi"]["flags"].append("d")
+
+    norm_plat = plat.replace("-", "_")
+    ext_suffix = f".cp{constants.VER_MAJOR}{constants.VER_MINOR}{pyd_abi_flags}-{norm_plat}.pyd"
+    details["abi"]["extension_suffix"] = ext_suffix
+    details["suffixes"]["extensions"].insert(0, ext_suffix)
+
+    details["platform"] = plat
+
+    if ns.include_dev:
+        details["c_api"]["headers"] = "Include"
+
+    if ns.include_freethreaded:
+        details["libpython"]["dynamic"] = constants.FREETHREADED_PYTHON_DLL_NAME
+        details["libpython"]["dynamic_stableabi"] = constants.FREETHREADED_PYTHON_STABLE_DLL_NAME
+
+    if ns.debug:
+        _add_d(details, "base_interpreter")
+        _add_d(details, "abi", "stable_abi_suffix")
+        _add_d(details, "abi", "extension_suffix")
+        _add_d(details, "suffixes", "extensions")
+        _add_d(details, "libpython", "dynamic")
+        _add_d(details, "libpython", "dynamic_stableabi")
+
+    buffer = io.StringIO()
+    json.dump(details, buffer, indent=2)
+    yield "build-details.json", ("build-details.json", buffer.getvalue().encode())
index 6b8c915e519743f015df2bb538ae45020fd347b5..cb16f534685c8f8d422b48433cbe2d68aceb15ab 100644 (file)
@@ -23,6 +23,14 @@ def _unpack_hexversion():
             return _read_patchlevel_version(pathlib.Path(os.getenv("PYTHONINCLUDE")))
         except OSError:
             pass
+    # Manual search for a '-s <source dir>` arument
+    try:
+        src = sys.argv[sys.argv.index("-s") + 1]
+        return _read_patchlevel_version(pathlib.Path(src) / "Include")
+    except (IndexError, ValueError):
+        pass
+    except OSError:
+        pass
     return struct.pack(">i", sys.hexversion)
 
 
@@ -68,6 +76,7 @@ def check_patchlevel_version(sources):
 
 
 VER_MAJOR, VER_MINOR, VER_MICRO, VER_FIELD4 = _unpack_hexversion()
+VER_HEXVERSION = (VER_MAJOR << 24) | (VER_MINOR << 16) | (VER_MICRO << 8) | (VER_FIELD4)
 VER_SUFFIX = _get_suffix(VER_FIELD4)
 VER_FIELD3 = VER_MICRO << 8 | VER_FIELD4
 VER_DOT = "{}.{}".format(VER_MAJOR, VER_MINOR)
index e8c393385425e72e91881e1f4b527ef9b6e84d3f..3a6e00f720f01ffb5b296ec4912b20b4a89b383a 100644 (file)
@@ -39,6 +39,7 @@ OPTIONS = {
     "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"},
+    "builddetails-json": {"help": "a PEP 739 build-details.json"},
 }
 
 
@@ -69,6 +70,7 @@ PRESETS = {
             "props",
             "nuspec",
             "alias",
+            "builddetails-json",
         ],
     },
     "iot": {"help": "Windows IoT Core", "options": ["alias", "stable", "pip"]},
@@ -85,6 +87,7 @@ PRESETS = {
             "symbols",
             "html-doc",
             "alias",
+            "builddetails-json",
         ],
     },
     "embed": {
@@ -96,6 +99,7 @@ PRESETS = {
             "flat-dlls",
             "underpth",
             "precompile",
+            "builddetails-json",
         ],
     },
     "pymanager": {
@@ -109,6 +113,7 @@ PRESETS = {
             "dev",
             "html-doc",
             "install-json",
+            "builddetails-json",
         ],
     },
     "pymanager-test": {
@@ -124,6 +129,7 @@ PRESETS = {
             "symbols",
             "tests",
             "install-test-json",
+            "builddetails-json",
         ],
     },
 }