| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- #!/usr/bin/python3
- # Extract information from C headers.
- # Copyright (C) 2018-2026 Free Software Foundation, Inc.
- # This file is part of the GNU C Library.
- #
- # The GNU C Library is free software; you can redistribute it and/or
- # modify it under the terms of the GNU Lesser General Public
- # License as published by the Free Software Foundation; either
- # version 2.1 of the License, or (at your option) any later version.
- #
- # The GNU C Library is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public
- # License along with the GNU C Library; if not, see
- # <https://www.gnu.org/licenses/>.
- import collections
- import os.path
- import re
- import subprocess
- import tempfile
- def compute_c_consts(sym_data, cc):
- """Compute the values of some C constants.
- The first argument is a list whose elements are either strings
- (preprocessor directives, or the special string 'START' to
- indicate this function should insert its initial boilerplate text
- in the output there) or pairs of strings (a name and a C
- expression for the corresponding value). Preprocessor directives
- in the middle of the list may be used to select which constants
- end up being evaluated using which expressions.
- """
- out_lines = []
- for arg in sym_data:
- if isinstance(arg, str):
- if arg == 'START':
- out_lines.append('void\ndummy (void)\n{')
- else:
- out_lines.append(arg)
- continue
- name = arg[0]
- value = arg[1]
- out_lines.append('asm ("/* @@@name@@@%s@@@value@@@%%0@@@end@@@ */" '
- ': : \"i\" ((long int) (%s)));'
- % (name, value))
- out_lines.append('}')
- out_lines.append('')
- out_text = '\n'.join(out_lines)
- with tempfile.TemporaryDirectory() as temp_dir:
- c_file_name = os.path.join(temp_dir, 'test.c')
- s_file_name = os.path.join(temp_dir, 'test.s')
- with open(c_file_name, 'w') as c_file:
- c_file.write(out_text)
- # Compilation has to be from stdin to avoid the temporary file
- # name being written into the generated dependencies.
- cmd = ('%s -S -o %s -x c - < %s' % (cc, s_file_name, c_file_name))
- subprocess.check_call(cmd, shell=True)
- consts = {}
- with open(s_file_name, 'r') as s_file:
- for line in s_file:
- match = re.search('@@@name@@@([^@]*)'
- '@@@value@@@[^0-9Xxa-fA-F-]*'
- '([0-9Xxa-fA-F-]+).*@@@end@@@', line)
- if match:
- if (match.group(1) in consts
- and match.group(2) != consts[match.group(1)]):
- raise ValueError('duplicate constant %s'
- % match.group(1))
- consts[match.group(1)] = match.group(2)
- return consts
- def list_macros(source_text, cc):
- """List the preprocessor macros defined by the given source code.
- The return value is a pair of dicts, the first one mapping macro
- names to their expansions and the second one mapping macro names
- to lists of their arguments, or to None for object-like macros.
- """
- with tempfile.TemporaryDirectory() as temp_dir:
- c_file_name = os.path.join(temp_dir, 'test.c')
- i_file_name = os.path.join(temp_dir, 'test.i')
- with open(c_file_name, 'w') as c_file:
- c_file.write(source_text)
- cmd = ('%s -E -dM -o %s %s' % (cc, i_file_name, c_file_name))
- subprocess.check_call(cmd, shell=True)
- macros_exp = {}
- macros_args = {}
- with open(i_file_name, 'r') as i_file:
- for line in i_file:
- match = re.fullmatch('#define ([0-9A-Za-z_]+)(.*)\n', line)
- if not match:
- raise ValueError('bad -dM output line: %s' % line)
- name = match.group(1)
- value = match.group(2)
- if value.startswith(' '):
- value = value[1:]
- args = None
- elif value.startswith('('):
- match = re.fullmatch(r'\((.*?)\) (.*)', value)
- if not match:
- raise ValueError('bad -dM output line: %s' % line)
- args = match.group(1).split(',')
- value = match.group(2)
- else:
- raise ValueError('bad -dM output line: %s' % line)
- if name in macros_exp:
- raise ValueError('duplicate macro: %s' % line)
- macros_exp[name] = value
- macros_args[name] = args
- return macros_exp, macros_args
- def compute_macro_consts(source_text, cc, macro_re, exclude_re=None):
- """Compute the integer constant values of macros defined by source_text.
- Macros must match the regular expression macro_re, and if
- exclude_re is defined they must not match exclude_re. Values are
- computed with compute_c_consts.
- """
- macros_exp, macros_args = list_macros(source_text, cc)
- macros_set = {m for m in macros_exp
- if (macros_args[m] is None
- and re.fullmatch(macro_re, m)
- and (exclude_re is None
- or not re.fullmatch(exclude_re, m)))}
- sym_data = [source_text, 'START']
- sym_data.extend(sorted((m, m) for m in macros_set))
- return compute_c_consts(sym_data, cc)
- def compare_macro_consts(source_1, source_2, cc, macro_re, exclude_re=None,
- allow_extra_1=False, allow_extra_2=False):
- """Compare the values of macros defined by two different sources.
- The sources would typically be includes of a glibc header and a
- kernel header. If allow_extra_1, the first source may define
- extra macros (typically if the kernel headers are older than the
- version glibc has taken definitions from); if allow_extra_2, the
- second source may define extra macros (typically if the kernel
- headers are newer than the version glibc has taken definitions
- from). Return 1 if there were any differences other than those
- allowed, 0 if the macro values were the same apart from any
- allowed differences.
- """
- macros_1 = compute_macro_consts(source_1, cc, macro_re, exclude_re)
- macros_2 = compute_macro_consts(source_2, cc, macro_re, exclude_re)
- if macros_1 == macros_2:
- return 0
- print('First source:\n%s\n' % source_1)
- print('Second source:\n%s\n' % source_2)
- ret = 0
- for name, value in sorted(macros_1.items()):
- if name not in macros_2:
- print('Only in first source: %s' % name)
- if not allow_extra_1:
- ret = 1
- elif macros_1[name] != macros_2[name]:
- print('Different values for %s: %s != %s'
- % (name, macros_1[name], macros_2[name]))
- ret = 1
- for name in sorted(macros_2.keys()):
- if name not in macros_1:
- print('Only in second source: %s' % name)
- if not allow_extra_2:
- ret = 1
- return ret
- CompileResult = collections.namedtuple("CompileResult", "returncode output")
- def compile_c_snippet(snippet, cc, extra_cc_args=''):
- """Compile and return whether the SNIPPET can be build with CC along
- EXTRA_CC_ARGS compiler flags. Return a CompileResult with RETURNCODE
- being 0 for success, or the failure value and the compiler output.
- """
- with tempfile.TemporaryDirectory() as temp_dir:
- c_file_name = os.path.join(temp_dir, 'test.c')
- obj_file_name = os.path.join(temp_dir, 'test.o')
- with open(c_file_name, 'w') as c_file:
- c_file.write(snippet + '\n')
- cmd = cc.split() + extra_cc_args.split() + ['-c', '-o', obj_file_name,
- c_file_name]
- r = subprocess.run(cmd, check=False, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- return CompileResult(r.returncode, r.stdout)
|