aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Code-formatting.md58
-rw-r--r--docs/markdown/snippets/clang-format.md43
-rw-r--r--docs/sitemap.txt1
-rw-r--r--mesonbuild/scripts/clangformat.py50
-rwxr-xr-xrun_unittests.py16
-rw-r--r--test cases/unit/93 clangformat/.clang-format4
-rw-r--r--test cases/unit/93 clangformat/.clang-format-ignore3
-rw-r--r--test cases/unit/93 clangformat/.clang-format-include3
-rw-r--r--test cases/unit/93 clangformat/meson.build1
-rw-r--r--test cases/unit/93 clangformat/not-included/badformat.cpp2
-rw-r--r--test cases/unit/93 clangformat/src/badformat.c2
-rw-r--r--test cases/unit/93 clangformat/src/badformat.cpp2
12 files changed, 174 insertions, 11 deletions
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 {
+};