Move include dependency checks to doc-check
authorTeemu Murtola <teemu.murtola@gmail.com>
Thu, 20 Mar 2014 18:21:32 +0000 (20:21 +0200)
committerTeemu Murtola <teemu.murtola@gmail.com>
Thu, 27 Mar 2014 18:05:06 +0000 (20:05 +0200)
The new doc-check target now contains all the checks that were
previously in depcheck and doccheck.  Remove the old targets.

Update the documentation to describe the new checker.

Change-Id: I3cd6b916507a9d21318914dbf88a8431b2a12149

admin/includedeps.py
doxygen/CMakeLists.txt
doxygen/doxygen-check.py
doxygen/doxygen.md
doxygen/doxygenxml.py
doxygen/gmxtree.py
doxygen/suppressions.txt

index f3cc0e022d625938a5850ecab9157e64eb472f63..0122aa150df9205458f63f351627492020ecdb56 100755 (executable)
@@ -505,91 +505,6 @@ def _is_legacy_module(module):
     return False
 
 
-class IncludeFileChecker(object):
-    def __init__(self, deps, options):
-        self._deps = deps
-        self._options = options
-
-    def _check_file(self, checkfile, reporter):
-        if not self._options.check_doc:
-            return
-        if not checkfile.is_documented():
-            if self._options.warn_undoc:
-                is_legacy = _is_legacy_module(checkfile.module)
-                is_external = checkfile.module.name in ('gmx_lapack', 'gmx_blas', 'thread_mpi')
-                if not is_legacy and not is_external:
-                    reporter.error(checkfile.path, 'file not documented')
-        elif checkfile.doctype == 'implementation' and \
-                checkfile.type in ('publicheader', 'libheader'):
-            reporter.error(checkfile.path,
-                    'file documentation visibility incorrect')
-        elif checkfile.doctype == 'library' and checkfile.type == 'publicheader':
-            reporter.error(checkfile.path,
-                    'file documentation visibility incorrect')
-        elif checkfile.installed and checkfile.doctype not in ('public', 'unknown'):
-            reporter.error(checkfile.path,
-                    'installed header has no public documentation')
-        elif not checkfile.installed and checkfile.doctype == 'public':
-            reporter.error(checkfile.path,
-                    'non-installed file has public documentation')
-        selfmodfullname = checkfile.module.fullname
-        docmodule = checkfile.docmodule
-        if docmodule and \
-                not selfmodfullname.startswith('module_' + docmodule) and \
-                not selfmodfullname.startswith('module_gromacs_' + docmodule) and \
-                not checkfile.name == docmodule + '.h':
-            reporter.error(checkfile.path,
-                    'file documented in incorrect module "{0}"'
-                        .format(docmodule))
-
-    def _check_included_file(self, checkfile, includedfile, reporter):
-        otherfile = includedfile._included_file
-        if includedfile._is_system:
-            # TODO: This doesn't report errors with files not listed in
-            # the input files, although those could be included.
-            # That would produce a massive amount of errors for <config.h>.
-            if otherfile:
-                reporter.error(checkfile.path,
-                        'local file included as <{0}>'
-                            .format(includedfile._included_path))
-        elif not includedfile._is_relative and checkfile.installed:
-            if not includedfile._included_path == 'gmx_header_config_gen.h':
-                reporter.error(checkfile.path,
-                        'installed header includes "{0}", '
-                        'which is not found using relative path'
-                            .format(includedfile._included_path))
-        if not otherfile:
-            return
-        if checkfile.installed and not otherfile.installed:
-            reporter.error(checkfile.path,
-                    'installed header includes '
-                    'non-installed header "{0}"'
-                        .format(includedfile._included_path))
-        if not otherfile.is_documented():
-            return
-        if not self._options.check_doc:
-            return
-        intramodule = \
-                (checkfile.module.get_top_level_module() == \
-                 otherfile.module.get_top_level_module())
-        if otherfile.type not in ('publicheader', 'libheader', 'test'):
-            if not intramodule and not _is_legacy_module(otherfile.module):
-                reporter.error(checkfile.path,
-                        'included file "{0}" is missing API definition'
-                            .format(otherfile.path))
-        elif checkfile.type == 'publicheader':
-            if not otherfile.type == 'publicheader' and not otherfile.doctype == 'public':
-                reporter.error(checkfile.path,
-                        'public API file includes non-public header "{0}"'
-                            .format(otherfile.path))
-
-    def check_all(self, reporter):
-        for checkfile in sorted(self._deps.files.values()):
-            self._check_file(checkfile, reporter)
-            for includedfile in checkfile.get_included_files():
-                self._check_included_file(checkfile, includedfile, reporter)
-
-
 class GraphBuilder(object):
     def __init__(self, deps):
         self._deps = deps
