new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/clang_format/commands.py
@@ -0,0 +1,141 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import files
+
+def get_clang_format_diff_command(repository):
+ if repository.name == 'hg':
+ args = ["hg", "diff", "-U0", "-r" ".^"]
+ for dot_extension in self._format_include_extensions:
+ args += ['--include', 'glob:**{0}'.format(dot_extension)]
+ args += ['--exclude', 'listfile:{0}'.format(self._format_ignore_file)]
+ else:
+ args = ["git", "diff", "--no-color", "-U0", "HEAD", "--"]
+ for dot_extension in self._format_include_extensions:
+ args += ['*{0}'.format(dot_extension)]
+ # git-diff doesn't support an 'exclude-from-files' param, but
+ # allow to add individual exclude pattern since v1.9, see
+ # https://git-scm.com/docs/gitglossary#gitglossary-aiddefpathspecapathspec
+ with open(self._format_ignore_file, 'rb') as exclude_pattern_file:
+ for pattern in exclude_pattern_file.readlines():
+ pattern = pattern.rstrip()
+ pattern = pattern.replace('.*', '**')
+ if not pattern or pattern.startswith('#'):
+ continue # empty or comment
+ magics = ['exclude']
+ if pattern.startswith('^'):
+ magics += ['top']
+ pattern = pattern[1:]
+ args += [':({0}){1}'.format(','.join(magics), pattern)]
+ return args
+
+def _run_clang_format_diff(self, clang_format_diff, clang_format, show):
+ # Run clang-format on the diff
+ # Note that this will potentially miss a lot things
+ from subprocess import Popen, PIPE, check_output, CalledProcessError
+
+ diff_process = Popen(self._get_clang_format_diff_command(), stdout=PIPE)
+ args = [sys.executable, clang_format_diff, "-p1", "-binary=%s" % clang_format]
+
+ if not show:
+ args.append("-i")
+ try:
+ output = check_output(args, stdin=diff_process.stdout)
+ if show:
+ # We want to print the diffs
+ print(output)
+ return 0
+ except CalledProcessError as e:
+ # Something wrong happend
+ print("clang-format: An error occured while running clang-format-diff.")
+ return e.returncode
+
+
+def run_clang_format_path(repository, clang_format, show, paths):
+ # Run clang-format on files or directories directly
+ from subprocess import check_output, CalledProcessError
+
+ args = [clang_format, "-i"]
+
+ path_list = files.generate_path_list("/home/sylvestre/dev/mozilla/mozilla-central.hg", paths)
+
+ if path_list == []:
+ return
+
+ print("Processing %d file(s)..." % len(path_list))
+
+ batchsize = 200
+
+ for i in range(0, len(path_list), batchsize):
+ l = path_list[i: (i + batchsize)]
+ # Run clang-format on the list
+ try:
+ check_output(args + l)
+ except CalledProcessError as e:
+ # Something wrong happend
+ print("clang-format: An error occured while running clang-format.")
+ return e.returncode
+
+ if show:
+ # show the diff
+ if repository.name == 'hg':
+ diff_command = ["hg", "diff"] + paths
+ else:
+ assert repository.name == 'git'
+ diff_command = ["git", "diff"] + paths
+ try:
+ output = check_output(diff_command)
+ print(output)
+ except CalledProcessError as e:
+ # Something wrong happend
+ print("clang-format: Unable to run the diff command.")
+ return e.returncode
+ return 0
+
+def get_clang_format_diff_command(repository):
+ if repository.name == 'hg':
+ args = ["hg", "diff", "-U0", "-r" ".^"]
+ for dot_extension in self._format_include_extensions:
+ args += ['--include', 'glob:**{0}'.format(dot_extension)]
+ args += ['--exclude', 'listfile:{0}'.format(self._format_ignore_file)]
+ else:
+ args = ["git", "diff", "--no-color", "-U0", "HEAD", "--"]
+ for dot_extension in self._format_include_extensions:
+ args += ['*{0}'.format(dot_extension)]
+ # git-diff doesn't support an 'exclude-from-files' param, but
+ # allow to add individual exclude pattern since v1.9, see
+ # https://git-scm.com/docs/gitglossary#gitglossary-aiddefpathspecapathspec
+ with open(self._format_ignore_file, 'rb') as exclude_pattern_file:
+ for pattern in exclude_pattern_file.readlines():
+ pattern = pattern.rstrip()
+ pattern = pattern.replace('.*', '**')
+ if not pattern or pattern.startswith('#'):
+ continue # empty or comment
+ magics = ['exclude']
+ if pattern.startswith('^'):
+ magics += ['top']
+ pattern = pattern[1:]
+ args += [':({0}){1}'.format(','.join(magics), pattern)]
+ return args
+
+def _run_clang_format_diff(self, clang_format_diff, clang_format, show):
+ # Run clang-format on the diff
+ # Note that this will potentially miss a lot things
+ from subprocess import Popen, PIPE, check_output, CalledProcessError
+
+ diff_process = Popen(self._get_clang_format_diff_command(), stdout=PIPE)
+ args = [sys.executable, clang_format_diff, "-p1", "-binary=%s" % clang_format]
+
+ if not show:
+ args.append("-i")
+ try:
+ output = check_output(args, stdin=diff_process.stdout)
+ if show:
+ # We want to print the diffs
+ print(output)
+ return 0
+ except CalledProcessError as e:
+ # Something wrong happend
+ print("clang-format: An error occured while running clang-format-diff.")
+ return e.returncode
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/clang_format/files.py
@@ -0,0 +1,57 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import re
+
+# List of file extension to consider (should start with dot)
+_format_include_extensions = ('.cpp', '.c', '.h')
+# File contaning all paths to exclude from formatting
+_format_ignore_file = '.clang-format-ignore'
+
+def is_ignored_path(topsrcdir, ignored_dir_re, f):
+ # Remove upto topsrcdir in pathname and match
+ if f.startswith(topsrcdir + '/'):
+ match_f = f[len(topsrcdir + '/'):]
+ else:
+ match_f = f
+ return re.match(ignored_dir_re, match_f)
+
+def generate_path_list(topsrcdir, paths):
+ path_to_third_party = os.path.join(topsrcdir, _format_ignore_file)
+ ignored_dir = []
+ with open(path_to_third_party, 'r') as fh:
+ for line in fh:
+ # Remove comments and empty lines
+ if line.startswith('#') or len(line.strip()) == 0:
+ continue
+ # The regexp is to make sure we are managing relative paths
+ ignored_dir.append(r"^[\./]*" + line.rstrip())
+
+ # Generates the list of regexp
+ ignored_dir_re = '(%s)' % '|'.join(ignored_dir)
+ extensions = _format_include_extensions
+
+ path_list = []
+ for f in paths:
+ if is_ignored_path(topsrcdir, ignored_dir_re, f):
+ # Early exit if we have provided an ignored directory
+ print("clang-format: Ignored third party code '{0}'".format(f))
+ continue
+
+ if os.path.isdir(f):
+ # Processing a directory, generate the file list
+ for folder, subs, files in os.walk(f):
+ subs.sort()
+ for filename in sorted(files):
+ f_in_dir = os.path.join(folder, filename)
+ if (f_in_dir.endswith(extensions)
+ and not is_ignored_path(topsrcdir, ignored_dir_re, f_in_dir)):
+ # Supported extension and accepted path
+ path_list.append(f_in_dir)
+ else:
+ if f.endswith(extensions):
+ path_list.append(f)
+
+ return path_list
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1,14 +1,19 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
+from mozbuild.clang_format import (
+ files,
+ commands
+)
+
import argparse
import hashlib
import itertools
import json
import logging
import operator
import os
import re
@@ -1595,21 +1600,16 @@ class StaticAnalysisMonitor(object):
return (warning, False)
return (warning, True)
@CommandProvider
class StaticAnalysis(MachCommandBase):
"""Utilities for running C++ static analysis checks and format."""
- # List of file extension to consider (should start with dot)
- _format_include_extensions = ('.cpp', '.c', '.h')
- # File contaning all paths to exclude from formatting
- _format_ignore_file = '.clang-format-ignore'
-
@Command('static-analysis', category='testing',
description='Run C++ static analysis checks')
def static_analysis(self):
# If not arguments are provided, just print a help message.
mach = Mach(os.getcwd())
mach.run(['static-analysis', '--help'])
@StaticAnalysisSubCommand('static-analysis', 'check',
@@ -1854,20 +1854,20 @@ class StaticAnalysis(MachCommandBase):
os.chdir(self.topsrcdir)
rc = self._get_clang_tools(verbose=verbose)
if rc != 0:
return rc
if path is None:
- return self._run_clang_format_diff(self._clang_format_diff,
+ return commands.run_clang_format_diff(self._clang_format_diff,
self._clang_format_path, show)
else:
- return self._run_clang_format_path(self._clang_format_path, show, path)
+ return commands.run_clang_format_path(self._clang_format_path, show, path)
def _verify_checker(self, item):
check = item['name']
test_file_path = mozpath.join(self._clang_tidy_base_path, "test", check)
test_file_path_cpp = test_file_path + '.cpp'
test_file_path_json = test_file_path + '.json'
self.log(logging.INFO, 'static-analysis', {},"RUNNING: clang-tidy checker {}.".format(check))
@@ -2125,149 +2125,16 @@ class StaticAnalysis(MachCommandBase):
'the expected output')
assert os.path.exists(self._clang_tidy_path)
assert os.path.exists(self._clang_format_path)
assert os.path.exists(self._clang_apply_replacements)
assert os.path.exists(self._run_clang_tidy_path)
return 0
- def _get_clang_format_diff_command(self):
- if self.repository.name == 'hg':
- args = ["hg", "diff", "-U0", "-r" ".^"]
- for dot_extension in self._format_include_extensions:
- args += ['--include', 'glob:**{0}'.format(dot_extension)]
- args += ['--exclude', 'listfile:{0}'.format(self._format_ignore_file)]
- else:
- args = ["git", "diff", "--no-color", "-U0", "HEAD", "--"]
- for dot_extension in self._format_include_extensions:
- args += ['*{0}'.format(dot_extension)]
- # git-diff doesn't support an 'exclude-from-files' param, but
- # allow to add individual exclude pattern since v1.9, see
- # https://git-scm.com/docs/gitglossary#gitglossary-aiddefpathspecapathspec
- with open(self._format_ignore_file, 'rb') as exclude_pattern_file:
- for pattern in exclude_pattern_file.readlines():
- pattern = pattern.rstrip()
- pattern = pattern.replace('.*', '**')
- if not pattern or pattern.startswith('#'):
- continue # empty or comment
- magics = ['exclude']
- if pattern.startswith('^'):
- magics += ['top']
- pattern = pattern[1:]
- args += [':({0}){1}'.format(','.join(magics), pattern)]
- return args
-
- def _run_clang_format_diff(self, clang_format_diff, clang_format, show):
- # Run clang-format on the diff
- # Note that this will potentially miss a lot things
- from subprocess import Popen, PIPE, check_output, CalledProcessError
-
- diff_process = Popen(self._get_clang_format_diff_command(), stdout=PIPE)
- args = [sys.executable, clang_format_diff, "-p1", "-binary=%s" % clang_format]
-
- if not show:
- args.append("-i")
- try:
- output = check_output(args, stdin=diff_process.stdout)
- if show:
- # We want to print the diffs
- print(output)
- return 0
- except CalledProcessError as e:
- # Something wrong happend
- print("clang-format: An error occured while running clang-format-diff.")
- return e.returncode
-
- def _is_ignored_path(self, ignored_dir_re, f):
- # Remove upto topsrcdir in pathname and match
- if f.startswith(self.topsrcdir + '/'):
- match_f = f[len(self.topsrcdir + '/'):]
- else:
- match_f = f
- return re.match(ignored_dir_re, match_f)
-
- def _generate_path_list(self, paths):
- path_to_third_party = os.path.join(self.topsrcdir, self._format_ignore_file)
- ignored_dir = []
- with open(path_to_third_party, 'r') as fh:
- for line in fh:
- # Remove comments and empty lines
- if line.startswith('#') or len(line.strip()) == 0:
- continue
- # The regexp is to make sure we are managing relative paths
- ignored_dir.append(r"^[\./]*" + line.rstrip())
-
- # Generates the list of regexp
- ignored_dir_re = '(%s)' % '|'.join(ignored_dir)
- extensions = self._format_include_extensions
-
- path_list = []
- for f in paths:
- if self._is_ignored_path(ignored_dir_re, f):
- # Early exit if we have provided an ignored directory
- print("clang-format: Ignored third party code '{0}'".format(f))
- continue
-
- if os.path.isdir(f):
- # Processing a directory, generate the file list
- for folder, subs, files in os.walk(f):
- subs.sort()
- for filename in sorted(files):
- f_in_dir = os.path.join(folder, filename)
- if (f_in_dir.endswith(extensions)
- and not self._is_ignored_path(ignored_dir_re, f_in_dir)):
- # Supported extension and accepted path
- path_list.append(f_in_dir)
- else:
- if f.endswith(extensions):
- path_list.append(f)
-
- return path_list
-
- def _run_clang_format_path(self, clang_format, show, paths):
- # Run clang-format on files or directories directly
- from subprocess import check_output, CalledProcessError
-
- args = [clang_format, "-i"]
-
- path_list = self._generate_path_list(paths)
-
- if path_list == []:
- return
-
- print("Processing %d file(s)..." % len(path_list))
-
- batchsize = 200
-
- for i in range(0, len(path_list), batchsize):
- l = path_list[i: (i + batchsize)]
- # Run clang-format on the list
- try:
- check_output(args + l)
- except CalledProcessError as e:
- # Something wrong happend
- print("clang-format: An error occured while running clang-format.")
- return e.returncode
-
- if show:
- # show the diff
- if self.repository.name == 'hg':
- diff_command = ["hg", "diff"] + paths
- else:
- assert self.repository.name == 'git'
- diff_command = ["git", "diff"] + paths
- try:
- output = check_output(diff_command)
- print(output)
- except CalledProcessError as e:
- # Something wrong happend
- print("clang-format: Unable to run the diff command.")
- return e.returncode
- return 0
@CommandProvider
class Vendor(MachCommandBase):
"""Vendor third-party dependencies into the source repository."""
@Command('vendor', category='misc',
description='Vendor third-party dependencies into the source repository.')
def vendor(self):
new file mode 100644
--- /dev/null
+++ b/tools/lint/clang-format.yml
@@ -0,0 +1,14 @@
+---
+clang-format:
+ description: C/C++ coding style
+ include:
+ - dom/presentation/
+ exclude:
+ - third_party
+ extensions:
+ - cpp
+ - c
+ support-files:
+ - 'tools/lint/clang-format/**'
+ type: external
+ payload: clang-format:lint
new file mode 100644
--- /dev/null
+++ b/tools/lint/clang-format/__init__.py
@@ -0,0 +1,145 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, print_function
+from mozversioncontrol import get_repository_object
+
+import os
+import signal
+import which
+import re
+
+from mozbuild.clang_format import (
+ commands,
+ files
+)
+
+
+# Py3/Py2 compatibility.
+try:
+ from json.decoder import JSONDecodeError
+except ImportError:
+ JSONDecodeError = ValueError
+
+from mozlint import result
+from mozlint.util import pip
+from mozprocess import ProcessHandlerMixin
+
+here = os.path.abspath(os.path.dirname(__file__))
+CLANG_FORMAT_PATH="/home/sylvestre/.mozbuild/clang-tidy/clang/bin/clang-format"
+# CLANG_FORMAT_NOT_FOUND = """
+# Could not find clang-format! Install clang-format and try again.
+
+# $ pip install -U --require-hashes -r {}
+# """.strip().format(CLANG_FORMAT_REQUIREMENTS_PATH)
+
+
+CLANG_FORMAT_INSTALL_ERROR = """
+Unable to install correct version of clang-format
+{}
+
+""".strip().format(CLANG_FORMAT_PATH)
+
+results = []
+
+CLANG_FORMAT_FORMAT_REGEX = re.compile(r'(.*):(.*): (.*) ==> (.*)$')
+
+
+class ClangFormatProcess(ProcessHandlerMixin):
+ def __init__(self, config, *args, **kwargs):
+ self.config = config
+ kwargs['processOutputLine'] = [self.process_line]
+ ProcessHandlerMixin.__init__(self, *args, **kwargs)
+# ProcessHandlerMixin.__init__(self, *args, **kwargs)
+
+
+ def process_line(self, line):
+ try:
+ match = CLANG_FORMAT_FORMAT_REGEX.match(line)
+ abspath, line, typo, correct = match.groups()
+ except AttributeError:
+# print('Unable to match regex against output: {}'.format(line))
+ return
+
+ # Ignore false positive like aParent (which would be fixed to apparent)
+ # See https://github.com/lucasdemarchi/codespell/issues/314
+ m = re.match(r'^[a-z][A-Z][a-z]*', typo)
+ if m:
+ return
+ res = {'path': os.path.relpath(abspath, self.config['root']),
+ 'message': typo + " ==> " + correct,
+ 'level': "warning",
+ 'lineno': line,
+ }
+ results.append(result.from_config(self.config, **res))
+
+ def run(self, *args, **kwargs):
+ print("---------------")
+ print(args)
+ print(*args)
+ print(*kwargs)
+ print(**kwargs)
+# commands.run_clang_format_path(kwargs['base_command'])
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ ProcessHandlerMixin.run(self, *args, **kwargs)
+ signal.signal(signal.SIGINT, orig)
+
+
+def run_process(config, binary, paths):
+ print(config)
+ repository = get_repository_object(config["root"])
+ commands.run_clang_format_path(repository, binary, True, config["include"])
+
+
+
+def get_clang_format_binary():
+ """
+ Returns the path of the first clang-format binary available
+ if not found returns None
+ """
+ binary = os.environ.get('CLANG_FORMAT')
+ if binary:
+ return binary
+
+ try:
+ return which.which('clang-format')
+ except which.WhichError:
+ return None
+
+
+def lint(paths, config, fix=None, **lintargs):
+
+ if not os.path.isfile(CLANG_FORMAT_PATH):
+ print(CLANG_FORMAT_INSTALL_ERROR)
+ return 1
+
+ binary = get_clang_format_binary()
+
+ if not binary:
+ print(CLANG_FORMAT_NOT_FOUND)
+ if 'MOZ_AUTOMATION' in os.environ:
+ return 1
+ return []
+
+ config['root'] = lintargs['root']
+ exclude_list = os.path.join(here, 'exclude-list.txt')
+ cmd_args = [binary,
+ '--disable-colors',
+ # Silence some warnings:
+ # 1: disable warnings about wrong encoding
+ # 2: disable warnings about binary file
+ # 4: shut down warnings about automatic fixes
+ # that were disabled in dictionary.
+ '--quiet-level=7',
+ '--ignore-words=' + exclude_list,
+ # Ignore dictonnaries
+ '--skip=*.dic',
+ ]
+
+ if fix:
+ cmd_args.append('--write-changes')
+ cmd_args = [binary]
+ paths = files.generate_path_list("/home/sylvestre/dev/mozilla/mozilla-central.hg", paths)
+ run_process(config, binary, paths)
+ return results