https://github.com/python/cpython/commit/4aa296f9c4a85a7badc09bf7ca6ede36cd8cd14c
commit: 4aa296f9c4a85a7badc09bf7ca6ede36cd8cd14c
branch: main
author: Steve Dower <[email protected]>
committer: zooba <[email protected]>
date: 2026-05-15T13:51:27+01:00
summary:
gh-138489: Add build-details.json generation to PC/layout (GH-149153)
files:
A Misc/NEWS.d/next/Windows/2026-04-29-14-44-51.gh-issue-138489.234aj6.rst
A PC/layout/support/builddetails.py
M PC/layout/main.py
M PC/layout/support/constants.py
M 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
index 00000000000000..4afb8f737b692e
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2026-04-29-14-44-51.gh-issue-138489.234aj6.rst
@@ -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.
diff --git a/PC/layout/main.py b/PC/layout/main.py
index 3a62ea91420c9e..f70a26b2b29659 100644
--- a/PC/layout/main.py
+++ b/PC/layout/main.py
@@ -22,6 +22,7 @@
__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 _c(d):
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
index 00000000000000..6ef860eeb04354
--- /dev/null
+++ b/PC/layout/support/builddetails.py
@@ -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())
diff --git a/PC/layout/support/constants.py b/PC/layout/support/constants.py
index 6b8c915e519743..cb16f534685c8f 100644
--- a/PC/layout/support/constants.py
+++ b/PC/layout/support/constants.py
@@ -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)
diff --git a/PC/layout/support/options.py b/PC/layout/support/options.py
index e8c393385425e7..3a6e00f720f01f 100644
--- a/PC/layout/support/options.py
+++ b/PC/layout/support/options.py
@@ -39,6 +39,7 @@ def public(f):
"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 @@ def public(f):
"props",
"nuspec",
"alias",
+ "builddetails-json",
],
},
"iot": {"help": "Windows IoT Core", "options": ["alias", "stable", "pip"]},
@@ -85,6 +87,7 @@ def public(f):
"symbols",
"html-doc",
"alias",
+ "builddetails-json",
],
},
"embed": {
@@ -96,6 +99,7 @@ def public(f):
"flat-dlls",
"underpth",
"precompile",
+ "builddetails-json",
],
},
"pymanager": {
@@ -109,6 +113,7 @@ def public(f):
"dev",
"html-doc",
"install-json",
+ "builddetails-json",
],
},
"pymanager-test": {
@@ -124,6 +129,7 @@ def public(f):
"symbols",
"tests",
"install-test-json",
+ "builddetails-json",
],
},
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]