@@ -782,12 +697,6 @@ def main():
     #parser.add_option('--external-at-bottom', action='store_true',
     #                  help='Force external dependencies files at the bottom '
     #                       'of the graph')
-    parser.add_option('--check', action='store_true',
-                      help='Check for problems in include file dependencies')
-    parser.add_option('--check-doc', action='store_true',
-                      help='Check for problems in Doxygen documentation')
-    parser.add_option('--warn-undoc', action='store_true',
-                      help='Warn for files that do not have Doxygen documentation')
     parser.add_option('--left-to-right', action='store_true',
                       help='Lay out from left to right')
     parser.add_option('--file-graph',
@@ -798,10 +707,6 @@ def main():
                       help='Write file graphs for each module')
     options, args = parser.parse_args()
 
-    if not options.file_graph and not options.module_graph and \
-            not options.module_file_graphs:
-        options.check = True
-
     # Constructs lists of files
     filelist = []
     ignorelist = []
@@ -827,10 +732,6 @@ def main():
 
     deps.scan_files(ignorelist, reporter)
 
-    if options.check or options.check_doc:
-        checker = IncludeFileChecker(deps, options)
-        checker.check_all(reporter)
-
     #if options.with_external:
     #    for filename in extrafiles:
     #        file = files[filename]
index ee8e0992ba88ae0bdc80e16f0a22becce918c89d..085d739431f9adf99cdd7e9de90c08469af37baf 100644 (file)
@@ -137,6 +137,9 @@ if (DOXYGEN_FOUND)
     endif()
 
     if (USE_PYTHON_SCRIPTS)
+        # TODO: Consider whether this is the best name and location for this
+        # code, since not all of it is Doxygen-specific (but nearly all of it
+        # relies on the Doxygen XML documentation).
         set(doc_check_command
             ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/doxygen-check.py
             -S ${CMAKE_SOURCE_DIR} -B ${CMAKE_BINARY_DIR}
@@ -165,32 +168,4 @@ if (USE_PYTHON_SCRIPTS)
         -DMODE=GRAPHS
         -P ${CMAKE_SOURCE_DIR}/admin/includedeps.cmake
         COMMENT "Generating include dependency graphs" VERBATIM)
-    # TODO: This should be moved away from here and preferably made into
-    # a CTest test.
-    # TODO: To do the above, issues found by the script should be fixed.
-    # A suppression system would also be good to have, since it would be
-    # nice to include also legacyheaders in the checks, but those generate
-    # a lot of errors about missing documentation.  The script is also
-    # currently quite strict, so I don't know whether we really want to
-    # enforce all the rules.
-    # TODO: Would be nice to not generate the file lists for each of these,
-    # but what would be a good shared location for the intermediate files?
-    add_custom_target(depcheck
-        ${CMAKE_COMMAND}
-        -DSRCDIR=${CMAKE_SOURCE_DIR}
-        -DBUILDDIR=${CMAKE_BINARY_DIR}
-        -DOUTDIR=${CMAKE_BINARY_DIR}/doxygen/depgraphs
-        -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}
-        -DMODE=CHECK
-        -P ${CMAKE_SOURCE_DIR}/admin/includedeps.cmake
-        COMMENT "Checking include dependencies" VERBATIM)
-    add_custom_target(doccheck
-        ${CMAKE_COMMAND}
-        -DSRCDIR=${CMAKE_SOURCE_DIR}
-        -DBUILDDIR=${CMAKE_BINARY_DIR}
-        -DOUTDIR=${CMAKE_BINARY_DIR}/doxygen/depgraphs
-        -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}
-        -DMODE=CHECKDOC
-        -P ${CMAKE_SOURCE_DIR}/admin/includedeps.cmake
-        COMMENT "Checking some aspects of Doxygen documentation" VERBATIM)
 endif()
index c17224a0cd82677171be7dcf10340915a980fd4a..f292b85abedb06194a0934b8aee968dd7d9b491b 100755 (executable)
@@ -108,6 +108,43 @@ def check_file(fileobj, reporter):
                     "is not documented in any module, but {0} exists"
                     .format(expectedmod.get_name()))
 
