2 # Extract information from C headers.
3 # Copyright (C) 2018-2022 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/>.
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.
41 if isinstance(arg
, str):
43 out_lines
.append('void\ndummy (void)\n{')
49 out_lines
.append('asm ("/* @@@name@@@%s@@@value@@@%%0@@@end@@@ */" '
50 ': : \"i\" ((long int) (%s)));'
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)
65 with
open(s_file_name
, 'r') as s_file
:
67 match
= re
.search('@@@name@@@([^@]*)'
68 '@@@value@@@[^0-9Xxa-fA-F-]*'
69 '([0-9Xxa-fA-F-]+).*@@@end@@@', line
)
71 if (match
.group(1) in consts
72 and match
.group(2) != consts
[match
.group(1)]):
73 raise ValueError('duplicate constant %s'
75 consts
[match
.group(1)] = match
.group(2)
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.
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)
96 with
open(i_file_name
, 'r') as i_file
:
98 match
= re
.fullmatch('#define ([0-9A-Za-z_]+)(.*)\n', line
)
100 raise ValueError('bad -dM output line: %s' % line
)
101 name
= match
.group(1)
102 value
= match
.group(2)
103 if value
.startswith(' '):
106 elif value
.startswith('('):
107 match
= re
.fullmatch(r
'\((.*?)\) (.*)', value
)
109 raise ValueError('bad -dM output line: %s' % line
)
110 args
= match
.group(1).split(',')
111 value
= match
.group(2)
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
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
:
159 print('First source:\n%s\n' % source_1
)
160 print('Second source:\n%s\n' % source_2
)
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
:
167 elif macros_1
[name
] != macros_2
[name
]:
168 print('Different values for %s: %s != %s'
169 % (name
, macros_1
[name
], macros_2
[name
]))
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
:
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
,
192 r
= subprocess
.run(cmd
, check
=False, stdout
=subprocess
.PIPE
,
193 stderr
=subprocess
.STDOUT
)
194 return CompileResult(r
.returncode
, r
.stdout
)