diff options
| author | Jonathon Anderson <anderson.jonathonm@gmail.com> | 2024-08-20 08:59:37 -0500 |
|---|---|---|
| committer | Dylan Baker <dylan@pnwbakers.com> | 2024-09-24 14:55:45 -0700 |
| commit | 8eaeff5b1f3057f827dfc95ac3987903c9bcb828 (patch) | |
| tree | f1ec6db6e77396b3f7b6cf142ee264a96122892b | |
| parent | f3daf6265aa412c2d54784100c8618e9008a2f9d (diff) | |
| download | meson-8eaeff5b1f3057f827dfc95ac3987903c9bcb828.tar.gz | |
clang-tidy: Avoid spawning too many threads
The clang-tidy-fix target uses run-clang-tidy to do the fixing, however
this script itself spawns `os.cpu_count()` threads as part of its
internal parallelism. When combined with Meson's parallelism this
results in the creation of potentially thousands of unecessary threads.
This commit rewrites the clang-tidy-fix to perform the same task
run-clang-tidy does but exclusively on Meson's thread pool. "Fix-it"
snippets are saved to `meson-private/clang-tidy-fix/` by a parallel
clang-tidy phase, afterwards (to avoid races) all collected fixes are
applied with a single call to clang-apply-replacements.
| -rw-r--r-- | mesonbuild/backend/ninjabackend.py | 5 | ||||
| -rw-r--r-- | mesonbuild/environment.py | 26 | ||||
| -rw-r--r-- | mesonbuild/scripts/clangtidy.py | 50 |
3 files changed, 72 insertions, 9 deletions
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index d8995f031..6eb1f1d01 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3666,10 +3666,11 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.generate_clangtool('format', 'check') def generate_clangtidy(self) -> None: - import shutil - if not shutil.which('clang-tidy'): + if not environment.detect_clangtidy(): return self.generate_clangtool('tidy') + if not environment.detect_clangapply(): + return self.generate_clangtool('tidy', 'fix') def generate_tags(self, tool: str, target_name: str) -> None: diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 90c9bb911..71a2f3afc 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -275,6 +275,32 @@ def detect_clangformat() -> T.List[str]: return [path] return [] +def detect_clangtidy() -> T.List[str]: + """ Look for clang-tidy binary on build platform + + Return: a single-element list of the found clang-tidy binary ready to be + passed to Popen() + """ + tools = get_llvm_tool_names('clang-tidy') + for tool in tools: + path = shutil.which(tool) + if path is not None: + return [path] + return [] + +def detect_clangapply() -> T.List[str]: + """ Look for clang-apply-replacements binary on build platform + + Return: a single-element list of the found clang-apply-replacements binary + ready to be passed to Popen() + """ + tools = get_llvm_tool_names('clang-apply-replacements') + for tool in tools: + path = shutil.which(tool) + if path is not None: + return [path] + return [] + def detect_windows_arch(compilers: CompilersDict) -> str: """ Detecting the 'native' architecture of Windows is not a trivial task. We diff --git a/mesonbuild/scripts/clangtidy.py b/mesonbuild/scripts/clangtidy.py index 1e0c4a5a3..a922f8514 100644 --- a/mesonbuild/scripts/clangtidy.py +++ b/mesonbuild/scripts/clangtidy.py @@ -6,15 +6,22 @@ from __future__ import annotations import argparse import subprocess from pathlib import Path +import tempfile +import os +import shutil +import sys from .run_tool import run_tool +from ..environment import detect_clangtidy, detect_clangapply import typing as T -def run_clang_tidy(fname: Path, builddir: Path) -> subprocess.CompletedProcess: - return subprocess.run(['clang-tidy', '-quiet', '-p', str(builddir), str(fname)]) - -def run_clang_tidy_fix(fname: Path, builddir: Path) -> subprocess.CompletedProcess: - return subprocess.run(['run-clang-tidy', '-fix', '-format', '-quiet', '-p', str(builddir), str(fname)]) +def run_clang_tidy(fname: Path, tidyexe: list, builddir: Path, fixesdir: T.Optional[Path]) -> subprocess.CompletedProcess: + args = [] + if fixesdir is not None: + handle, name = tempfile.mkstemp(prefix=fname.name + '.', suffix='.yaml', dir=fixesdir) + os.close(handle) + args.extend(['-export-fixes', name]) + return subprocess.run(tidyexe + args + ['-quiet', '-p', str(builddir), str(fname)]) def run(args: T.List[str]) -> int: parser = argparse.ArgumentParser() @@ -26,5 +33,34 @@ def run(args: T.List[str]) -> int: srcdir = Path(options.sourcedir) builddir = Path(options.builddir) - run_func = run_clang_tidy_fix if options.fix else run_clang_tidy - return run_tool('clang-tidy', srcdir, builddir, run_func, builddir) + tidyexe = detect_clangtidy() + if not tidyexe: + print(f'Could not execute clang-tidy "{" ".join(tidyexe)}"') + return 1 + + fixesdir: T.Optional[Path] = None + if options.fix: + applyexe = detect_clangapply() + if not applyexe: + print(f'Could not execute clang-apply-replacements "{" ".join(applyexe)}"') + return 1 + + fixesdir = builddir / 'meson-private' / 'clang-tidy-fix' + if fixesdir.is_dir(): + shutil.rmtree(fixesdir) + elif fixesdir.exists(): + fixesdir.unlink() + fixesdir.mkdir(parents=True) + + tidyret = run_tool('clang-tidy', srcdir, builddir, run_clang_tidy, tidyexe, builddir, fixesdir) + if fixesdir is not None: + print('Applying fix-its...') + applyret = subprocess.run(applyexe + ['-format', '-style=file', '-ignore-insert-conflict', fixesdir]).returncode + + if tidyret != 0: + print('Errors encountered while running clang-tidy', file=sys.stderr) + return tidyret + if fixesdir is not None and applyret != 0: + print('Errors encountered while running clang-apply-replacements', file=sys.stderr) + return applyret + return 0 |
