clangformat: Add include and ignore files

pull/8546/head
Xavier Claessens 4 years ago committed by Xavier Claessens
parent a45f939092
commit f558689277
  1. 58
      docs/markdown/Code-formatting.md
  2. 43
      docs/markdown/snippets/clang-format.md
  3. 1
      docs/sitemap.txt
  4. 50
      mesonbuild/scripts/clangformat.py
  5. 16
      run_unittests.py
  6. 4
      test cases/unit/93 clangformat/.clang-format
  7. 3
      test cases/unit/93 clangformat/.clang-format-ignore
  8. 3
      test cases/unit/93 clangformat/.clang-format-include
  9. 1
      test cases/unit/93 clangformat/meson.build
  10. 2
      test cases/unit/93 clangformat/not-included/badformat.cpp
  11. 2
      test cases/unit/93 clangformat/src/badformat.c
  12. 2
      test cases/unit/93 clangformat/src/badformat.cpp

@ -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.

@ -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.

@ -32,6 +32,7 @@ index.md
Build-options.md
Subprojects.md
Disabler.md
Code-formatting.md
Modules.md
CMake-module.md
Cuda-module.md

@ -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)

@ -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):
'''

@ -0,0 +1,4 @@
---
BasedOnStyle: Google
...

@ -0,0 +1,3 @@
# Only reformat in src/
src/**/*

@ -0,0 +1 @@
project('dummy', 'c', 'cpp')
Loading…
Cancel
Save