customizations will be added.
"""
+import collections
import os
import os.path
import re
def is_relative(self):
return self._is_relative
+ def get_included_path(self):
+ return self._included_path
+
def get_including_file(self):
return self._including_file
def get_file(self):
return self._included_file
+ def get_line_number(self):
+ return self._line_number
+
def get_reporter_location(self):
return reporter.Location(self._including_file.get_abspath(), self._line_number)
+class IncludeBlock(object):
+
+ """Block of consequent #include directives in a file."""
+
+ def __init__(self, first_included_file):
+ self._first_line = first_included_file.get_line_number()
+ self._last_line = self._first_line
+ self._files = []
+ self.add_file(first_included_file)
+
+ def add_file(self, included_file):
+ self._files.append(included_file)
+ self._last_line = included_file.get_line_number()
+
+ def get_includes(self):
+ return self._files
+
+ def get_first_line(self):
+ return self._first_line
+
+ def get_last_line(self):
+ return self._last_line
+
class File(object):
"""Source/header file in the GROMACS tree."""
self._apitype = DocType.none
self._modules = set()
self._includes = []
+ self._include_blocks = []
+ self._main_header = None
+ self._lines = None
directory.add_file(self)
def set_doc_xml(self, rawdoc, sourcetree):
"""Mark the file installed."""
self._installed = True
+ def set_main_header(self, included_file):
+ """Set the main header file for a source file."""
+ assert self.is_source_file()
+ self._main_header = included_file
+
def _process_include(self, lineno, is_system, includedpath, sourcetree):
"""Process #include directive during scan()."""
is_relative = False
fileobj = sourcetree.get_file(fullpath)
else:
fileobj = sourcetree.find_include_file(includedpath)
- self._includes.append(IncludedFile(self, lineno, fileobj, includedpath,
- is_relative, is_system))
+ included_file = IncludedFile(self, lineno, fileobj, includedpath,
+ is_relative, is_system)
+ self._includes.append(included_file)
+ return included_file
- def scan_contents(self, sourcetree):
+ def scan_contents(self, sourcetree, keep_contents):
"""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>[^">]*)[">]'
+ include_re = r'^\s*#\s*include\s+(?P<quote>["<])(?P<path>[^">]*)[">]'
+ current_block = None
+ # TODO: Consider reading directly into this list, and iterate that.
+ lines = []
with open(self._abspath, 'r') as scanfile:
for lineno, line in enumerate(scanfile, 1):
+ lines.append(line)
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)
+ included_file = self._process_include(lineno, is_system,
+ includedpath, sourcetree)
+ if current_block is None:
+ current_block = IncludeBlock(included_file)
+ self._include_blocks.append(current_block)
+ else:
+ current_block.add_file(included_file)
+ elif line and not line.isspace():
+ current_block = None
+ if keep_contents:
+ self._lines = lines
def get_reporter_location(self):
return reporter.Location(self._abspath, None)
def get_name(self):
return os.path.basename(self._abspath)
+ def get_directory(self):
+ return self._dir
+
def get_doc_type(self):
if not self._rawdoc:
return DocType.none
return self._apitype
def api_type_is_reliable(self):
- if self._apitype > DocType.internal:
+ if self._apitype in (DocType.internal, DocType.library):
return True
module = self.get_module()
return module and module.is_documented()
def get_includes(self):
return self._includes
+ def get_include_blocks(self):
+ return self._include_blocks
+
+ def get_main_header(self):
+ return self._main_header
+
+ def get_contents(self):
+ return self._lines
+
class GeneratedFile(File):
+ def __init__(self, abspath, relpath, directory):
+ File.__init__(self, abspath, relpath, directory)
+ self._generator_source_file = None
+
+ def set_generator_source(self, sourcefile):
+ self._generator_source_file = sourcefile
+
+ def get_reporter_location(self):
+ if self._generator_source_file:
+ return self._generator_source_file.get_reporter_location()
+ return File.get_reporter_location(self)
+
+class GeneratorSourceFile(File):
pass
class Directory(object):
for fileobj in self._files:
yield fileobj
+ def contains(self, fileobj):
+ """Check whether file is within the directory or its subdirectories."""
+ dirobj = fileobj.get_directory()
+ while dirobj:
+ if dirobj == self:
+ return True
+ dirobj = dirobj._parent
+ return False
+
class ModuleDependency(object):
"""Dependency between modules."""
subdirectories. At this point, only information that is accessible from
file names and paths only is available.
- set_installed_file_list() can be called to set the list of installed
- files.
+ load_installed_file_list() can be called to load the list of installed
+ files from the build tree (generated by the find-installed-headers target).
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
self._namespaces = set()
self._members = set()
self._walk_dir(os.path.join(self._source_root, 'src'))
+ for fileobj in self.get_files():
+ if fileobj and fileobj.is_source_file() and not fileobj.is_external():
+ (basedir, name) = os.path.split(fileobj.get_abspath())
+ (basename, ext) = os.path.splitext(name)
+ header = self.get_file(os.path.join(basedir, basename + '.h'))
+ if not header and ext == '.cu':
+ header = self.get_file(os.path.join(basedir, basename + '.cuh'))
+ if not header and fileobj.is_test_file():
+ basedir = os.path.dirname(basedir)
+ header = self.get_file(os.path.join(basedir, basename + '.h'))
+ if not header:
+ # Somewhat of a hack; currently, the tests for
+ # analysisdata/modules/ and trajectoryanalysis/modules/
+ # is at the top-level tests directory.
+ # TODO: It could be clearer to split the tests so that
+ # there would be a separate modules/tests/.
+ header = self.get_file(os.path.join(basedir, 'modules', basename + '.h'))
+ if not header and basename.endswith('_tests'):
+ header = self.get_file(os.path.join(basedir, basename[:-6] + '.h'))
+ if not header and fileobj.get_relpath().startswith('src/gromacs'):
+ header = self._files.get(os.path.join('src/gromacs/legacyheaders', basename + '.h'))
+ if header:
+ fileobj.set_main_header(header)
rootdir = self._get_dir(os.path.join('src', 'gromacs'))
for subdir in rootdir.get_subdirectories():
self._create_module(subdir)
elif extension == '.cmakein':
extension = os.path.splitext(basename)[1]
if extension in extensions:
+ fullpath = os.path.join(dirpath, filename)
+ relpath = self._get_rel_path(fullpath)
+ sourcefile = GeneratorSourceFile(fullpath, relpath, currentdir)
+ self._files[relpath] = sourcefile
fullpath = os.path.join(dirpath, basename)
relpath = self._get_rel_path(fullpath)
- fullpath = os.path.join(dirpath, filename)
- self._files[relpath] = GeneratedFile(fullpath, relpath, currentdir)
+ fullpath = os.path.join(self._build_root, relpath)
+ generatedfile = GeneratedFile(fullpath, relpath, currentdir)
+ self._files[relpath] = generatedfile
+ generatedfile.set_generator_source(sourcefile)
+ elif extension in ('.l', '.y', '.pre'):
+ fullpath = os.path.join(dirpath, filename)
+ relpath = self._get_rel_path(fullpath)
+ self._files[relpath] = GeneratorSourceFile(fullpath, relpath, currentdir)
def _create_module(self, rootdir):
"""Create module for a subdirectory."""
rootdir.set_module(moduleobj)
self._modules[name] = moduleobj
- def scan_files(self):
+ def scan_files(self, only_files=None, keep_contents=False):
"""Read source files to initialize #include dependencies."""
- for fileobj in self._files.itervalues():
+ if only_files:
+ filelist = only_files
+ else:
+ filelist = self._files.itervalues()
+ for fileobj in filelist:
if not fileobj.is_external():
- fileobj.scan_contents(self)
+ fileobj.scan_contents(self, keep_contents)
module = fileobj.get_module()
if module:
for includedfile in fileobj.get_includes():
if othermodule and othermodule != module:
module.add_dependency(othermodule, includedfile)
- def load_xml(self, only_files=False):
+ def load_xml(self, only_files=None):
"""Load Doxygen XML information.
If only_files is True, XML data is not loaded for code constructs, but
xmldir = os.path.join(self._build_root, 'docs', 'html', 'doxygen', 'xml')
self._docset = xml.DocumentationSet(xmldir, self._reporter)
if only_files:
- self._docset.load_file_details()
+ if isinstance(only_files, collections.Iterable):
+ filelist = [x.get_abspath() for x in only_files]
+ self._docset.load_file_details(filelist)
+ else:
+ self._docset.load_file_details()
else:
self._docset.load_details()
self._docset.merge_duplicates()
"""Load Doxygen XML file information."""
for filedoc in self._docset.get_files():
path = filedoc.get_path()
+ if not path:
+ # In case of only partially loaded file information,
+ # the path information is not set for unloaded files.
+ continue
if not os.path.isabs(path):
self._reporter.xml_assert(filedoc.get_xml_path(),
"expected absolute path in Doxygen-produced XML file")
continue
- extension = os.path.splitext(filedoc.get_path())[1]
+ extension = os.path.splitext(path)[1]
# We don't care about Markdown files that only produce pages
# (and fail the directory check below).
if extension == '.md':
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:
- if not os.path.isabs(path):
- self._reporter.input_error(
- "installed file not specified with absolute path: {0}"
- .format(path))
- continue
- relpath = self._get_rel_path(path)
- if relpath not in self._files:
- self._reporter.input_error(
- "installed file not in source tree: {0}".format(path))
- continue
- self._files[relpath].set_installed()
+ def load_installed_file_list(self):
+ """Load list of installed files from the build tree."""
+ listpath = os.path.join(self._build_root, 'docs', 'doxygen', 'installed-headers.txt')
+ with open(listpath, 'r') as installedfp:
+ for line in installedfp:
+ path = line.strip()
+ if not os.path.isabs(path):
+ self._reporter.input_error(
+ "installed file not specified with absolute path: {0}"
+ .format(path))
+ continue
+ relpath = self._get_rel_path(path)
+ if relpath not in self._files:
+ self._reporter.input_error(
+ "installed file not in source tree: {0}".format(path))
+ continue
+ self._files[relpath].set_installed()
def load_cycle_suppression_list(self, filename):
"""Load a list of edges to suppress in cycles.