Skip to content

Commit d6fd2d4

Browse files
committed
Shorter clang version; add tests
1 parent 4281ae9 commit d6fd2d4

2 files changed

Lines changed: 201 additions & 14 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""Tests Tools/build/generate_getbuildinfo.py."""
2+
3+
import locale
4+
import os
5+
import subprocess
6+
import sys
7+
import textwrap
8+
import unittest
9+
from unittest import mock
10+
from test import support
11+
from test.support import os_helper
12+
from test import test_tools
13+
14+
SRC_DIR = os.path.dirname(test_tools.toolsdir)
15+
16+
with test_tools.imports_under_tool('build'):
17+
import generate_getbuildinfo
18+
19+
20+
class TestGetBuildInfo(unittest.TestCase):
21+
def test_get_py_version(self):
22+
with os_helper.change_cwd(SRC_DIR):
23+
PY_VERSION = generate_getbuildinfo.get_py_version()
24+
25+
# "./python -V" writes "Python {PY_VERSION}"
26+
proc = subprocess.run([sys.executable, '-V'],
27+
stdout=subprocess.PIPE,
28+
text=True, check=True)
29+
python_version = proc.stdout.rstrip()
30+
self.assertEqual(f'Python {PY_VERSION}', python_version)
31+
32+
def test_get_date_time(self):
33+
# Test get_date_time() with SOURCE_DATE_EPOCH env var
34+
def check(timestamp, date, time):
35+
with os_helper.EnvironmentVarGuard() as env:
36+
env['SOURCE_DATE_EPOCH'] = str(timestamp)
37+
result = generate_getbuildinfo.get_date_time()
38+
39+
self.assertEqual(result, (date, time))
40+
41+
check(0, 'Jan 1 1970', '00:00:00')
42+
check(1791419852, 'Oct 8 2026', '00:37:32')
43+
44+
def test_get_build_info(self):
45+
get_build_info = generate_getbuildinfo.get_build_info
46+
47+
with os_helper.EnvironmentVarGuard() as env:
48+
env['SOURCE_DATE_EPOCH'] = '0'
49+
date_time = 'Jan 1 1970, 00:00:00'
50+
# all set
51+
self.assertEqual(get_build_info('tag', 'branch', 'version'),
52+
(f'tag:version, {date_time}', 'tag'))
53+
# missing version
54+
self.assertEqual(get_build_info('tag', 'branch', ''),
55+
(f'tag, {date_time}', 'tag'))
56+
# Missing tag
57+
self.assertEqual(get_build_info('', 'branch', 'version'),
58+
(f'branch:version, {date_time}', 'branch'))
59+
# undefined tag
60+
self.assertEqual(get_build_info('undefined', 'branch', 'version'),
61+
(f'branch:version, {date_time}', 'branch'))
62+
# all empty
63+
self.assertEqual(get_build_info('', '', ''),
64+
(f'main, {date_time}', 'main'))
65+
66+
def test_get_compiler(self):
67+
getcompiler = 'Programs/_getcompiler'
68+
MOCK_COMPILER = 'MOCK_COMPILER'
69+
70+
for hostrunner in ('', 'node'):
71+
for use_cc in (False, True):
72+
makefile_vars = {
73+
'HOSTRUNNER': hostrunner,
74+
'CC': 'gcc -std=c11',
75+
}
76+
with mock.patch.object(generate_getbuildinfo,
77+
'get_makefile_vars',
78+
return_value=makefile_vars):
79+
with mock.patch.object(generate_getbuildinfo,
80+
'run_command') as mock_run_command:
81+
if use_cc:
82+
mock_run_command.side_effect = ('', MOCK_COMPILER)
83+
else:
84+
mock_run_command.return_value = MOCK_COMPILER
85+
compiler = generate_getbuildinfo._get_compiler()
86+
self.assertEqual(compiler, MOCK_COMPILER)
87+
if hostrunner:
88+
cmd = [hostrunner, getcompiler]
89+
else:
90+
cmd = [getcompiler]
91+
if use_cc:
92+
self.assertEqual(mock_run_command.call_args_list,
93+
[
94+
mock.call(cmd, check=False),
95+
mock.call(['gcc', '-std=c11', '--version'], check=False),
96+
]
97+
)
98+
else:
99+
mock_run_command.assert_called_once_with(cmd, check=False)
100+
101+
def test_shorter_clang_version(self):
102+
shorter_clang_version = generate_getbuildinfo.shorter_clang_version
103+
104+
def check(compiler, expected):
105+
self.assertEqual(shorter_clang_version(compiler), expected)
106+
107+
def check_unchanged(compiler):
108+
self.assertEqual(shorter_clang_version(compiler), compiler)
109+
110+
compiler = (
111+
'Android (13691557, +pgo, +bolt, +lto, +mlgo, based on r522817d) '
112+
'clang version 18.0.4 '
113+
'(https://android.googlesource.com/toolchain/llvm-project'
114+
' d8003a456d14a3deb8054cdaa529ffbf02d9b262)'
115+
)
116+
check(compiler, 'Clang 18.0.4')
117+
118+
compiler = (
119+
'Clang 18.0.4 '
120+
'(https://android.googlesource.com/toolchain/llvm-project'
121+
' d8003a456d14a3deb8054cdaa529ffbf02d9b262)'
122+
)
123+
check(compiler, 'Clang 18.0.4')
124+
125+
compiler = (
126+
'Clang 24.0.0git '
127+
'(https:/github.com/llvm/llvm-project'
128+
' a06d9165905ce89b5ffef2bbb84c886d60a9b8bf)'
129+
)
130+
check(compiler, 'Clang 24.0.0git')
131+
132+
check_unchanged('Clang 21.0.0 (clang-2100.1.1.101)')
133+
check_unchanged('Apple clang version 21.0.0 (clang-2100.1.1.101)')
134+
135+
def test_functional(self):
136+
old_locale = locale.setlocale(locale.LC_ALL)
137+
self.addCleanup(locale.setlocale, locale.LC_ALL, old_locale)
138+
139+
filename = 'output.txt'
140+
self.addCleanup(os.unlink, filename)
141+
142+
argv = [
143+
'program',
144+
'--output', filename,
145+
'--platform', 'SET_PLATFORM',
146+
'--git-version', 'VERSION',
147+
'--git-tag', 'TAG',
148+
'--git-branch', 'BRANCH',
149+
'--compiler', 'COMPILER',
150+
'--free-threading', '1',
151+
]
152+
with support.swap_attr(sys, 'argv', argv):
153+
with os_helper.EnvironmentVarGuard() as env:
154+
env['SOURCE_DATE_EPOCH'] = str(1791419852)
155+
with support.captured_stdout() as stdout:
156+
generate_getbuildinfo.main()
157+
self.assertEqual(stdout.getvalue(), f'{filename} updated\n')
158+
159+
with open(filename) as fp:
160+
output = fp.read()
161+
expected = textwrap.dedent('''
162+
// Header file auto-generated by Tools/build/generate_getbuildinfo.py
163+
164+
#define PLATFORM "SET_PLATFORM"
165+
#define COMPILER "[COMPILER]"
166+
#define GIT_VERSION "VERSION"
167+
#define GIT_IDENTIFIER "TAG"
168+
#define BUILD_INFO "TAG:VERSION, Oct 8 2026, 00:37:32"
169+
#define VERSION "3.16.0a0 free-threading build (TAG:VERSION, Oct 8 2026, 00:37:32) [COMPILER]"
170+
''').lstrip()
171+
self.assertEqual(output, expected)
172+
173+
174+
if __name__ == "__main__":
175+
unittest.main()

‎Tools/build/generate_getbuildinfo.py‎

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,26 @@
2222
# 'Android (13691557, +pgo, ...) clang version 18.0.4 (https://android.googlesource.com/toolchain/llvm-project d80...262)'
2323
CLANG_VERBOSE_VERSION = re.compile(r'(Clang .*|clang version .*) \(https:/.*\)')
2424

25+
# Parse short Clang version: 'clang version 18.0.4'
26+
CLANG_SHORT_VERSION = re.compile(r'clang version (.*)')
27+
2528

2629
def exit_error(msg):
2730
print(msg)
2831
print("cwd: {os.getcwd()}")
2932
sys.exit(1)
3033

3134

32-
def parse_file(variable_name, filename, regex):
35+
def get_py_version():
36+
filename = os.path.join(SRC_DIR, 'Include', 'patchlevel.h')
3337
with open(filename, encoding='utf8') as fp:
3438
code = fp.read()
35-
match = regex.search(code)
39+
match = PY_VERSION_REGEX.search(code)
3640
if not match:
37-
exit_error(f"ERROR: Unable to locate {variable_name} in {filename}")
41+
exit_error(f"ERROR: Unable to locate PY_VERSION in {filename}")
3842
return match.group(1)
3943

4044

41-
def get_py_version():
42-
patchlevel_h = os.path.join(SRC_DIR, 'Include', 'patchlevel.h')
43-
return parse_file('PY_VERSION ', patchlevel_h, PY_VERSION_REGEX)
44-
45-
4645
def get_makefile_vars():
4746
# Look in the current working directory
4847
makefile = 'Makefile'
@@ -60,10 +59,11 @@ def get_gil_disable():
6059
def get_date_time():
6160
if os.environ.get('SOURCE_DATE_EPOCH'):
6261
ts = int(os.environ['SOURCE_DATE_EPOCH'])
62+
time_tuple = time.gmtime(ts)
6363
else:
6464
ts = time.time()
65+
time_tuple = time.localtime(ts)
6566

66-
time_tuple = time.localtime(ts)
6767
day = time.strftime("%d", time_tuple)
6868
if day.startswith("0"):
6969
day = " " + day[1:]
@@ -154,6 +154,20 @@ def _get_compiler():
154154
return None
155155

156156

157+
def shorter_clang_version(compiler):
158+
# Make verbose Clang version shorter: strip the prefix and URL
159+
match = CLANG_VERBOSE_VERSION.search(compiler)
160+
if match:
161+
compiler = match.group(1)
162+
163+
# Use a full match: don't replace 'Apple clang version 21.0.0' for example
164+
match = CLANG_SHORT_VERSION.fullmatch(compiler)
165+
if match:
166+
compiler = 'Clang ' + match.group(1)
167+
168+
return compiler
169+
170+
157171
def get_compiler(compiler):
158172
if not compiler:
159173
compiler = _get_compiler()
@@ -162,10 +176,7 @@ def get_compiler(compiler):
162176
# (see Programs/_getcompiler.c)
163177
compiler = 'C'
164178

165-
# Make verbose Clang version shorter: strip the prefix and URL
166-
match = CLANG_VERBOSE_VERSION.search(compiler)
167-
if match:
168-
compiler = match.group(1)
179+
compiler = shorter_clang_version(compiler)
169180

170181
return f'[{compiler}]'
171182

@@ -194,8 +205,9 @@ def main():
194205

195206
# Get PY_VERSION macro from Include/patchlevel.h
196207
PY_VERSION = get_py_version()
208+
197209
if free_threading is None:
198-
# Get Py_GIL_DISABLED macro from pyconfig.h (defined or undefined)
210+
# Get Py_GIL_DISABLED macro from pyconfig.h
199211
free_threading = get_gil_disable()
200212

201213
if free_threading:

0 commit comments

Comments
 (0)