3 # This file is part of the GROMACS molecular simulation package.
5 # Copyright (c) 2014, by the GROMACS development team, led by
6 # Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 # and including many others, as listed in the AUTHORS file in the
8 # top-level source directory and at http://www.gromacs.org.
10 # GROMACS is free software; you can redistribute it and/or
11 # modify it under the terms of the GNU Lesser General Public License
12 # as published by the Free Software Foundation; either version 2.1
13 # of the License, or (at your option) any later version.
15 # GROMACS is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 # Lesser General Public License for more details.
20 # You should have received a copy of the GNU Lesser General Public
21 # License along with GROMACS; if not, see
22 # http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 # If you want to redistribute modifications to GROMACS, please
26 # consider that scientific software is very special. Version
27 # control is crucial - bugs must be traceable. We will be happy to
28 # consider code for inclusion in the official distribution, but
29 # derived work must not be called official GROMACS. Details are found
30 # in the README & COPYING files - if they are missing, get the
31 # official version at http://www.gromacs.org.
33 # To help us fund GROMACS development, we humbly ask that you cite
34 # the research papers on the package. Check out http://www.gromacs.org.
36 """Check Doxygen documentation for issues that Doxygen does not warn about.
38 This script for some issues in the Doxygen documentation, using Doxygen XML
39 output. Part of the checks are generic, like checking that all documented
40 entities have brief descriptions. Other are specific to GROMACS, like checking
41 that only installed headers contribute to the public API documentation.
43 The checks should be self-evident from the source code of the script.
44 All the logic of parsing the Doxygen XML output and creating a GROMACS-specific
45 representation of the source tree is separated into separate Python modules
46 (doxygenxml.py and gmxtree.py, respectively). Similarly, logic for handling
47 the output messages is in reporter.py. This leaves only the actual checks and
48 the script command-line interface in this file.
50 The script can be run using the 'doc-check' target generated by CMake.
51 This target takes care of generating all the necessary input files and passing
56 from optparse import OptionParser
58 from gmxtree import GromacsTree, DocType
59 from reporter import Reporter
61 def check_file(fileobj, reporter):
62 """Check file-level documentation."""
63 if not fileobj.is_documented():
64 # TODO: Add rules for required documentation
67 if fileobj.is_source_file():
68 # TODO: Add rule to exclude examples from this check
69 if fileobj.is_installed():
70 reporter.file_error(fileobj, "source file is installed")
71 if fileobj.get_doc_type() != DocType.internal:
72 reporter.file_error(fileobj,
73 "source file documentation appears outside full documentation")
74 elif fileobj.get_api_type() != DocType.internal:
75 reporter.file_error(fileobj, "source file marked as non-internal")
76 elif fileobj.is_test_file() and fileobj.is_installed():
77 reporter.file_error(fileobj, "test file is installed")
78 elif fileobj.is_installed():
79 if fileobj.get_doc_type() != DocType.public:
80 reporter.file_error(fileobj,
81 "public header has non-public documentation")
82 elif fileobj.get_doc_type() == DocType.public:
83 reporter.file_error(fileobj,
84 "non-installed header has public documentation")
85 elif fileobj.get_api_type() == DocType.public:
86 reporter.file_error(fileobj,
87 "non-installed header specified as part of public API")
88 elif fileobj.get_doc_type() < fileobj.get_api_type():
89 reporter.file_error(fileobj,
90 "API type ({0}) conflicts with documentation visibility ({1})"
91 .format(fileobj.get_api_type(), fileobj.get_doc_type()))
93 if not fileobj.has_brief_description():
94 reporter.file_error(fileobj,
95 "is documented, but does not have brief description")
97 expectedmod = fileobj.get_expected_module()
99 docmodules = fileobj.get_doc_modules()
101 for module in docmodules:
102 if module != expectedmod:
103 reporter.file_error(fileobj,
104 "is documented in incorrect module: {0}"
105 .format(module.get_name()))
106 elif expectedmod.is_documented():
107 reporter.file_error(fileobj,
108 "is not documented in any module, but {0} exists"
109 .format(expectedmod.get_name()))
111 def check_include(fileobj, includedfile, reporter):
112 """Check an #include directive."""
113 if includedfile.is_system():
114 if includedfile.get_file():
115 reporter.code_issue(includedfile,
116 "includes local file as {0}".format(includedfile))
118 otherfile = includedfile.get_file()
120 reporter.code_issue(includedfile,
121 "includes non-local file as {0}".format(includedfile))
122 elif fileobj.is_installed() and not includedfile.is_relative():
123 reporter.code_issue(includedfile,
124 "installed header includes {0} using non-relative path"
125 .format(includedfile))
128 if fileobj.is_installed() and not otherfile.is_installed():
129 reporter.code_issue(includedfile,
130 "installed header includes non-installed {0}"
131 .format(includedfile))
132 filemodule = fileobj.get_module()
133 othermodule = otherfile.get_module()
134 if fileobj.is_documented() and otherfile.is_documented():
135 filetype = fileobj.get_doc_type()
136 othertype = otherfile.get_doc_type()
137 if filetype > othertype:
138 reporter.code_issue(includedfile,
139 "{0} file includes {1} file {2}"
140 .format(filetype, othertype, includedfile))
141 check_api = (otherfile.api_type_is_reliable() and filemodule != othermodule)
142 if check_api and otherfile.get_api_type() < DocType.library:
143 reporter.code_issue(includedfile,
144 "included file {0} is not documented as exposed outside its module"
145 .format(includedfile))
147 def check_entity(entity, reporter):
148 """Check documentation for a code construct."""
149 if entity.is_documented():
150 if not entity.has_brief_description():
151 reporter.doc_error(entity,
152 "is documented, but does not have brief description")
154 def check_class(classobj, reporter):
155 """Check documentation for a class/struct/union."""
156 check_entity(classobj, reporter)
157 if classobj.is_documented():
158 classtype = classobj.get_doc_type()
159 filetype = classobj.get_file_doc_type()
160 if classtype == DocType.public and not classobj.is_in_installed_file():
161 reporter.doc_error(classobj,
162 "has public documentation, but is not in installed header")
163 elif filetype is not DocType.none and classtype > filetype:
164 reporter.doc_error(classobj,
165 "is in {0} file(s), but appears in {1} documentation"
166 .format(filetype, classtype))
168 def check_member(member, reporter, check_ignored):
169 """Check documentation for a generic member."""
170 check_entity(member, reporter)
171 if member.is_documented():
172 if check_ignored and not member.is_visible():
173 reporter.doc_note(member,
174 "is documented, but is ignored by Doxygen, because its scope is not documented")
175 if member.has_inbody_description():
176 reporter.doc_note(member, "has in-body comments, which are ignored")
179 """Run the checking script."""
180 parser = OptionParser()
181 parser.add_option('-S', '--source-root',
182 help='Source tree root directory')
183 parser.add_option('-B', '--build-root',
184 help='Build tree root directory')
185 parser.add_option('--installed',
186 help='Read list of installed files from given file')
187 parser.add_option('-l', '--log',
188 help='Write issues into a given log file in addition to stderr')
189 parser.add_option('--ignore',
190 help='Set file with patterns for messages to ignore')
191 parser.add_option('--check-ignored', action='store_true',
192 help='Issue notes for comments ignored by Doxygen')
193 parser.add_option('-q', '--quiet', action='store_true',
194 help='Do not write status messages')
195 options, args = parser.parse_args()
198 if options.installed:
199 with open(options.installed, 'r') as outfile:
201 installedlist.append(line.strip())
203 reporter = Reporter(options.log)
205 reporter.load_filters(options.ignore)
207 if not options.quiet:
208 sys.stderr.write('Scanning source tree...\n')
209 tree = GromacsTree(options.source_root, options.build_root, reporter)
210 tree.set_installed_file_list(installedlist)
211 if not options.quiet:
212 sys.stderr.write('Reading source files...\n')
214 if not options.quiet:
215 sys.stderr.write('Reading Doxygen XML files...\n')
218 reporter.write_pending()
220 if not options.quiet:
221 sys.stderr.write('Checking...\n')
223 for fileobj in tree.get_files():
224 check_file(fileobj, reporter)
225 for includedfile in fileobj.get_includes():
226 check_include(fileobj, includedfile, reporter)
228 for classobj in tree.get_classes():
229 check_class(classobj, reporter)
231 for memberobj in tree.get_members():
232 check_member(memberobj, reporter, options.check_ignored)
234 reporter.write_pending()
235 reporter.report_unused_filters()