+def check_include(fileobj, includedfile, reporter):
+    """Check an #include directive."""
+    if includedfile.is_system():
+        if includedfile.get_file():
+            reporter.code_issue(includedfile,
+                    "includes local file as {0}".format(includedfile))
+    else:
+        otherfile = includedfile.get_file()
+        if not otherfile:
+            reporter.code_issue(includedfile,
+                    "includes non-local file as {0}".format(includedfile))
+        elif fileobj.is_installed() and not includedfile.is_relative():
+            reporter.code_issue(includedfile,
+                    "installed header includes {0} using non-relative path"
+                    .format(includedfile))
+        if not otherfile:
+            return
+        if fileobj.is_installed() and not otherfile.is_installed():
+            reporter.code_issue(includedfile,
+                    "installed header includes non-installed {0}"
+                    .format(includedfile))
+        filemodule = fileobj.get_module()
+        othermodule = otherfile.get_module()
+        if fileobj.is_documented() and otherfile.is_documented():
+            filetype = fileobj.get_documentation_type()
+            othertype = otherfile.get_documentation_type()
+            if filetype > othertype:
+                reporter.code_issue(includedfile,
+                        "{0} file includes {1} file {2}"
+                        .format(filetype, othertype, includedfile))
+        check_api = (othermodule and othermodule.is_documented() and
+                filemodule != othermodule)
+        if check_api and otherfile.get_api_type() < DocType.library:
+            reporter.code_issue(includedfile,
+                    "included file {0} is not documented as exposed outside its module"
+                    .format(includedfile))
+
 def check_entity(entity, reporter):
     """Check documentation for a code construct."""
     if entity.is_documented():
@@ -173,6 +210,9 @@ def main():
         sys.stderr.write('Scanning source tree...\n')
     tree = GromacsTree(options.source_root, options.build_root, reporter)
     tree.set_installed_file_list(installedlist)
+    if not options.quiet:
+        sys.stderr.write('Reading source files...\n')
+    tree.scan_files()
     if not options.quiet:
         sys.stderr.write('Reading Doxygen XML files...\n')
     tree.load_xml()
@@ -184,6 +224,8 @@ def main():
 
     for fileobj in tree.get_files():
         check_file(fileobj, reporter)
+        for includedfile in fileobj.get_includes():
+            check_include(fileobj, includedfile, reporter)
 
     for classobj in tree.get_classes():
         check_class(classobj, reporter)
@@ -192,8 +234,6 @@ def main():
         if memberobj.is_visible() or options.check_ignored:
             check_member(memberobj, reporter)
 
-    # TODO: Check #include statements, like old 'make doccheck'
-
     reporter.write_pending()
     reporter.report_unused_filters()
     reporter.close_log()
index b759e7310e971238ab1acbfac0fb6489dfa2d344..a34258f005d92bdc61f731a5693b3fd6a9794e6d 100644 (file)
@@ -259,33 +259,96 @@ in such a case, the module group is preferred over the API group.
 Automatic dependency checking
 -----------------------------
 
