blob: 499e2581716087bae7e5ab4aa13327ad12bc0308 [file] [log] [blame]
# Copyright 2017 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Python wrapper for Doxygen."""
import errno
import logging
import os
import re
import subprocess
import environment
_MODULE_OVERVIEW_PATTERN = r'Module Overview: '
_MODULE_OVERVIEW_RE = re.compile(_MODULE_OVERVIEW_PATTERN)
def _subprocess(command, working_directory):
"""Executes command in working_directory."""
if not os.path.isdir(working_directory):
raise RuntimeError(
f'Running {command}: directory {working_directory} not found')
try:
# shell=True for Windows to be able to find git in the PATH.
subprocess.check_output(
' '.join(command),
shell=True,
cwd=working_directory,
stderr=subprocess.STDOUT)
return True
except subprocess.CalledProcessError as e:
logging.warning('%s: \"%s\" failed. Return Code: %d', working_directory,
e.cmd, e.returncode)
logging.debug('>>>\n%s\n<<<', e.output)
return False
def _mkdirs(directory_path):
"""Makes the given path and all ancestors necessary."""
try:
os.makedirs(directory_path)
except OSError as e:
if e.errno == errno.EEXIST and os.path.isdir(directory_path):
pass
else:
raise
def _join_config(lines):
return ' \\\n '.join(lines)
def doxygen(project_number, input_files, predefined_macros,
output_directory_path):
"""Runs doxygen with the given semantic parameters."""
doxyfile_template_path = os.path.join(environment.SCRIPTS_DIR,
'doxyfile.template')
doxyfile_template = environment.read_file(doxyfile_template_path)
os.makedirs(output_directory_path)
predefined_macros = predefined_macros[:]
predefined_macros.append(f'SB_API_VERSION={project_number}')
doxyfile_contents = doxyfile_template.format(
**{
'project_number': 1,
'output_directory': output_directory_path,
'input_files': _join_config(input_files),
'predefined_macros': _join_config(predefined_macros),
})
doxyfile_path = os.path.join(output_directory_path, 'Doxyfile')
environment.write_file(doxyfile_path, doxyfile_contents)
_subprocess(['doxygen', doxyfile_path], output_directory_path)
def _split(line):
"""Splits a line into a (indentation, lstripped content) tuple."""
stripped = line.lstrip()
return line[:(len(line) - len(stripped))], stripped
def _doxygenate_line(line):
"""Adds an extra slash to a comment line."""
indent, stripped = _split(line)
return indent + '/' + stripped
def _doxygenate_lines(lines):
"""Makes a list of comment lines visible to Doxygen."""
if not lines:
return []
indent, _ = _split(lines[0])
return [_doxygenate_line(x) for x in lines] + [indent + '///']
def _is_comment(line):
"""Whether the given line is a comment."""
stripped = line.lstrip()
return stripped[0] == '/'
def _find_end_of_block(line_iterator):
"""Consumes the next comment block, returning (comment_list, terminator)."""
lines = []
last_line = None
any_lines = False
for line in line_iterator:
any_lines = True
if not line or not _is_comment(line):
last_line = line
break
lines.append(line)
if not any_lines:
raise StopIteration
return lines, last_line
def doxygenate(input_file_paths, output_directory):
"""Converts a list of source files into more doxygen-friendly files."""
common_prefix_path = os.path.commonprefix(input_file_paths)
output_file_paths = []
os.makedirs(output_directory)
for input_file_path in input_file_paths:
output_file_path = os.path.join(output_directory,
input_file_path[len(common_prefix_path):])
_mkdirs(os.path.dirname(output_file_path))
input_contents = environment.read_lines(input_file_path)
output_contents = []
line_iterator = iter(x.rstrip() for x in input_contents)
try:
# Remove copyright header.
_, _ = _find_end_of_block(line_iterator)
# Doxygenate module overview.
lines, last_line = _find_end_of_block(line_iterator)
if not lines:
continue
output_contents.append(f'/// \\file {os.path.basename(input_file_path)}')
if _MODULE_OVERVIEW_RE.search(lines[0]):
del lines[0]
output_contents.extend(_doxygenate_lines(lines))
output_contents.append(last_line)
# Doxygenate module overview.
lines, last_line = _find_end_of_block(line_iterator)
# Doxygenate the rest of the file, block by block.
while True:
lines, last_line = _find_end_of_block(line_iterator)
if not last_line:
continue
if lines:
output_contents.extend(_doxygenate_lines(lines))
output_contents.append(last_line)
except StopIteration:
pass
environment.write_file(output_file_path, '\n'.join(output_contents))
output_file_paths.append(output_file_path)
return output_file_paths