11# Script to generate Modules/getbuildinfo.h
22
3+ import argparse
34import locale
45import os
56import re
7+ import shlex
8+ import subprocess
69import sys
710import time
811
1114SCRIPT_FULLNAME = f'Tools/build/{ SCRIPT_NAME } '
1215
1316# Get PY_VERSION from Include/patchlevel.h
14- PY_VERSION_REGEX = re .compile (r'#define PY_VERSION +"(.*)"' )
17+ PY_VERSION_REGEX = re .compile (r'^#define PY_VERSION +"(.*)"$' , re .MULTILINE )
18+
19+ # Get HOSTRUNNER from Makefile
20+ HOSTRUNNER_REGEX = re .compile (r'^HOSTRUNNER *= *(.*)$' , re .MULTILINE )
1521
1622# Py_GIL_DISABLED from pyconfig.h
17- Py_GIL_DISABLED_DEF_REGEX = re .compile (r'#define Py_GIL_DISABLED 1' )
18- Py_GIL_DISABLED_UNDEF_REGEX = re .compile (r'/\* #undef Py_GIL_DISABLED \*/' )
23+ Py_GIL_DISABLED_DEF_REGEX = re .compile (r'^ #define Py_GIL_DISABLED 1$' , re . MULTILINE )
24+ Py_GIL_DISABLED_UNDEF_REGEX = re .compile (r'^ /\* #undef Py_GIL_DISABLED \*/$' , re . MULTILINE )
1925
2026
21- def get_py_version ():
22- patchlevel_h = os .path .join (SRC_DIR , 'Include' , 'patchlevel.h' )
23- with open (patchlevel_h , encoding = 'utf8' ) as fp :
27+ def parse_file (variable_name , filename , regex ):
28+ with open (filename , encoding = 'utf8' ) as fp :
2429 code = fp .read ()
25- match = PY_VERSION_REGEX .search (code )
30+ match = regex .search (code )
2631 if not match :
27- print (f"ERROR: Unable to locate PY_VERSION in { patchlevel_h } " )
32+ print (f"ERROR: Unable to locate { variable_name } in { filename } " )
2833 sys .exit (1 )
2934 return match .group (1 )
3035
3136
37+ def get_py_version ():
38+ patchlevel_h = os .path .join (SRC_DIR , 'Include' , 'patchlevel.h' )
39+ return parse_file ('PY_VERSION ' , patchlevel_h , PY_VERSION_REGEX )
40+
41+
42+ def get_host_runner ():
43+ # Look in the current working directory
44+ makefile = 'Makefile'
45+ HOSTRUNNER = parse_file ('HOSTRUNNER' , makefile , HOSTRUNNER_REGEX )
46+ return HOSTRUNNER .rstrip ()
47+
48+
3249def get_gil_disable ():
3350 # Look in the current working directory
3451 pyconfig_h = 'pyconfig.h'
@@ -54,79 +71,114 @@ def get_date_time():
5471 day = time .strftime ("%d" , time_tuple )
5572 if day .startswith ("0" ):
5673 day = " " + day [1 :]
57- DATE = time .strftime (f"%b { day } %Y" , time_tuple )
58- TIME = time .strftime ("%H:%M:%S" , time_tuple )
59- return (DATE , TIME )
74+ build_date = time .strftime (f"%b { day } %Y" , time_tuple )
75+ build_time = time .strftime ("%H:%M:%S" , time_tuple )
76+ return (build_date , build_time )
6077
6178
62- def main ():
63- if len (sys .argv ) != 8 :
64- print (f"usage: { SCRIPT_NAME } "
65- "OUTPUT PLATFORM GITVERSION GITTAG GITBRANCH COMPILER GIL_ENABLED" )
66- sys .exit (1 )
79+ def get_build_info (git_tag , git_branch , git_version , build_date , build_time ):
80+ if git_tag and git_tag != "undefined" :
81+ git_id = git_tag
82+ else :
83+ git_id = git_branch
84+ if not git_id :
85+ git_id = "main"
6786
68- OUTPUT = sys .argv [1 ]
69- PLATFORM = sys .argv [2 ]
70- GITVERSION = sys .argv [3 ]
71- GITTAG = sys .argv [4 ]
72- GITBRANCH = sys .argv [5 ]
73- COMPILER = sys .argv [6 ]
74- GIL_ENABLED = sys .argv [7 ]
87+ sep = ":" if git_version else ""
7588
76- if not PLATFORM :
77- PLATFORM = "unknown"
89+ build_info = f" { git_id } { sep } { git_version } , { build_date :.20s } , { build_time :.9s } "
90+ return ( build_info , git_id )
7891
79- # Force the C locale to format date as English
80- locale .setlocale (locale .LC_ALL , 'C' )
8192
82- gittag = GITTAG
83- if gittag and gittag != "undefined" :
84- gitid = gittag
93+ def parse_args ():
94+ parser = argparse .ArgumentParser ()
95+ parser .add_argument ('-o' , '--output' , type = str )
96+ parser .add_argument ('--platform' , type = str , required = True )
97+ parser .add_argument ('--git-version' , type = str , required = True )
98+ parser .add_argument ('--git-tag' , type = str , required = True )
99+ parser .add_argument ('--git-branch' , type = str , required = True )
100+ parser .add_argument ('--compiler' , type = str )
101+ parser .add_argument ('--gil-enabled' , type = int )
102+ return parser .parse_args ()
103+
104+
105+ def run_command (cmd ):
106+ try :
107+ proc = subprocess .run (cmd , stdout = subprocess .PIPE , text = True )
108+ except OSError as exc :
109+ error = f'with: { exc !r} '
85110 else :
86- gitid = GITBRANCH
111+ exitcode = proc .returncode
112+ if exitcode :
113+ error = f'with exit code { exitcode } '
114+ else :
115+ error = None
116+
117+ if error :
118+ print (f"Command { shlex .join (cmd )} failed { error } " )
119+ sys .exit (1 )
120+ return proc .stdout .rstrip ()
87121
88- revision = GITVERSION
89- sep = ":" if revision else ""
90- if not gitid :
91- gitid = "main"
92122
93- DATE , TIME = get_date_time ()
123+ def main ():
124+ # Force the C locale to format date as English
125+ locale .setlocale (locale .LC_ALL , 'C' )
126+
127+ args = parse_args ()
128+ output_filename = args .output
129+ platform = args .platform
130+ git_version = args .git_version
131+ git_tag = args .git_tag
132+ git_branch = args .git_branch
133+ compiler = args .compiler
134+ gil_enabled = args .gil_enabled
135+
136+ if not output_filename :
137+ # In the current directory
138+ output_filename = os .path .join ('Modules' , 'getbuildinfo.h' )
139+ if not platform :
140+ platform = "unknown"
141+
142+ if not compiler :
143+ getcompiler = os .path .join ('Programs' , '_getcompiler' )
144+ HOSTRUNNER = get_host_runner ()
145+ if HOSTRUNNER :
146+ runner = shlex .split (HOSTRUNNER )[0 ]
147+ compiler = run_command ([runner , getcompiler ])
148+ else :
149+ compiler = run_command ([getcompiler ])
150+
151+ build_date , build_time = get_date_time ()
152+ build_info , git_id = get_build_info (git_tag , git_branch , git_version , build_date , build_time )
153+
154+ # Get PY_VERSION macro from Include/patchlevel.h
94155 PY_VERSION = get_py_version ()
95- if GIL_ENABLED == 'use_pyconfig' :
156+ if gil_enabled is None :
157+ # Get Py_GIL_DISABLED macro from pyconfig.h (defined or undefined)
96158 Py_GIL_DISABLED = get_gil_disable ()
97- GIL_ENABLED = not Py_GIL_DISABLED
98- else :
99- GIL_ENABLED = int (GIL_ENABLED )
100- BUILDINFO = f"{ gitid } { sep } { revision } , { DATE :.20s} , { TIME :.9s} "
159+ gil_enabled = not Py_GIL_DISABLED
101160
102- if not GIL_ENABLED :
103- GET_VERSION = f"{ PY_VERSION } free-threading build ({ BUILDINFO } ) { COMPILER } "
161+ if not gil_enabled :
162+ version = f"{ PY_VERSION } free-threading build ({ build_info } ) { compiler } "
104163 else :
105- GET_VERSION = f"{ PY_VERSION } ({ BUILDINFO } ) { COMPILER } "
164+ version = f"{ PY_VERSION } ({ build_info } ) { compiler } "
106165
107- new_filename = OUTPUT + ".new"
166+ new_filename = output_filename + ".new"
108167 with open (new_filename , "w" , encoding = "utf8" ) as fp :
109168 def write_macro (name , value ):
110169 print (f'#define { name } "{ value } "' , file = fp )
111170
112171 print (f'// Header file auto-generated by { SCRIPT_FULLNAME } ' , file = fp )
113172 print (file = fp )
114- write_macro ('DATE' , DATE )
115- write_macro ('TIME' , TIME )
116- write_macro ('COMPILER' , COMPILER )
117- print (file = fp )
118- write_macro ('PLATFORM' , PLATFORM )
119- print (file = fp )
120- write_macro ('GITBRANCH' , GITBRANCH )
121- write_macro ('GITTAG' , GITTAG )
122- write_macro ('GITVERSION' , GITVERSION )
123- write_macro ('GIT_IDENTIFIER' , gitid )
124- print (file = fp )
125- write_macro ('BUILDINFO' , BUILDINFO )
126- write_macro ('GET_VERSION' , GET_VERSION )
127-
128- os .replace (new_filename , OUTPUT )
129- print (f"{ OUTPUT } updated" )
173+ write_macro ('PLATFORM' , platform )
174+ write_macro ('COMPILER' , compiler )
175+ write_macro ('GIT_VERSION' , git_version )
176+ write_macro ('GIT_IDENTIFIER' , git_id )
177+ write_macro ('BUILD_INFO' , build_info )
178+ write_macro ('VERSION' , version )
179+
180+ os .replace (new_filename , output_filename )
181+ print (f"{ output_filename } updated" )
130182
131183
132184if __name__ == "__main__" :
0 commit comments