-The build system provides three targets, `depcheck`, `doccheck`, and
-`depgraphs`, that depend on correct usage of the above commands, in particular
-the visibility and API definitions in file-level comments.
-These checks also provide some level of enforcement for rules about
-dependencies between the modules, but currently the checks are not run
-automatically.
-
-The `depcheck` target checks for issues that either completely or subtly break
-the installed headers: an installed header including a non-installed header, or
-an installed header installing another header using a path that does not
-resolve correctly when the headers are installed.  It also checks for
-consistent usage of
-\code
-#include "..." // This should be used for Gromacs headers
-\endcode
-versus
-\code
-#include <...> // This should be used for system and external headers.
-\endcode
-
-The `doccheck` target additionally checks that if an included file is
-documented, it is not used against the API definition specified in the
-file-level comment.  For example, including a file without any API definition
-from another module produces an error.  Another example of a check is that a
-non-installed header with public documentation triggers an error.
-
-The `depgraphs` target generates include dependency graphs.  One graph is
+The build system provides a `doc-check` target that automatically checks some
+aspects of the documentation, as well as checking that header files are
+actually used according to their API specifications (i.e., an internal headers
+are not included from other modules).  The checks depend on correct usage of
+the commands listed above, in particular the visibility and API definitions in
+file-level comments.  These checks also provide some level of enforcement for
+rules about dependencies between the modules, but currently the checks are not
+run automatically.
+
+The checker currently checks for a few different types of issues:
+* For all Doxygen documentation (currently does not apply for members within
+  anonymous namespaces or members that do not appear in the documentation):
+   * If a member has documentation, it should have a brief description.
+   * A note is issued for in-body documentation for functions, since this is
+     ignored by our current settings.
+   * If a class has documentation, it should have public documentation only if
+     it appears in an installed header.
+   * If a class and its containing file has documentation, the class
+     documentation should not be visible if the file documentation is not.
+* For all files:
+   * Consistent usage of
+
+         #include "..." // This should be used for Gromacs headers
+
+     and
+
+         #include <...> // This should be used for system and external headers
+
+   * Installed headers must not include non-installed headers, and must include
+     all other \Gromacs headers using relative paths so that they resolve
+     correctly also when installed.
+* For documented files:
+   * Installed headers should have public documentation, and other files should
+     not.
+   * The API level specified for a file should not be higher than where its
+     documentation is visible.  For example, only publicly documented headers
+     should be specified as part of the public API.
+   * If an \c \\ingroup module_foo exists, it should match the subdirectory
+     that the file is actually part of in the file system.
+   * If a \c \\defgroup module_foo exists for the subdirectory where the file is,
+     the file should contain \c \\ingroup module_foo.
+   * Files should not include other files whose documentation visibility is
+     lower (if the included file is not documented, the check is skipped).
+* For files that are part of documented modules
+  (\c \\defgroup module_foo exists for the subdirectory):
+   * Such files should not be included from outside their module if they are
+     undocumented or are not specified as part of library or public API.
+
+The checker is based on extracting the Doxygen documentation in XML format.
+This information is then read using a Python script, and combined with
+information extracted from the file system and knowledge about the \Gromacs
+source tree layout.  The Python scripts are in the `doxygen/` folder.
+In addition to printing the issues, they are also written into
+`doxygen/doxygen-check.log` for later inspection.
+
+The script is not currently perfect (either because of unfinished
+implementation, or because Doxygen bugs or incompleteness of the Doxygen XML
+output), and the current code also contains issues that the script detects, but
+the authors have not fixed.  To allow the script to still be used,
+`doxygen/suppressions.txt` contains a list of issues that are filtered out from
+the report.  The syntax is simple:
+
+    <file>: <text>
+
+where `<file>` is a path to the file that reports the message, and `<text>` is
+the text reported.  Both support `*` as a wildcard.  If `<file>` is empty, the
+suppression matches only messages that do not have an associated file.
+`<file>` is matched against the trailing portion of the file name to make it
+work even though the script reports absolute paths.
+Empty lines and lines starting with `#` are ignored.
+
+To add suppression for an issue, the line that reports the issue can be copied
+into `suppressios.txt`, and the line number (if any) removed.  If the
+issue does not have a file name (or a pseudo-file) associated, a leading `:`
+must be added.  To cover many similar issues, parts of the line can then be
+replaced with wildcards.
+
+For some false positives from the script, the suppression mechanism is the
+easiest way to silence the script, but otherwise the goal would be to minimize
+the number of suppressions.
+
+As a side effect, the XML extraction makes Doxygen parse all comments in the
+code, even if they do not appear in the documentation.  This can reveal latent
+issues in the comments, like invalid Doxygen syntax.  The messages from the XML
+parsing are stored in `doxygen/doxygen-xml.log` in the build tree, similar to
+other Doxygen runs.
+
+There is an additional target `depgraphs` that generates include dependency
+graphs.  This is in the process of getting rewritten to take advantage of the
+mechanisms used for `doc-check`.  One graph is
 produced that shows all the modules under `src/gromacs/`, and their include
 dependencies.  Additionally, a file-level graph is produced for each module,
 showing the include dependencies within that module.  Currently, these are
@@ -294,8 +357,8 @@ dependencies to clean up the architecture.  A legend for the graphs is
 currently included as comments in `admin/includedeps.py`.  The output is put in
 `doxygen/depgraphs/`.
 
-These targets require Python 2.7; the graph generation additionally requires
-`graphviz`.
+These targets require Python 2.7 (other versions may work, but have not been
+tested); the graph generation additionally requires `graphviz`.
 
 
 Documenting specific code constructs
