blob: e7a407406deec92940b02ae7b222451d5c5b7b0e [file]
"""Build step class for formatting C++ files with clang-format-diff."""
from __future__ import absolute_import
import logging
import os
from helpers import commit_message_utils
from helpers import diff_utils
from helpers import git_utils
from slave import base_step
CLANG_FORMAT_DIFF_SCRIPT = 'scripts/helpers/clang_format/clang-format-diff.py'
GIT_DIFF_COMMAND = ['git', 'diff', '-U0', 'HEAD^']
CLANG_FORMAT_ERROR_MSG = ('Fix format errors with `git cast format` '
'(see go/git-cast for more info)')
class ClangFormatStep(base_step.BaseStep):
"""Build step class for clang-formatting C++ files."""
def __init__(self, **kwargs):
"""Creates a ClangFormatStep instance.
Note that this step will only format files with the following extensions:
.cc, .cpp, .cxx, .c++, .c, .cl, .h, .hpp, .m, .mm, .inc, .js, .proto,
.protodevel, .java
Note that this step uses clang-format-diff, so that only the changed
parts of a file are re-formatted.
Args:
**kwargs: Any additional args to pass to BaseStep.
"""
base_step.BaseStep.__init__(self, name='clang-format', **kwargs)
def _filter_comments(self, comments):
for filename in list(comments):
if self.allow_listed(filename) and not self.deny_listed(filename):
continue
del comments[filename]
return comments
def _clang_format_binary(self):
"""Returns the path to the clang-format binary.
This first attempts to find the clang-format from buildtools, but will
fall back to the latest pre-installed clang format if it cannot find it.
Returns:
Path to the clang-format binary.
"""
try:
chromium_src = self.get_project_path('chromium/src')
clang_format_binary = os.path.join(chromium_src, 'buildtools', 'linux64',
'clang-format')
if os.path.exists(clang_format_binary):
return clang_format_binary
except KeyError:
pass
return 'clang-format-3.6'
def _clang_format_diff_script(self):
"""Returns the path of the clang-format-diff.py script."""
try:
chromium_src = self.get_project_path('chromium/src')
clang_format_diff_script = os.path.join(chromium_src, 'buildtools',
'clang_format', 'script',
'clang-format-diff.py')
if os.path.exists(clang_format_diff_script):
return clang_format_diff_script
except KeyError:
pass
return os.path.join(self.get_cq_root(), CLANG_FORMAT_DIFF_SCRIPT)
def run(self):
"""Runs clang-format-diff on changed files for the current commit.
Sets a build property "review" with the output.
Returns:
True iff there were no formatting errors.
"""
# Do not format cherry-picks; errors are ignored for upstream consistency
if git_utils.is_cherry_pick(self, self.directory):
logging.info('clang_format check skipped because this is a cherry-pick.')
return True
diff_command = (GIT_DIFF_COMMAND, {'cwd': self.directory})
clang_format_diff = os.path.relpath(self._clang_format_diff_script(),
self.directory)
clang_format = os.path.relpath(self._clang_format_binary(), self.directory)
format_command = (['python3',
clang_format_diff,
'-binary',
clang_format,
'-p1',
'-style',
'file',], {'cwd': self.directory})
returncode, stdout, _ = self.exec_subprocess_chain([diff_command,
format_command])
if returncode == 0 and not stdout:
logging.info('clang_format check received no output on stdout')
return True
comments = diff_utils.diff_to_comments(stdout.splitlines())
comments = self._filter_comments(comments)
if not comments:
# All the comments were filtered out, so this was actually a success.
logging.info('clang_format had no comments after filtering.')
return True
self.add_review({'message': CLANG_FORMAT_ERROR_MSG, 'comments': comments})
# Do not block the build if this is a revert.
commit_message = git_utils.commit_message(self, self.directory)
if commit_message_utils.is_revert_commit_message(commit_message):
logging.info('looks like a revert, allowing it through.')
return True
logging.info('clang_format failure, adding %d comments.', len(comments))
logging.info('If you believe that your project should be exempt from '
'formatting requirements you can follow the instructions at '
'go/cast-format-exemption in order to disable formatting checks '
'on your code. This is often useful for projects that pull code '
'from third-party sources.')
return False