diff --git a/ChangeLog b/ChangeLog index f3368b7bcb..25985141e2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2019-02-15 Florian Weimer + + Check that non-sysdeps headers have wrapper headers. + * scripts/check-wrapper-headers.py: New file. + * Makefile (tests-special): Add check-wrapper-headers.out. + (check-wrapper-headers.out): New target. + * Rules (tests-special): Add check-wrapper-headers.out. + (check-wrapper-headers.out): New target. + 2019-02-15 Florian Weimer Add missing header wrappers under include/. diff --git a/Makefile b/Makefile index 1bfe7fefe1..dc5de7aa6b 100644 --- a/Makefile +++ b/Makefile @@ -331,8 +331,13 @@ $(objpfx)check-installed-headers-cxx.out: \ "$(CXX) $(filter-out -std=%,$(CXXFLAGS)) -D_ISOMAC $(+includes)" \ $(headers) > $@; \ $(evaluate-test) -endif -endif +endif # $(CXX) + +tests-special += $(objpfx)check-wrapper-headers.out +$(objpfx)check-wrapper-headers.out: scripts/check-wrapper-headers.py $(headers) + $(PYTHON) $< --root=. --subdir=. $(headers) \ + --generated $(common-generated) > $@; $(evaluate-test) +endif # $(headers) define summarize-tests @egrep -v '^(PASS|XFAIL):' $(objpfx)$1 || true diff --git a/Rules b/Rules index 1562f2ce6d..e08a28d9f3 100644 --- a/Rules +++ b/Rules @@ -101,8 +101,15 @@ $(objpfx)check-installed-headers-cxx.out: \ "$(CXX) $(filter-out -std=%,$(CXXFLAGS)) -D_ISOMAC $(+includes)" \ $(headers) > $@; \ $(evaluate-test) -endif -endif +endif # $(CXX) + +tests-special += $(objpfx)check-wrapper-headers.out +$(objpfx)check-wrapper-headers.out: \ + $(..)scripts/check-wrapper-headers.py $(headers) + $(PYTHON) $< --root=$(..) --subdir=$(subdir) $(headers) > $@; \ + $(evaluate-test) + +endif # $(headers) # This makes all the auxiliary and test programs. diff --git a/scripts/check-wrapper-headers.py b/scripts/check-wrapper-headers.py new file mode 100644 index 0000000000..094faa3ced --- /dev/null +++ b/scripts/check-wrapper-headers.py @@ -0,0 +1,113 @@ +#!/usr/bin/python3 +# Check that a wrapper header exist for each non-sysdeps header. +# Copyright (C) 2019 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 +# . + +# Non-sysdeps subdirectories are not on the C include path, so +# installed headers need to have a sysdep wrapper header. +# +# usage: scripts/checl-wrapper-headers.py \ +# --root=$(..) --subdir=$(subdir) $(headers) \ +# [--generated $(common-generated)] +# +# If invoked with --root=., the script is invoked from the root of the +# source tree, so paths starting with "include/" are skipped (because +# those do not require wrappers). + +import argparse +import os +import sys + +# Some subdirectories are only compiled for essentially one target. +# In this case, we do not need to check for consistent wrapper +# headers. Hurd uses a custom way to Hurd-specific inject wrapper +# headers; see sysdeps/mach/Makefiles under "ifdef in-Makerules". +SINGLE_TARGET_SUBDIRS = frozenset(("hurd", "mach")) + +# Name of the special subdirectory with the wrapper headers. +INCLUDE = "include" + +def check_sysdeps_bits(args): + """Check that the directory sysdeps/generic/bits does not exist.""" + bits = os.path.join(args.root, 'sysdeps', 'generic', 'bits') + if os.path.exists(bits): + # See commit c72565e5f1124c2dc72573e83406fe999e56091f and + # . + print('error: directory {} has been added, use bits/ instead'.format( + os.path.relpath(os.path.realpath(bits), args.root))) + return False + return True + +def check_headers_root(args): + """Check headers located at the top level of the source tree.""" + good = True + generated = frozenset(args.generated) + for header in args.headers: + if not (header.startswith('bits/') + or os.path.exists(os.path.join(args.root, INCLUDE, header)) + or header in generated): + print('error: top-level header {} must be in bits/ or {}/' + .format(header, INCLUDE)) + good = False + return good + +def check_headers(args): + """Check headers located in a subdirectory.""" + good = True + for header in args.headers: + # Whitelist .x files, which never have include wrappers. + if header.endswith(".x"): + continue + + is_nonsysdep_header = os.access(header, os.R_OK) + if is_nonsysdep_header: + include_path = os.path.join(args.root, INCLUDE, header) + if not os.access(include_path, os.R_OK): + print('error: missing wrapper header {} for {}'.format( + os.path.join(INCLUDE, header), + os.path.relpath(os.path.realpath(header), args.root))) + good = False + return good + +def main(): + """The main entry point.""" + parser = argparse.ArgumentParser( + description='Check for missing wrapper headers in include/.') + parser.add_argument('--root', metavar='DIRECTORY', required=True, + help='Path to the top-level of the source tree') + parser.add_argument('--subdir', metavar='DIRECTORY', required=True, + help='Name of the subdirectory being processed') + parser.add_argument('--generated', metavar='FILE', default="", nargs="*", + help="Generated files (which are ignored)") + parser.add_argument('headers', help='Header files to process', nargs='+') + args = parser.parse_args() + + good = (args.root == '.') == (args.subdir == '.') + if not good: + print('error: --root/--subdir disagree about top-of-tree location') + + if args.subdir == '.': + good &= check_sysdeps_bits(args) + good &= check_headers_root(args) + elif args.subdir not in SINGLE_TARGET_SUBDIRS: + good &= check_headers(args) + + if not good: + sys.exit(1) + +if __name__ == '__main__': + main()