index b63ad3612caa85dc8bdcc2d211ddb5a2fd391a6b..6a8c21f28aee29be3049ef3fcc2ca99d376617f4 100755 (executable)
@@ -90,7 +90,7 @@ class DocType(object):
     """Documentation visibility in the generated documentation."""
 
     # Mapping to string representations for the internal integer values
-    _names = ['none', 'internal', 'library', 'public']
+    _names = ['undocumented', 'internal', 'library', 'public']
 
     def __init__(self, value):
         """Initialize a DocType instance.
index 2595c447e604c441c2b0bd3c296cf4b608b70cbe..836d1db426e6bc0ce380b35d2b34f4e9ebb1096a 100644 (file)
@@ -52,6 +52,7 @@ customizations will be added.
 
 import os
 import os.path
+import re
 
 import doxygenxml as xml
 import reporter
@@ -70,6 +71,37 @@ def _get_api_type_for_compound(grouplist):
             # TODO: Check for multiple group membership
     return result
 
+class IncludedFile(object):
+
+    """Information about an #include directive in a file."""
+
+    def __init__(self, path, lineno, included_file, included_path, is_relative, is_system):
+        self._path = path
+        self._line_number = lineno
+        self._included_file = included_file
+        self._included_path = included_path
+        #self._used_include_path = used_include_path
+        self._is_relative = is_relative
+        self._is_system = is_system
+
+    def __str__(self):
+        if self._is_system:
+            return '<{0}>'.format(self._included_path)
+        else:
+            return '"{0}"'.format(self._included_path)
+
+    def is_system(self):
+        return self._is_system
+
+    def is_relative(self):
+        return self._is_relative
+
+    def get_file(self):
+        return self._included_file
+
+    def get_reporter_location(self):
+        return reporter.Location(self._path, self._line_number)
+
 class File(object):
 
     """Source/header file in the GROMACS tree."""
@@ -84,6 +116,7 @@ class File(object):
         self._sourcefile = (extension in ('.c', '.cc', '.cpp', '.cu'))
         self._apitype = DocType.none
         self._modules = set()
+        self._includes = []
 
     def set_doc_xml(self, rawdoc, sourcetree):
         """Assiociate Doxygen documentation entity with the file."""
@@ -102,12 +135,44 @@ class File(object):
         """Mark the file installed."""
         self._installed = True
 
+    def _process_include(self, lineno, is_system, includedpath, sourcetree):
+        """Process #include directive during scan()."""
+        is_relative = False
+        if is_system:
+            fileobj = sourcetree.find_include_file(includedpath)
+        else:
+            fullpath = os.path.join(self._dir.get_path(), includedpath)
+            fullpath = os.path.abspath(fullpath)
+            if os.path.exists(fullpath):
+                is_relative = True
+                fileobj = sourcetree.get_file(fullpath)
+            else:
+                fileobj = sourcetree.find_include_file(includedpath)
+        self._includes.append(IncludedFile(self.get_path(), lineno, fileobj, includedpath,
+                is_relative, is_system))
+
+    def scan_contents(self, sourcetree):
+        """Scan the file contents and initialize information based on it."""
+        # TODO: Consider a more robust regex.
+        include_re = r'^#\s*include\s+(?P<quote>["<])(?P<path>[^">]*)[">]'
+        with open(self._path, 'r') as scanfile:
+            for lineno, line in enumerate(scanfile, 1):
+                match = re.match(include_re, line)
+                if match:
+                    is_system = (match.group('quote') == '<')
+                    includedpath = match.group('path')
+                    self._process_include(lineno, is_system, includedpath,
+                            sourcetree)
+
     def get_reporter_location(self):
         return reporter.Location(self._path, None)
 
     def is_installed(self):
         return self._installed
 
+    def is_external(self):
+        return self._dir.is_external()
+
     def is_source_file(self):
         return self._sourcefile
 
@@ -137,6 +202,15 @@ class File(object):
     def get_doc_modules(self):
         return self._modules
 
+    def get_module(self):
+        module = self.get_expected_module()
+        if not module and len(self._modules) == 1:
+            module = list(self._modules)[0]
+        return module
+
+    def get_includes(self):
+        return self._includes
+
 class GeneratedFile(File):
     pass
 
@@ -155,6 +229,10 @@ class Directory(object):
         if parent and parent.is_test_directory() or \
                 os.path.basename(path) in ('tests', 'legacytests'):
             self._is_test_dir = True
