GH-121970: Modernise the patchlevel extension (#121995)

This commit is contained in:
Adam Turner 2024-07-20 14:44:43 +01:00 committed by GitHub
parent 3de092b82f
commit b7ad711fcb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 66 additions and 58 deletions

View file

@ -6,7 +6,6 @@ extend-exclude = [
"includes/*", "includes/*",
# Temporary exclusions: # Temporary exclusions:
"tools/extensions/escape4chm.py", "tools/extensions/escape4chm.py",
"tools/extensions/patchlevel.py",
"tools/extensions/pyspecific.py", "tools/extensions/pyspecific.py",
] ]

View file

@ -6,6 +6,7 @@
# The contents of this file are pickled, so don't put values in the namespace # The contents of this file are pickled, so don't put values in the namespace
# that aren't pickleable (module imports are okay, they're removed automatically). # that aren't pickleable (module imports are okay, they're removed automatically).
import importlib
import os import os
import sys import sys
import time import time
@ -64,9 +65,8 @@ copyright = f"2001-{time.strftime('%Y')}, Python Software Foundation"
# We look for the Include/patchlevel.h file in the current Python source tree # We look for the Include/patchlevel.h file in the current Python source tree
# and replace the values accordingly. # and replace the values accordingly.
import patchlevel # noqa: E402 # See Doc/tools/extensions/patchlevel.py
version, release = importlib.import_module('patchlevel').get_version_info()
version, release = patchlevel.get_version_info()
rst_epilog = f""" rst_epilog = f"""
.. |python_version_literal| replace:: ``Python {version}`` .. |python_version_literal| replace:: ``Python {version}``

View file

@ -1,68 +1,77 @@
# -*- coding: utf-8 -*- """Extract version information from Include/patchlevel.h."""
"""
patchlevel.py
~~~~~~~~~~~~~
Extract version info from Include/patchlevel.h.
Adapted from Doc/tools/getversioninfo.
:copyright: 2007-2008 by Georg Brandl.
:license: Python license.
"""
from __future__ import print_function
import os
import re import re
import sys import sys
from pathlib import Path
from typing import Literal, NamedTuple
def get_header_version_info(srcdir): CPYTHON_ROOT = Path(
patchlevel_h = os.path.join(srcdir, '..', 'Include', 'patchlevel.h') __file__, # cpython/Doc/tools/extensions/patchlevel.py
"..", # cpython/Doc/tools/extensions
"..", # cpython/Doc/tools
"..", # cpython/Doc
"..", # cpython
).resolve()
PATCHLEVEL_H = CPYTHON_ROOT / "Include" / "patchlevel.h"
# This won't pick out all #defines, but it will pick up the ones we RELEASE_LEVELS = {
# care about. "PY_RELEASE_LEVEL_ALPHA": "alpha",
rx = re.compile(r'\s*#define\s+([a-zA-Z][a-zA-Z_0-9]*)\s+([a-zA-Z_0-9]+)') "PY_RELEASE_LEVEL_BETA": "beta",
"PY_RELEASE_LEVEL_GAMMA": "candidate",
d = {} "PY_RELEASE_LEVEL_FINAL": "final",
with open(patchlevel_h) as f:
for line in f:
m = rx.match(line)
if m is not None:
name, value = m.group(1, 2)
d[name] = value
release = version = '%s.%s' % (d['PY_MAJOR_VERSION'], d['PY_MINOR_VERSION'])
micro = int(d['PY_MICRO_VERSION'])
release += '.' + str(micro)
level = d['PY_RELEASE_LEVEL']
suffixes = {
'PY_RELEASE_LEVEL_ALPHA': 'a',
'PY_RELEASE_LEVEL_BETA': 'b',
'PY_RELEASE_LEVEL_GAMMA': 'rc',
} }
if level != 'PY_RELEASE_LEVEL_FINAL':
release += suffixes[level] + str(int(d['PY_RELEASE_SERIAL']))
return version, release
def get_sys_version_info(): class version_info(NamedTuple): # noqa: N801
major, minor, micro, level, serial = sys.version_info major: int #: Major release number
release = version = '%s.%s' % (major, minor) minor: int #: Minor release number
release += '.%s' % micro micro: int #: Patch release number
if level != 'final': releaselevel: Literal["alpha", "beta", "candidate", "final"]
release += '%s%s' % (level[0], serial) serial: int #: Serial release number
def get_header_version_info() -> version_info:
# Capture PY_ prefixed #defines.
pat = re.compile(r"\s*#define\s+(PY_\w*)\s+(\w+)", re.ASCII)
defines = {}
patchlevel_h = PATCHLEVEL_H.read_text(encoding="utf-8")
for line in patchlevel_h.splitlines():
if (m := pat.match(line)) is not None:
name, value = m.groups()
defines[name] = value
return version_info(
major=int(defines["PY_MAJOR_VERSION"]),
minor=int(defines["PY_MINOR_VERSION"]),
micro=int(defines["PY_MICRO_VERSION"]),
releaselevel=RELEASE_LEVELS[defines["PY_RELEASE_LEVEL"]],
serial=int(defines["PY_RELEASE_SERIAL"]),
)
def format_version_info(info: version_info) -> tuple[str, str]:
version = f"{info.major}.{info.minor}"
release = f"{info.major}.{info.minor}.{info.micro}"
if info.releaselevel != "final":
suffix = {"alpha": "a", "beta": "b", "candidate": "rc"}
release += f"{suffix[info.releaselevel]}{info.serial}"
return version, release return version, release
def get_version_info(): def get_version_info():
try: try:
return get_header_version_info('.') info = get_header_version_info()
except (IOError, OSError): return format_version_info(info)
version, release = get_sys_version_info() except OSError:
print('Can\'t get version info from Include/patchlevel.h, ' \ version, release = format_version_info(sys.version_info)
'using version of this interpreter (%s).' % release, file=sys.stderr) print(
f"Failed to get version info from Include/patchlevel.h, "
f"using version of this interpreter ({release}).",
file=sys.stderr,
)
return version, release return version, release
if __name__ == '__main__':
print(get_header_version_info('.')[1]) if __name__ == "__main__":
print(format_version_info(get_header_version_info())[1])