| """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 |