+        self._is_external = False
+        if parent and parent.is_external() or \
+                os.path.basename(path) == 'external':
+            self._is_external = True
         self._subdirs = set()
         if parent:
             parent._subdirs.add(self)
@@ -175,9 +253,15 @@ class Directory(object):
     def get_reporter_location(self):
         return reporter.Location(self._path, None)
 
+    def get_path(self):
+        return self._path
+
     def is_test_directory(self):
         return self._is_test_dir
 
+    def is_external(self):
+        return self._is_external
+
     def get_module(self):
         if self._module:
             return self._module
@@ -263,6 +347,13 @@ class GromacsTree(object):
     set_installed_file_list() can be called to set the list of installed
     files.
 
+    scan_files() can be called to read all the files and initialize #include
+    dependencies between the files based on the information.  This is done like
+    this instead of relying on Doxygen-extracted include files to make the
+    dependency graph independent from preprocessor macro definitions
+    (Doxygen only sees those #includes that the preprocessor sees, which
+    depends on what #defines it has seen).
+
     load_xml() can be called to load information from Doxygen XML data in
     the build tree (the Doxygen XML data must have been built separately).
     """
@@ -313,7 +404,7 @@ class GromacsTree(object):
                     continue
                 relpath = self._get_rel_path(fullpath)
                 self._dirs[relpath] = Directory(fullpath, currentdir)
-            extensions = ('.h', '.cuh', '.hpp', '.c', '.cc', '.cpp', '.cu')
+            extensions = ('.h', '.cuh', '.hpp', '.c', '.cc', '.cpp', '.cu', '.bm')
             for filename in filenames:
                 basename, extension = os.path.splitext(filename)
                 if extension in extensions:
@@ -335,6 +426,12 @@ class GromacsTree(object):
         rootdir.set_module(moduleobj)
         self._modules[name] = moduleobj
 
+    def scan_files(self):
+        """Read source files to initialize #include dependencies."""
+        for fileobj in self._files.itervalues():
+            if not fileobj.is_external():
+                fileobj.scan_contents(self)
+
     def load_xml(self):
         """Load Doxygen XML information."""
         xmldir = os.path.join(self._build_root, 'doxygen', 'xml')
@@ -422,6 +519,17 @@ class GromacsTree(object):
         """Get directory object for a path relative to source tree root."""
         return self._dirs.get(relpath)
 
+    def get_file(self, path):
+        """Get file object for a path relative to source tree root."""
+        return self._files.get(self._get_rel_path(path))
+
+    def find_include_file(self, includedpath):
+        """Find a file object corresponding to an include path."""
+        for testdir in ('src', 'src/gromacs/legacyheaders', 'src/external/thread_mpi/include'):
+            testpath = os.path.join(testdir, includedpath)
+            if testpath in self._files:
+                return self._files[testpath]
+
     def set_installed_file_list(self, installedfiles):
         """Set list of installed files."""
         for path in installedfiles:
index f4946e57d77dac66fd2bb665e45819652c07d4b1..a1c2b34989937db525133571c3713cc94a7795e7 100644 (file)
@@ -13,3 +13,18 @@ share/template/template.cpp: error: source file documentation appears outside fu
 src/gromacs/imd/imd.c: error: source file documentation appears outside full documentation
 src/gromacs/imd/imdsocket.c: error: source file documentation appears outside full documentation
 src/gromacs/imd/imd.h: note: init_IMD: has in-body comments, which are ignored
+src/gromacs/legacyheaders/types/*.h: warning: included file "../../swap/enums.h" is not documented as exposed outside its module
+
+# These would be nice to fix, but can wait for later
+*: warning: includes local file as <config.h>
+src/programs/view/*: warning: includes local file as <*.h>
+src/gromacs/gmxlib/nonbonded/nb_kernel_*/*: warning: included file "gromacs/simd/math_x86_*.h" is not documented as exposed outside its module
+src/gromacs/gmxlib/copyrite.cpp: warning: included file "gromacs/utility/gitversion.h" is not documented as exposed outside its module
+
+# These are specific to Folding@Home, and easiest to suppress here
+*: warning: includes non-local file as "corewrap.h"
+src/config.h.cmakein: warning: includes non-local file as "swindirect.h"
+
+# These are limitations in the current script
+src/gromacs/version.h.cmakein: warning: includes local file as <gromacs/version.h>
+src/gromacs/utility/gmx_header_config.h: warning: includes non-local file as "gmx_header_config_gen.h"