"""Information about an #include directive in a file."""
- def __init__(self, abspath, lineno, included_file, included_path, is_relative, is_system):
- self._abspath = abspath
+ def __init__(self, including_file, lineno, included_file, included_path, is_relative, is_system):
+ self._including_file = including_file
self._line_number = lineno
self._included_file = included_file
self._included_path = included_path
def is_relative(self):
return self._is_relative
+ def get_including_file(self):
+ return self._including_file
+
def get_file(self):
return self._included_file
def get_reporter_location(self):
- return reporter.Location(self._abspath, self._line_number)
+ return reporter.Location(self._including_file.get_abspath(), self._line_number)
class File(object):
fileobj = sourcetree.get_file(fullpath)
else:
fileobj = sourcetree.find_include_file(includedpath)
- self._includes.append(IncludedFile(self.get_abspath(), lineno, fileobj, includedpath,
+ self._includes.append(IncludedFile(self, lineno, fileobj, includedpath,
is_relative, is_system))
def scan_contents(self, sourcetree):
def get_name(self):
return os.path.basename(self._abspath)
- def get_documentation_type(self):
+ def get_doc_type(self):
if not self._rawdoc:
return DocType.none
return self._rawdoc.get_visibility()
def get_api_type(self):
return self._apitype
+ def api_type_is_reliable(self):
+ if self._apitype > DocType.internal:
+ return True
+ module = self.get_module()
+ return module and module.is_documented()
+
+ def is_public(self):
+ if self.api_type_is_reliable():
+ return self.get_api_type() == DocType.public
+ return self.get_api_type() == DocType.public or self.is_installed()
+
+ def is_module_internal(self):
+ if self.is_source_file():
+ return True
+ return not self.is_installed() and self.get_api_type() <= DocType.internal
+
def get_expected_module(self):
return self._dir.get_module()
for fileobj in self._files:
yield fileobj
+class ModuleDependency(object):
+
+ """Dependency between modules."""
+
+ def __init__(self, othermodule):
+ """Initialize empty dependency object with given module as dependency."""
+ self._othermodule = othermodule
+ self._includedfiles = []
+ self._cyclesuppression = None
+
+ def add_included_file(self, includedfile):
+ """Add IncludedFile that is part of this dependency."""
+ assert includedfile.get_file().get_module() == self._othermodule
+ self._includedfiles.append(includedfile)
+
+ def set_cycle_suppression(self):
+ """Set suppression on cycles containing this dependency."""
+ self._cyclesuppression = True
+
+ def is_cycle_suppressed(self):
+ """Return whether cycles containing this dependency are suppressed."""
+ return self._cyclesuppression is not None
+
+ def get_other_module(self):
+ """Get module that this dependency is to."""
+ return self._othermodule
+
+ def get_included_files(self):
+ """Get IncludedFile objects for the individual include dependencies."""
+ return self._includedfiles
+
class Module(object):
"""Code module in the GROMACS source tree.
self._rawdoc = None
self._rootdir = rootdir
self._group = None
+ self._dependencies = dict()
def set_doc_xml(self, rawdoc, sourcetree):
"""Assiociate Doxygen documentation entity with the module."""
if groupname.startswith('group_'):
self._group = groupname[6:]
+ def add_dependency(self, othermodule, includedfile):
+ """Add #include dependency from a file in this module."""
+ assert includedfile.get_file().get_module() == othermodule
+ if othermodule not in self._dependencies:
+ self._dependencies[othermodule] = ModuleDependency(othermodule)
+ self._dependencies[othermodule].add_included_file(includedfile)
+
def is_documented(self):
return self._rawdoc is not None
def get_group(self):
return self._group
+ def get_dependencies(self):
+ return self._dependencies.itervalues()
+
+class Namespace(object):
+
+ """Namespace in the GROMACS source code."""
+
+ def __init__(self, rawdoc):
+ self._rawdoc = rawdoc
+
+ def is_anonymous(self):
+ return self._rawdoc.is_anonymous()
class Class(object):
def has_brief_description(self):
return self._rawdoc.has_brief_description()
- def get_documentation_type(self):
+ def get_doc_type(self):
+ """Return documentation type (visibility) for the class.
+
+ In addition to the actual code, this encodes GROMACS-specific logic
+ of setting EXTRACT_LOCAL_CLASSES=YES only for the full documentation.
+ Local classes never appear outside the full documentation, no matter
+ what is their visibility.
+ """
if not self.is_documented():
return DocType.none
if self._rawdoc.is_local():
return DocType.internal
return self._rawdoc.get_visibility()
- def get_file_documentation_type(self):
- return max([fileobj.get_documentation_type() for fileobj in self._files])
+ def get_file_doc_type(self):
+ return max([fileobj.get_doc_type() for fileobj in self._files])
def is_in_installed_file(self):
return any([fileobj.is_installed() for fileobj in self._files])
+class Member(object):
+
+ """Member (in Doxygen terminology) in the GROMACS source tree.
+
+ Currently, modeling is limited to the minimal set of properties that the
+ checker uses.
+ """
+
+ def __init__(self, rawdoc, namespace):
+ self._rawdoc = rawdoc
+ self._namespace = namespace
+
+ def get_name(self):
+ return self._rawdoc.get_name()
+
+ def get_reporter_location(self):
+ return self._rawdoc.get_reporter_location()
+
+ def is_documented(self):
+ return self._rawdoc.is_documented()
+
+ def has_brief_description(self):
+ return self._rawdoc.has_brief_description()
+
+ def has_inbody_description(self):
+ return self._rawdoc.has_inbody_description()
+
+ def is_visible(self):
+ """Return whether the member is visible in Doxygen documentation.
+
+ Doxygen ignores members whose parent compounds are not documented.
+ However, when EXTRACT_ANON_NPACES=ON (which is set for our full
+ documentation), members of anonymous namespaces are extracted even if
+ the namespace is the only parent and is not documented.
+ """
+ if self._namespace and self._namespace.is_anonymous():
+ return True
+ return self._rawdoc.get_inherited_visibility() != DocType.none
+
+
class GromacsTree(object):
"""Root object for navigating the GROMACS source tree.
self._files = dict()
self._modules = dict()
self._classes = set()
+ self._namespaces = set()
+ self._members = set()
self._walk_dir(os.path.join(self._source_root, 'src'))
rootdir = self._get_dir(os.path.join('src', 'gromacs'))
for subdir in rootdir.get_subdirectories():
for fileobj in self._files.itervalues():
if not fileobj.is_external():
fileobj.scan_contents(self)
+ module = fileobj.get_module()
+ if module:
+ for includedfile in fileobj.get_includes():
+ otherfile = includedfile.get_file()
+ if otherfile:
+ othermodule = otherfile.get_module()
+ if othermodule and othermodule != module:
+ module.add_dependency(othermodule, includedfile)
def load_xml(self, only_files=False):
"""Load Doxygen XML information.
self._load_modules()
self._load_files()
if not only_files:
+ self._load_namespaces()
self._load_classes()
+ self._load_members()
def _load_dirs(self):
"""Load Doxygen XML directory information."""
fileobj.set_doc_xml(filedoc, self)
self._docmap[filedoc] = fileobj
+ def _load_namespaces(self):
+ """Load Doxygen XML namespace information."""
+ nsdocs = self._docset.get_namespaces()
+ for nsdoc in nsdocs:
+ nsobj = Namespace(nsdoc)
+ self._docmap[nsdoc] = nsobj
+ self._namespaces.add(nsobj)
+
def _load_classes(self):
"""Load Doxygen XML class information."""
classdocs = self._docset.get_classes()
self._docmap[classdoc] = classobj
self._classes.add(classobj)
+ def _load_members(self):
+ """Load Doxygen XML member information."""
+ memberdocs = self._docset.get_members()
+ for memberdoc in memberdocs:
+ nsdoc = memberdoc.get_namespace()
+ nsobj = self.get_object(nsdoc)
+ memberobj = Member(memberdoc, nsobj)
+ self._docmap[memberdoc] = memberobj
+ self._members.add(memberobj)
+
def _get_dir(self, relpath):
"""Get directory object for a path relative to source tree root."""
return self._dirs.get(relpath)
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'):
+ for testdir in ('src', 'src/external/thread_mpi/include',
+ 'src/external/tng_io/include'):
testpath = os.path.join(testdir, includedpath)
if testpath in self._files:
return self._files[testpath]
continue
self._files[relpath].set_installed()
+ def load_cycle_suppression_list(self, filename):
+ """Load a list of edges to suppress in cycles.
+
+ These edges between modules, if present, will be marked in the
+ corresponding ModuleDependency objects.
+ """
+ with open(filename, 'r') as fp:
+ for line in fp:
+ line = line.strip()
+ if not line or line.startswith('#'):
+ continue
+ modulenames = ['module_' + x.strip() for x in line.split('->')]
+ if len(modulenames) != 2:
+ self._reporter.input_error(
+ "invalid cycle suppression line: {0}".format(line))
+ continue
+ firstmodule = self._modules.get(modulenames[0])
+ secondmodule = self._modules.get(modulenames[1])
+ if not firstmodule or not secondmodule:
+ self._reporter.input_error(
+ "unknown modules mentioned on cycle suppression line: {0}".format(line))
+ continue
+ for dep in firstmodule.get_dependencies():
+ if dep.get_other_module() == secondmodule:
+ # TODO: Check that each suppression is actually part of
+ # a cycle.
+ dep.set_cycle_suppression()
+
def get_object(self, docobj):
"""Get tree object for a Doxygen XML object."""
+ if docobj is None:
+ return None
return self._docmap.get(docobj)
def get_files(self):
def get_members(self):
"""Get iterable for all members (in Doxygen terms) in the source tree."""
- # TODO: Add wrappers to solve some issues.
- return self._docset.get_members()
+ return self._members