hurd: fix build of tst-system.c
[glibc.git] / scripts / glibcextract.py
blob6a26b9bd317270fe079583ea6a234d5ab76e6e27
1 #!/usr/bin/python3
2 # Extract information from C headers.
3 # Copyright (C) 2018-2023 Free Software Foundation, Inc.
4 # This file is part of the GNU C Library.
6 # The GNU C Library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
11 # The GNU C Library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with the GNU C Library; if not, see
18 # <https://www.gnu.org/licenses/>.
20 import collections
21 import os.path
22 import re
23 import subprocess
24 import tempfile
27 def compute_c_consts(sym_data, cc):
28 """Compute the values of some C constants.
30 The first argument is a list whose elements are either strings
31 (preprocessor directives, or the special string 'START' to
32 indicate this function should insert its initial boilerplate text
33 in the output there) or pairs of strings (a name and a C
34 expression for the corresponding value). Preprocessor directives
35 in the middle of the list may be used to select which constants
36 end up being evaluated using which expressions.
38 """
39 out_lines = []
40 for arg in sym_data:
41 if isinstance(arg, str):
42 if arg == 'START':
43 out_lines.append('void\ndummy (void)\n{')
44 else:
45 out_lines.append(arg)
46 continue
47 name = arg[0]
48 value = arg[1]
49 out_lines.append('asm ("/* @@@name@@@%s@@@value@@@%%0@@@end@@@ */" '
50 ': : \"i\" ((long int) (%s)));'
51 % (name, value))
52 out_lines.append('}')
53 out_lines.append('')
54 out_text = '\n'.join(out_lines)
55 with tempfile.TemporaryDirectory() as temp_dir:
56 c_file_name = os.path.join(temp_dir, 'test.c')
57 s_file_name = os.path.join(temp_dir, 'test.s')
58 with open(c_file_name, 'w') as c_file:
59 c_file.write(out_text)
60 # Compilation has to be from stdin to avoid the temporary file
61 # name being written into the generated dependencies.
62 cmd = ('%s -S -o %s -x c - < %s' % (cc, s_file_name, c_file_name))
63 subprocess.check_call(cmd, shell=True)
64 consts = {}
65 with open(s_file_name, 'r') as s_file:
66 for line in s_file:
67 match = re.search('@@@name@@@([^@]*)'
68 '@@@value@@@[^0-9Xxa-fA-F-]*'
69 '([0-9Xxa-fA-F-]+).*@@@end@@@', line)
70 if match:
71 if (match.group(1) in consts
72 and match.group(2) != consts[match.group(1)]):
73 raise ValueError('duplicate constant %s'
74 % match.group(1))
75 consts[match.group(1)] = match.group(2)
76 return consts
79 def list_macros(source_text, cc):
80 """List the preprocessor macros defined by the given source code.
82 The return value is a pair of dicts, the first one mapping macro
83 names to their expansions and the second one mapping macro names
84 to lists of their arguments, or to None for object-like macros.
86 """
87 with tempfile.TemporaryDirectory() as temp_dir:
88 c_file_name = os.path.join(temp_dir, 'test.c')
89 i_file_name = os.path.join(temp_dir, 'test.i')
90 with open(c_file_name, 'w') as c_file:
91 c_file.write(source_text)
92 cmd = ('%s -E -dM -o %s %s' % (cc, i_file_name, c_file_name))
93 subprocess.check_call(cmd, shell=True)
94 macros_exp = {}
95 macros_args = {}
96 with open(i_file_name, 'r') as i_file:
97 for line in i_file:
98 match = re.fullmatch('#define ([0-9A-Za-z_]+)(.*)\n', line)
99 if not match:
100 raise ValueError('bad -dM output line: %s' % line)
101 name = match.group(1)
102 value = match.group(2)
103 if value.startswith(' '):
104 value = value[1:]
105 args = None
106 elif value.startswith('('):
107 match = re.fullmatch(r'\((.*?)\) (.*)', value)
108 if not match:
109 raise ValueError('bad -dM output line: %s' % line)
110 args = match.group(1).split(',')
111 value = match.group(2)
112 else:
113 raise ValueError('bad -dM output line: %s' % line)
114 if name in macros_exp:
115 raise ValueError('duplicate macro: %s' % line)
116 macros_exp[name] = value
117 macros_args[name] = args
118 return macros_exp, macros_args
121 def compute_macro_consts(source_text, cc, macro_re, exclude_re=None):
122 """Compute the integer constant values of macros defined by source_text.
124 Macros must match the regular expression macro_re, and if
125 exclude_re is defined they must not match exclude_re. Values are
126 computed with compute_c_consts.
129 macros_exp, macros_args = list_macros(source_text, cc)
130 macros_set = {m for m in macros_exp
131 if (macros_args[m] is None
132 and re.fullmatch(macro_re, m)
133 and (exclude_re is None
134 or not re.fullmatch(exclude_re, m)))}
135 sym_data = [source_text, 'START']
136 sym_data.extend(sorted((m, m) for m in macros_set))
137 return compute_c_consts(sym_data, cc)
140 def compare_macro_consts(source_1, source_2, cc, macro_re, exclude_re=None,
141 allow_extra_1=False, allow_extra_2=False):
142 """Compare the values of macros defined by two different sources.
144 The sources would typically be includes of a glibc header and a
145 kernel header. If allow_extra_1, the first source may define
146 extra macros (typically if the kernel headers are older than the
147 version glibc has taken definitions from); if allow_extra_2, the
148 second source may define extra macros (typically if the kernel
149 headers are newer than the version glibc has taken definitions
150 from). Return 1 if there were any differences other than those
151 allowed, 0 if the macro values were the same apart from any
152 allowed differences.
155 macros_1 = compute_macro_consts(source_1, cc, macro_re, exclude_re)
156 macros_2 = compute_macro_consts(source_2, cc, macro_re, exclude_re)
157 if macros_1 == macros_2:
158 return 0
159 print('First source:\n%s\n' % source_1)
160 print('Second source:\n%s\n' % source_2)
161 ret = 0
162 for name, value in sorted(macros_1.items()):
163 if name not in macros_2:
164 print('Only in first source: %s' % name)
165 if not allow_extra_1:
166 ret = 1
167 elif macros_1[name] != macros_2[name]:
168 print('Different values for %s: %s != %s'
169 % (name, macros_1[name], macros_2[name]))
170 ret = 1
171 for name in sorted(macros_2.keys()):
172 if name not in macros_1:
173 print('Only in second source: %s' % name)
174 if not allow_extra_2:
175 ret = 1
176 return ret
178 CompileResult = collections.namedtuple("CompileResult", "returncode output")
180 def compile_c_snippet(snippet, cc, extra_cc_args=''):
181 """Compile and return whether the SNIPPET can be build with CC along
182 EXTRA_CC_ARGS compiler flags. Return a CompileResult with RETURNCODE
183 being 0 for success, or the failure value and the compiler output.
185 with tempfile.TemporaryDirectory() as temp_dir:
186 c_file_name = os.path.join(temp_dir, 'test.c')
187 obj_file_name = os.path.join(temp_dir, 'test.o')
188 with open(c_file_name, 'w') as c_file:
189 c_file.write(snippet + '\n')
190 cmd = cc.split() + extra_cc_args.split() + ['-c', '-o', obj_file_name,
191 c_file_name]
192 r = subprocess.run(cmd, check=False, stdout=subprocess.PIPE,
193 stderr=subprocess.STDOUT)
194 return CompileResult(r.returncode, r.stdout)