• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Extract version information from Include/patchlevel.h."""
2
3import re
4import sys
5from pathlib import Path
6from typing import Literal, NamedTuple
7
8CPYTHON_ROOT = Path(
9    __file__,  # cpython/Doc/tools/extensions/patchlevel.py
10    "..",  # cpython/Doc/tools/extensions
11    "..",  # cpython/Doc/tools
12    "..",  # cpython/Doc
13    "..",  # cpython
14).resolve()
15PATCHLEVEL_H = CPYTHON_ROOT / "Include" / "patchlevel.h"
16
17RELEASE_LEVELS = {
18    "PY_RELEASE_LEVEL_ALPHA": "alpha",
19    "PY_RELEASE_LEVEL_BETA": "beta",
20    "PY_RELEASE_LEVEL_GAMMA": "candidate",
21    "PY_RELEASE_LEVEL_FINAL": "final",
22}
23
24
25class version_info(NamedTuple):  # noqa: N801
26    major: int  #: Major release number
27    minor: int  #: Minor release number
28    micro: int  #: Patch release number
29    releaselevel: Literal["alpha", "beta", "candidate", "final"]
30    serial: int  #: Serial release number
31
32
33def get_header_version_info() -> version_info:
34    # Capture PY_ prefixed #defines.
35    pat = re.compile(r"\s*#define\s+(PY_\w*)\s+(\w+)", re.ASCII)
36
37    defines = {}
38    patchlevel_h = PATCHLEVEL_H.read_text(encoding="utf-8")
39    for line in patchlevel_h.splitlines():
40        if (m := pat.match(line)) is not None:
41            name, value = m.groups()
42            defines[name] = value
43
44    return version_info(
45        major=int(defines["PY_MAJOR_VERSION"]),
46        minor=int(defines["PY_MINOR_VERSION"]),
47        micro=int(defines["PY_MICRO_VERSION"]),
48        releaselevel=RELEASE_LEVELS[defines["PY_RELEASE_LEVEL"]],
49        serial=int(defines["PY_RELEASE_SERIAL"]),
50    )
51
52
53def format_version_info(info: version_info) -> tuple[str, str]:
54    version = f"{info.major}.{info.minor}"
55    release = f"{info.major}.{info.minor}.{info.micro}"
56    if info.releaselevel != "final":
57        suffix = {"alpha": "a", "beta": "b", "candidate": "rc"}
58        release += f"{suffix[info.releaselevel]}{info.serial}"
59    return version, release
60
61
62def get_version_info():
63    try:
64        info = get_header_version_info()
65        return format_version_info(info)
66    except OSError:
67        version, release = format_version_info(sys.version_info)
68        print(
69            f"Failed to get version info from Include/patchlevel.h, "
70            f"using version of this interpreter ({release}).",
71            file=sys.stderr,
72        )
73        return version, release
74
75
76if __name__ == "__main__":
77    short_ver, full_ver = format_version_info(get_header_version_info())
78    if sys.argv[1:2] == ["--short"]:
79        print(short_ver)
80    else:
81        print(full_ver)
82