From 0638e38bfc8ef969c7ea9e2005ff051c14dab0f9 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Wed, 10 Mar 2021 08:41:54 -0500 Subject: [PATCH] clangformat: Add clang-format-check target --- docs/markdown/Code-formatting.md | 4 ++-- docs/markdown/snippets/clang-format.md | 4 ++-- mesonbuild/backend/ninjabackend.py | 10 ++++++++-- mesonbuild/scripts/clangformat.py | 21 +++++++++++++++------ run_unittests.py | 13 +++++++++++++ 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/docs/markdown/Code-formatting.md b/docs/markdown/Code-formatting.md index c8d83dee1..386c78725 100644 --- a/docs/markdown/Code-formatting.md +++ b/docs/markdown/Code-formatting.md @@ -54,5 +54,5 @@ src/*.cpp Note that `.clang-format-ignore` has the same format as used by [`run-clang-format.py`](https://github.com/Sarcasm/run-clang-format). -Modified files will be printed on the console which can be used for example by -CI to ensure all files are correctly formatted. +A new target `clang-format-check` has been added. It returns an error code if +any file needs to be reformatted. This is intended to be used by CI. diff --git a/docs/markdown/snippets/clang-format.md b/docs/markdown/snippets/clang-format.md index 8cb88e023..a390d13fd 100644 --- a/docs/markdown/snippets/clang-format.md +++ b/docs/markdown/snippets/clang-format.md @@ -39,5 +39,5 @@ Example of `.clang-format-ignore` file: src/*.cpp ``` -Modified files will be printed on the console which can be used for example by -CI to ensure all files are correctly formatted. +A new target `clang-format-check` has been added. It returns an error code if +any file needs to be reformatted. This is intended to be used by CI. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index b31339ebf..594e297db 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3042,8 +3042,12 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # Alias that runs the target defined above self.create_target_alias('meson-scan-build') - def generate_clangtool(self, name): + def generate_clangtool(self, name, extra_arg=None): target_name = 'clang-' + name + extra_args = [] + if extra_arg: + target_name += f'-{extra_arg}' + extra_args.append(f'--{extra_arg}') if not os.path.exists(os.path.join(self.environment.source_dir, '.clang-' + name)) and \ not os.path.exists(os.path.join(self.environment.source_dir, '_clang-' + name)): return @@ -3052,7 +3056,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if ('', target_name) in self.build.run_target_names: return cmd = self.environment.get_build_command() + \ - ['--internal', 'clang' + name, self.environment.source_dir, self.environment.build_dir] + ['--internal', 'clang' + name, self.environment.source_dir, self.environment.build_dir] + \ + extra_args elem = NinjaBuildElement(self.all_outputs, 'meson-' + target_name, 'CUSTOM_COMMAND', 'PHONY') elem.add_item('COMMAND', cmd) elem.add_item('pool', 'console') @@ -3063,6 +3068,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if not environment.detect_clangformat(): return self.generate_clangtool('format') + self.generate_clangtool('format', 'check') def generate_clangtidy(self): import shutil diff --git a/mesonbuild/scripts/clangformat.py b/mesonbuild/scripts/clangformat.py index 2cf757feb..ceb36ac23 100644 --- a/mesonbuild/scripts/clangformat.py +++ b/mesonbuild/scripts/clangformat.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse import subprocess import itertools import fnmatch @@ -34,15 +35,17 @@ def parse_pattern_file(fname: Path) -> T.List[str]: pass return patterns -def run_clang_format(exelist: T.List[str], fname: Path) -> subprocess.CompletedProcess: +def run_clang_format(exelist: T.List[str], fname: Path, check: bool) -> subprocess.CompletedProcess: before = fname.stat().st_mtime ret = subprocess.run(exelist + ['-style=file', '-i', str(fname)]) after = fname.stat().st_mtime if before != after: print('File reformatted: ', fname) + if check: + ret.returncode = 1 return ret -def clangformat(exelist: T.List[str], srcdir: Path, builddir: Path) -> int: +def clangformat(exelist: T.List[str], srcdir: Path, builddir: Path, check: bool) -> int: patterns = parse_pattern_file(srcdir / '.clang-format-include') if not patterns: patterns = ['**/*'] @@ -61,17 +64,23 @@ def clangformat(exelist: T.List[str], srcdir: Path, builddir: Path) -> int: if f.is_dir() or f.suffix not in suffixes or \ any(fnmatch.fnmatch(strf, i) for i in ignore): continue - futures.append(e.submit(run_clang_format, exelist, f)) + futures.append(e.submit(run_clang_format, exelist, f, check)) returncode = max([x.result().returncode for x in futures]) return returncode def run(args: T.List[str]) -> int: - srcdir = Path(args[0]) - builddir = Path(args[1]) + parser = argparse.ArgumentParser() + parser.add_argument('--check', action='store_true') + parser.add_argument('sourcedir') + parser.add_argument('builddir') + options = parser.parse_args(args) + + srcdir = Path(options.sourcedir) + builddir = Path(options.builddir) exelist = detect_clangformat() if not exelist: print('Could not execute clang-format "%s"' % ' '.join(exelist)) return 1 - return clangformat(exelist, srcdir, builddir) + return clangformat(exelist, srcdir, builddir, options.check) diff --git a/run_unittests.py b/run_unittests.py index b95ead2ca..03e931a9b 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -5602,10 +5602,23 @@ class AllPlatformTests(BasePlatformTests): shutil.copytree(testdir, newdir) self.new_builddir() self.init(newdir) + + # Should reformat 1 file but not return error output = self.build('clang-format') self.assertEqual(1, output.count('File reformatted:')) + + # Reset source tree then try again with clang-format-check, it should + # return an error code this time. + windows_proof_rmtree(newdir) + shutil.copytree(testdir, newdir) + with self.assertRaises(subprocess.CalledProcessError): + output = self.build('clang-format-check') + self.assertEqual(1, output.count('File reformatted:')) + + # All code has been reformatted already, so it should be no-op now. output = self.build('clang-format') self.assertEqual(0, output.count('File reformatted:')) + self.build('clang-format-check') class FailureTests(BasePlatformTests):