From f55868927721f273640a3ba7a8e703d418a352b6 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Tue, 9 Mar 2021 13:15:06 -0500 Subject: clangformat: Add include and ignore files --- docs/markdown/Code-formatting.md | 58 ++++++++++++++++++++++ docs/markdown/snippets/clang-format.md | 43 ++++++++++++++++ docs/sitemap.txt | 1 + mesonbuild/scripts/clangformat.py | 50 +++++++++++++++---- run_unittests.py | 16 ++++++ test cases/unit/93 clangformat/.clang-format | 4 ++ .../unit/93 clangformat/.clang-format-ignore | 3 ++ .../unit/93 clangformat/.clang-format-include | 3 ++ test cases/unit/93 clangformat/meson.build | 1 + .../unit/93 clangformat/not-included/badformat.cpp | 2 + test cases/unit/93 clangformat/src/badformat.c | 2 + test cases/unit/93 clangformat/src/badformat.cpp | 2 + 12 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 docs/markdown/Code-formatting.md create mode 100644 docs/markdown/snippets/clang-format.md create mode 100644 test cases/unit/93 clangformat/.clang-format create mode 100644 test cases/unit/93 clangformat/.clang-format-ignore create mode 100644 test cases/unit/93 clangformat/.clang-format-include create mode 100644 test cases/unit/93 clangformat/meson.build create mode 100644 test cases/unit/93 clangformat/not-included/badformat.cpp create mode 100644 test cases/unit/93 clangformat/src/badformat.c create mode 100644 test cases/unit/93 clangformat/src/badformat.cpp diff --git a/docs/markdown/Code-formatting.md b/docs/markdown/Code-formatting.md new file mode 100644 index 0000000..c8d83de --- /dev/null +++ b/docs/markdown/Code-formatting.md @@ -0,0 +1,58 @@ +--- +short-description: Code formatting +... + +# clang-format + +*Since 0.50.0* + +When `clang-format` is installed and a `.clang-format` file is found at the main +project's root source directory, Meson automatically adds a `clang-format` target +that reformat all C and C++ files (currently only with Ninja backend). + +```sh +ninja -C builddir clang-format +``` + +*Since 0.58.0* + +It is possible to restrict files to be reformatted with optional +`.clang-format-include` and `.clang-format-ignore` files. + +The file `.clang-format-include` contains a list of patterns matching the files +that will be reformatted. The `**` pattern matches this directory and all +subdirectories recursively. Empty lines and lines starting with `#` are ignored. +If `.clang-format-include` is not found, the pattern defaults to `**/*` which +means all files recursively in the source directory but has the disadvantage to +walk the whole source tree which could be slow in the case it contains lots of +files. + +Example of `.clang-format-include` file: +``` +# All files in src/ and its subdirectories +src/**/* + +# All files in include/ but not its subdirectories +include/* +``` + +The file `.clang-format-ignore` contains a list of patterns matching the files +that will be excluded. Files matching the include list (see above) that match +one of the ignore pattern will not be reformatted. Unlike include patters, ignore +patterns does not support `**` and a single `*` match any characters including +path separators. Empty lines and lines starting with `#` are ignored. + +The build directory and file without a well known C or C++ suffix are always +ignored. + +Example of `.clang-format-ignore` file: +``` +# Skip C++ files in src/ directory +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. diff --git a/docs/markdown/snippets/clang-format.md b/docs/markdown/snippets/clang-format.md new file mode 100644 index 0000000..8cb88e0 --- /dev/null +++ b/docs/markdown/snippets/clang-format.md @@ -0,0 +1,43 @@ +## clang-format include and ignore lists + +When clang-format is installed and a `.clang-format` file is found at the main +project's root source directory, Meson automatically adds a `clang-format` target +that reformat all C and C++ files. + +It is now possible to restrict files to be reformatted with optional +`.clang-format-include` and `.clang-format-ignore` files. + +The file `.clang-format-include` contains a list of patterns matching the files +that will be reformatted. The `**` pattern matches this directory and all +subdirectories recursively. Empty lines and lines starting with `#` are ignored. +If `.clang-format-include` is not found, the pattern defaults to `**/*` which +means all files recursively in the source directory but has the disadvantage to +walk the whole source tree which could be slow in the case it contains lots of +files. + +Example of `.clang-format-include` file: +``` +# All files in src/ and its subdirectories +src/**/* + +# All files in include/ but not its subdirectories +include/* +``` + +The file `.clang-format-ignore` contains a list of patterns matching the files +that will be excluded. Files matching the include list (see above) that match +one of the ignore pattern will not be reformatted. Unlike include patters, ignore +patterns does not support `**` and a single `*` match any characters including +path separators. Empty lines and lines starting with `#` are ignored. + +The build directory and file without a well known C or C++ suffix are always +ignored. + +Example of `.clang-format-ignore` file: +``` +# Skip C++ files in src/ directory +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. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index de98cd0..29c54c4 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -32,6 +32,7 @@ index.md Build-options.md Subprojects.md Disabler.md + Code-formatting.md Modules.md CMake-module.md Cuda-module.md diff --git a/mesonbuild/scripts/clangformat.py b/mesonbuild/scripts/clangformat.py index 062cb43..2cf757f 100644 --- a/mesonbuild/scripts/clangformat.py +++ b/mesonbuild/scripts/clangformat.py @@ -12,38 +12,66 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pathlib import subprocess +import itertools +import fnmatch +from pathlib import Path from concurrent.futures import ThreadPoolExecutor from ..environment import detect_clangformat from ..compilers import lang_suffixes import typing as T -def clangformat(exelist: T.List[str], srcdir_name: str, builddir_name: str) -> int: - srcdir = pathlib.Path(srcdir_name) +def parse_pattern_file(fname: Path) -> T.List[str]: + patterns = [] + try: + with fname.open(encoding='utf-8') as f: + for line in f: + pattern = line.strip() + if pattern and not pattern.startswith('#'): + patterns.append(pattern) + except FileNotFoundError: + pass + return patterns + +def run_clang_format(exelist: T.List[str], fname: Path) -> 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) + return ret + +def clangformat(exelist: T.List[str], srcdir: Path, builddir: Path) -> int: + patterns = parse_pattern_file(srcdir / '.clang-format-include') + if not patterns: + patterns = ['**/*'] + globs = [srcdir.glob(p) for p in patterns] + patterns = parse_pattern_file(srcdir / '.clang-format-ignore') + ignore = [str(builddir / '*')] + ignore.extend([str(srcdir / p) for p in patterns]) suffixes = set(lang_suffixes['c']).union(set(lang_suffixes['cpp'])) suffixes.add('h') + suffixes = set([f'.{s}' for s in suffixes]) futures = [] returncode = 0 with ThreadPoolExecutor() as e: - for f in (x for suff in suffixes for x in srcdir.glob('**/*.' + suff)): - if f.is_dir(): - continue + for f in itertools.chain(*globs): strf = str(f) - if strf.startswith(builddir_name): + 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(subprocess.run, exelist + ['-style=file', '-i', strf])) + futures.append(e.submit(run_clang_format, exelist, f)) returncode = max([x.result().returncode for x in futures]) return returncode def run(args: T.List[str]) -> int: - srcdir_name = args[0] - builddir_name = args[1] + srcdir = Path(args[0]) + builddir = Path(args[1]) exelist = detect_clangformat() if not exelist: print('Could not execute clang-format "%s"' % ' '.join(exelist)) return 1 - return clangformat(exelist, srcdir_name, builddir_name) + return clangformat(exelist, srcdir, builddir) diff --git a/run_unittests.py b/run_unittests.py index a381ae5..b95ead2 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -5591,6 +5591,22 @@ class AllPlatformTests(BasePlatformTests): self._run(cmd + python_command + [script]) self.assertEqual('This is text.', self._run(cmd + [app]).strip()) + def test_clang_format(self): + if self.backend is not Backend.ninja: + raise unittest.SkipTest(f'Skipping clang-format tests with {self.backend.name} backend') + if not shutil.which('clang-format'): + raise unittest.SkipTest('clang-format not found') + + testdir = os.path.join(self.unit_test_dir, '93 clangformat') + newdir = os.path.join(self.builddir, 'testdir') + shutil.copytree(testdir, newdir) + self.new_builddir() + self.init(newdir) + output = self.build('clang-format') + self.assertEqual(1, output.count('File reformatted:')) + output = self.build('clang-format') + self.assertEqual(0, output.count('File reformatted:')) + class FailureTests(BasePlatformTests): ''' diff --git a/test cases/unit/93 clangformat/.clang-format b/test cases/unit/93 clangformat/.clang-format new file mode 100644 index 0000000..689bc60 --- /dev/null +++ b/test cases/unit/93 clangformat/.clang-format @@ -0,0 +1,4 @@ +--- +BasedOnStyle: Google + +... diff --git a/test cases/unit/93 clangformat/.clang-format-ignore b/test cases/unit/93 clangformat/.clang-format-ignore new file mode 100644 index 0000000..7fc4d5a --- /dev/null +++ b/test cases/unit/93 clangformat/.clang-format-ignore @@ -0,0 +1,3 @@ + +# Ignore C files +*.c diff --git a/test cases/unit/93 clangformat/.clang-format-include b/test cases/unit/93 clangformat/.clang-format-include new file mode 100644 index 0000000..f057c00 --- /dev/null +++ b/test cases/unit/93 clangformat/.clang-format-include @@ -0,0 +1,3 @@ + +# Only reformat in src/ +src/**/* diff --git a/test cases/unit/93 clangformat/meson.build b/test cases/unit/93 clangformat/meson.build new file mode 100644 index 0000000..8f4af98 --- /dev/null +++ b/test cases/unit/93 clangformat/meson.build @@ -0,0 +1 @@ +project('dummy', 'c', 'cpp') diff --git a/test cases/unit/93 clangformat/not-included/badformat.cpp b/test cases/unit/93 clangformat/not-included/badformat.cpp new file mode 100644 index 0000000..99a0ea6 --- /dev/null +++ b/test cases/unit/93 clangformat/not-included/badformat.cpp @@ -0,0 +1,2 @@ +class { +}; diff --git a/test cases/unit/93 clangformat/src/badformat.c b/test cases/unit/93 clangformat/src/badformat.c new file mode 100644 index 0000000..f1d18b7 --- /dev/null +++ b/test cases/unit/93 clangformat/src/badformat.c @@ -0,0 +1,2 @@ +struct { +}; diff --git a/test cases/unit/93 clangformat/src/badformat.cpp b/test cases/unit/93 clangformat/src/badformat.cpp new file mode 100644 index 0000000..99a0ea6 --- /dev/null +++ b/test cases/unit/93 clangformat/src/badformat.cpp @@ -0,0 +1,2 @@ +class { +}; -- cgit v1.1