summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2025-10-07 18:22:06 +0200
committerJussi Pakkanen <jussi.pakkanen@mailbox.org>2025-10-29 18:59:30 +0200
commit795e7431ffafa0cc1257d64c5fdd0f2c3cb6d841 (patch)
tree40ff7c80d4ae38cb1b144824a284e0115368e7c0
parent1614401329f11d3763a56ed5ce207aba511a6a9d (diff)
downloadmeson-795e7431ffafa0cc1257d64c5fdd0f2c3cb6d841.tar.gz
environment: move tool detection functions to a new module
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-rw-r--r--mesonbuild/backend/ninjabackend.py15
-rw-r--r--mesonbuild/dependencies/dev.py2
-rw-r--r--mesonbuild/environment.py239
-rw-r--r--mesonbuild/mcompile.py2
-rw-r--r--mesonbuild/mdist.py3
-rw-r--r--mesonbuild/minit.py2
-rw-r--r--mesonbuild/minstall.py4
-rw-r--r--mesonbuild/mtest.py4
-rw-r--r--mesonbuild/scripts/clangformat.py2
-rw-r--r--mesonbuild/scripts/clangtidy.py2
-rw-r--r--mesonbuild/scripts/coverage.py6
-rw-r--r--mesonbuild/scripts/scanbuild.py2
-rw-r--r--mesonbuild/tooldetect.py248
-rwxr-xr-xrun_mypy.py1
-rwxr-xr-xrun_tests.py3
-rw-r--r--test cases/unit/116 empty project/expected_mods.json3
-rw-r--r--unittests/allplatformstests.py26
17 files changed, 292 insertions, 272 deletions
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 769de93ec..0196e84d2 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -20,10 +20,11 @@ import typing as T
from . import backends
from .. import modules
-from .. import environment, mesonlib
+from .. import mesonlib
from .. import build
from .. import mlog
from .. import compilers
+from .. import tooldetect
from ..arglist import CompilerArgs
from ..compilers import Compiler, is_library
from ..linkers import ArLikeLinker, RSPFileSyntax
@@ -595,7 +596,7 @@ class NinjaBackend(backends.Backend):
mlog.debug('cuda enabled globally, disabling thin archives for {}, since nvcc/nvlink cannot handle thin archives natively'.format(for_machine))
self.allow_thin_archives[for_machine] = False
- ninja = environment.detect_ninja_command_and_version(log=True)
+ ninja = tooldetect.detect_ninja_command_and_version(log=True)
if self.environment.coredata.optstore.get_value_for(OptionKey('vsenv')):
builddir = Path(self.environment.get_build_dir())
try:
@@ -656,7 +657,7 @@ class NinjaBackend(backends.Backend):
key = OptionKey('b_coverage')
if key in self.environment.coredata.optstore and\
self.environment.coredata.optstore.get_value_for('b_coverage'):
- gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe = environment.find_coverage_tools(self.environment.coredata)
+ gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe = tooldetect.find_coverage_tools(self.environment.coredata)
mlog.debug(f'Using {gcovr_exe} ({gcovr_version}), {lcov_exe} and {llvm_cov_exe} for code coverage')
if gcovr_exe or (lcov_exe and genhtml_exe):
self.add_build_comment(NinjaComment('Coverage rules'))
@@ -3875,7 +3876,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
self.add_build(elem)
def generate_scanbuild(self) -> None:
- if not environment.detect_scanbuild():
+ if not tooldetect.detect_scanbuild():
return
if 'scan-build' in self.all_outputs:
return
@@ -3916,16 +3917,16 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
self.add_build(elem)
def generate_clangformat(self) -> None:
- if not environment.detect_clangformat():
+ if not tooldetect.detect_clangformat():
return
self.generate_clangtool('format')
self.generate_clangtool('format', 'check')
def generate_clangtidy(self) -> None:
- if not environment.detect_clangtidy():
+ if not tooldetect.detect_clangtidy():
return
self.generate_clangtool('tidy', need_pch=True)
- if not environment.detect_clangapply():
+ if not tooldetect.detect_clangapply():
return
self.generate_clangtool('tidy', 'fix', need_pch=True)
diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py
index d9f8fb1ee..aa7c4686b 100644
--- a/mesonbuild/dependencies/dev.py
+++ b/mesonbuild/dependencies/dev.py
@@ -15,7 +15,7 @@ import functools
from mesonbuild.interpreterbase.decorators import FeatureDeprecated
from .. import mesonlib, mlog
-from ..environment import get_llvm_tool_names
+from ..tooldetect import get_llvm_tool_names
from ..mesonlib import version_compare, version_compare_many, search_version
from .base import DependencyException, DependencyMethods, detect_compiler, strip_system_includedirs, strip_system_libdirs, SystemDependency, ExternalDependency, DependencyTypeName
from .cmake import CMakeDependency
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
index 4d89c88ef..90b10697a 100644
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -5,7 +5,7 @@
from __future__ import annotations
import itertools
-import os, re, shutil
+import os, re
import typing as T
import collections
@@ -17,8 +17,8 @@ from . import options
from .mesonlib import (
MesonException, MachineChoice, Popen_safe, PerMachine,
- PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg,
- search_version, MesonBugException
+ PerMachineDefaultable, PerThreeMachineDefaultable, split_args,
+ MesonBugException
)
from .options import OptionKey
from . import mlog
@@ -78,239 +78,6 @@ def _get_env_var(for_machine: MachineChoice, is_cross: bool, var_name: str) -> T
return value
-def detect_gcovr(gcovr_exe: str = 'gcovr', min_version: str = '3.3', log: bool = False) \
- -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]:
- try:
- p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
- except (FileNotFoundError, PermissionError):
- # Doesn't exist in PATH or isn't executable
- return None, None
- found = search_version(found)
- if p.returncode == 0 and mesonlib.version_compare(found, '>=' + min_version):
- if log:
- mlog.log('Found gcovr-{} at {}'.format(found, quote_arg(shutil.which(gcovr_exe))))
- return gcovr_exe, found
- return None, None
-
-def detect_lcov(lcov_exe: str = 'lcov', log: bool = False) \
- -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]:
- try:
- p, found = Popen_safe([lcov_exe, '--version'])[0:2]
- except (FileNotFoundError, PermissionError):
- # Doesn't exist in PATH or isn't executable
- return None, None
- found = search_version(found)
- if p.returncode == 0 and found:
- if log:
- mlog.log('Found lcov-{} at {}'.format(found, quote_arg(shutil.which(lcov_exe))))
- return lcov_exe, found
- return None, None
-
-def detect_llvm_cov(suffix: T.Optional[str] = None) -> T.Optional[str]:
- # If there's a known suffix or forced lack of suffix, use that
- if suffix is not None:
- if suffix == '':
- tool = 'llvm-cov'
- else:
- tool = f'llvm-cov-{suffix}'
- if shutil.which(tool) is not None:
- return tool
- else:
- # Otherwise guess in the dark
- tools = get_llvm_tool_names('llvm-cov')
- for tool in tools:
- if shutil.which(tool):
- return tool
- return None
-
-def compute_llvm_suffix(coredata: coredata.CoreData) -> T.Optional[str]:
- # Check to see if the user is trying to do coverage for either a C or C++ project
- compilers = coredata.compilers[MachineChoice.BUILD]
- cpp_compiler_is_clang = 'cpp' in compilers and compilers['cpp'].id == 'clang'
- c_compiler_is_clang = 'c' in compilers and compilers['c'].id == 'clang'
- # Extract first the C++ compiler if available. If it's a Clang of some kind, compute the suffix if possible
- if cpp_compiler_is_clang:
- suffix = compilers['cpp'].version.split('.')[0]
- return suffix
-
- # Then the C compiler, again checking if it's some kind of Clang and computing the suffix
- if c_compiler_is_clang:
- suffix = compilers['c'].version.split('.')[0]
- return suffix
-
- # Neither compiler is a Clang, or no compilers are for C or C++
- return None
-
-def detect_lcov_genhtml(lcov_exe: str = 'lcov', genhtml_exe: str = 'genhtml') \
- -> T.Tuple[str, T.Optional[str], str]:
- lcov_exe, lcov_version = detect_lcov(lcov_exe)
- if shutil.which(genhtml_exe) is None:
- genhtml_exe = None
-
- return lcov_exe, lcov_version, genhtml_exe
-
-def find_coverage_tools(coredata: coredata.CoreData) -> T.Tuple[T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str]]:
- gcovr_exe, gcovr_version = detect_gcovr()
-
- llvm_cov_exe = detect_llvm_cov(compute_llvm_suffix(coredata))
- # Some platforms may provide versioned clang but only non-versioned llvm utils
- if llvm_cov_exe is None:
- llvm_cov_exe = detect_llvm_cov('')
-
- lcov_exe, lcov_version, genhtml_exe = detect_lcov_genhtml()
-
- return gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe
-
-def detect_ninja(version: str = '1.8.2', log: bool = False) -> T.Optional[T.List[str]]:
- r = detect_ninja_command_and_version(version, log)
- return r[0] if r else None
-
-def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> T.Optional[T.Tuple[T.List[str], str]]:
- env_ninja = os.environ.get('NINJA', None)
- for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']:
- prog = ExternalProgram(n, silent=True)
- if not prog.found():
- continue
- try:
- p, found = Popen_safe(prog.command + ['--version'])[0:2]
- except (FileNotFoundError, PermissionError):
- # Doesn't exist in PATH or isn't executable
- continue
- found = found.strip()
- # Perhaps we should add a way for the caller to know the failure mode
- # (not found or too old)
- if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version):
- if log:
- name = os.path.basename(n)
- if name.endswith('-' + found):
- name = name[0:-1 - len(found)]
- if name == 'ninja-build':
- name = 'ninja'
- if name == 'samu':
- name = 'samurai'
- mlog.log('Found {}-{} at {}'.format(name, found,
- ' '.join([quote_arg(x) for x in prog.command])))
- return (prog.command, found)
- return None
-
-def get_llvm_tool_names(tool: str) -> T.List[str]:
- # Ordered list of possible suffixes of LLVM executables to try. Start with
- # base, then try newest back to oldest (3.5 is arbitrary), and finally the
- # devel version. Please note that the development snapshot in Debian does
- # not have a distinct name. Do not move it to the beginning of the list
- # unless it becomes a stable release.
- suffixes = [
- '', # base (no suffix)
- '-21.1', '21.1',
- '-21', '21',
- '-20.1', '20.1',
- '-20', '20',
- '-19.1', '19.1',
- '-19', '19',
- '-18.1', '18.1',
- '-18', '18',
- '-17', '17',
- '-16', '16',
- '-15', '15',
- '-14', '14',
- '-13', '13',
- '-12', '12',
- '-11', '11',
- '-10', '10',
- '-9', '90',
- '-8', '80',
- '-7', '70',
- '-6.0', '60',
- '-5.0', '50',
- '-4.0', '40',
- '-3.9', '39',
- '-3.8', '38',
- '-3.7', '37',
- '-3.6', '36',
- '-3.5', '35',
- '-20', # Debian development snapshot
- '-devel', # FreeBSD development snapshot
- ]
- names: T.List[str] = []
- for suffix in suffixes:
- names.append(tool + suffix)
- return names
-
-def detect_scanbuild() -> T.List[str]:
- """ Look for scan-build binary on build platform
-
- First, if a SCANBUILD env variable has been provided, give it precedence
- on all platforms.
-
- For most platforms, scan-build is found is the PATH contains a binary
- named "scan-build". However, some distribution's package manager (FreeBSD)
- don't. For those, loop through a list of candidates to see if one is
- available.
-
- Return: a single-element list of the found scan-build binary ready to be
- passed to Popen()
- """
- exelist: T.List[str] = []
- if 'SCANBUILD' in os.environ:
- exelist = split_args(os.environ['SCANBUILD'])
-
- else:
- tools = get_llvm_tool_names('scan-build')
- for tool in tools:
- which = shutil.which(tool)
- if which is not None:
- exelist = [which]
- break
-
- if exelist:
- tool = exelist[0]
- if os.path.isfile(tool) and os.access(tool, os.X_OK):
- return [tool]
- return []
-
-def detect_clangformat() -> T.List[str]:
- """ Look for clang-format binary on build platform
-
- Do the same thing as detect_scanbuild to find clang-format except it
- currently does not check the environment variable.
-
- Return: a single-element list of the found clang-format binary ready to be
- passed to Popen()
- """
- tools = get_llvm_tool_names('clang-format')
- for tool in tools:
- path = shutil.which(tool)
- if path is not None:
- 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 []
-
-
class Environment:
private_dir = 'meson-private'
log_dir = 'meson-logs'
diff --git a/mesonbuild/mcompile.py b/mesonbuild/mcompile.py
index cfaeb7960..72219588c 100644
--- a/mesonbuild/mcompile.py
+++ b/mesonbuild/mcompile.py
@@ -18,7 +18,7 @@ from . import mlog
from . import mesonlib
from .options import OptionKey
from .mesonlib import MesonException, RealPathAction, join_args, listify_array_value, setup_vsenv
-from mesonbuild.environment import detect_ninja
+from mesonbuild.tooldetect import detect_ninja
from mesonbuild import build
if T.TYPE_CHECKING:
diff --git a/mesonbuild/mdist.py b/mesonbuild/mdist.py
index a68736406..5d9967420 100644
--- a/mesonbuild/mdist.py
+++ b/mesonbuild/mdist.py
@@ -20,7 +20,8 @@ import typing as T
from dataclasses import dataclass
from glob import glob
from pathlib import Path
-from mesonbuild.environment import Environment, detect_ninja
+from mesonbuild.environment import Environment
+from mesonbuild.tooldetect import detect_ninja
from mesonbuild.mesonlib import (GIT, MesonException, RealPathAction, get_meson_command, quiet_git,
windows_proof_rmtree, setup_vsenv)
from .options import OptionKey
diff --git a/mesonbuild/minit.py b/mesonbuild/minit.py
index 192c75a68..7d0d051ad 100644
--- a/mesonbuild/minit.py
+++ b/mesonbuild/minit.py
@@ -17,7 +17,7 @@ import typing as T
from mesonbuild import build, mesonlib, mlog
from mesonbuild.coredata import FORBIDDEN_TARGET_NAMES
-from mesonbuild.environment import detect_ninja
+from mesonbuild.tooldetect import detect_ninja
from mesonbuild.templates.mesontemplates import create_meson_build
from mesonbuild.templates.samplefactory import sample_generator
from mesonbuild.options import OptionKey
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index eec22be67..f9c848042 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -15,7 +15,7 @@ import sys
import typing as T
import re
-from . import build, environment
+from . import build, tooldetect
from .backend.backends import InstallData
from .mesonlib import (MesonException, Popen_safe, RealPathAction, is_windows,
is_aix, setup_vsenv, pickle_load, is_osx)
@@ -803,7 +803,7 @@ def rebuild_all(wd: str, backend: str) -> bool:
print('Only ninja backend is supported to rebuild the project before installation.')
return True
- ninja = environment.detect_ninja()
+ ninja = tooldetect.detect_ninja()
if not ninja:
print("Can't find ninja, can't rebuild test.")
return False
diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py
index cdba8a60e..f5e4bb519 100644
--- a/mesonbuild/mtest.py
+++ b/mesonbuild/mtest.py
@@ -29,7 +29,7 @@ import unicodedata
import xml.etree.ElementTree as et
from . import build
-from . import environment
+from . import tooldetect
from . import mlog
from .coredata import MesonVersionMismatchException, major_versions_differ
from .coredata import version as coredata_version
@@ -1702,7 +1702,7 @@ class TestHarness:
if self.options.no_rebuild:
return
- self.ninja = environment.detect_ninja()
+ self.ninja = tooldetect.detect_ninja()
if not self.ninja:
print("Can't find ninja, can't rebuild test.")
# If ninja can't be found return exit code 127, indicating command
diff --git a/mesonbuild/scripts/clangformat.py b/mesonbuild/scripts/clangformat.py
index a3c19e9ad..281cb10ca 100644
--- a/mesonbuild/scripts/clangformat.py
+++ b/mesonbuild/scripts/clangformat.py
@@ -8,7 +8,7 @@ from pathlib import Path
import sys
from .run_tool import run_clang_tool, run_with_buffered_output
-from ..environment import detect_clangformat
+from ..tooldetect import detect_clangformat
from ..mesonlib import version_compare
from ..programs import ExternalProgram
import typing as T
diff --git a/mesonbuild/scripts/clangtidy.py b/mesonbuild/scripts/clangtidy.py
index e5f702491..da4d45474 100644
--- a/mesonbuild/scripts/clangtidy.py
+++ b/mesonbuild/scripts/clangtidy.py
@@ -12,7 +12,7 @@ import shutil
import sys
from .run_tool import run_with_buffered_output, run_clang_tool_on_sources
-from ..environment import detect_clangtidy, detect_clangapply
+from ..tooldetect import detect_clangtidy, detect_clangapply
import typing as T
async def run_clang_tidy(fname: Path, tidyexe: list, builddir: Path, fixesdir: T.Optional[Path]) -> int:
diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py
index a4dfebfb9..f51536358 100644
--- a/mesonbuild/scripts/coverage.py
+++ b/mesonbuild/scripts/coverage.py
@@ -3,7 +3,7 @@
from __future__ import annotations
-from mesonbuild import environment, mesonlib
+from mesonbuild import tooldetect, mesonlib
import argparse, re, sys, os, subprocess, pathlib, stat, shutil
import typing as T
@@ -16,11 +16,11 @@ def coverage(outputs: T.List[str], source_root: str, subproject_root: str, build
if gcovr_exe == '':
gcovr_exe = None
else:
- gcovr_exe, gcovr_version = environment.detect_gcovr(gcovr_exe)
+ gcovr_exe, gcovr_version = tooldetect.detect_gcovr(gcovr_exe)
if llvm_cov_exe == '' or shutil.which(llvm_cov_exe) is None:
llvm_cov_exe = None
- lcov_exe, lcov_version, genhtml_exe = environment.detect_lcov_genhtml()
+ lcov_exe, lcov_version, genhtml_exe = tooldetect.detect_lcov_genhtml()
# load config files for tools if available in the source tree
# - lcov requires manually specifying a per-project config
diff --git a/mesonbuild/scripts/scanbuild.py b/mesonbuild/scripts/scanbuild.py
index 20ce0a621..3bf791833 100644
--- a/mesonbuild/scripts/scanbuild.py
+++ b/mesonbuild/scripts/scanbuild.py
@@ -7,7 +7,7 @@ import subprocess
import shutil
import tempfile
from ..cmdline import get_cmd_line_file, CmdLineFileParser
-from ..environment import detect_ninja, detect_scanbuild
+from ..tooldetect import detect_ninja, detect_scanbuild
from ..mesonlib import windows_proof_rmtree, determine_worker_count
from pathlib import Path
import typing as T
diff --git a/mesonbuild/tooldetect.py b/mesonbuild/tooldetect.py
new file mode 100644
index 000000000..198a6ee33
--- /dev/null
+++ b/mesonbuild/tooldetect.py
@@ -0,0 +1,248 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2012-2020 The Meson development team
+# Copyright © 2023-2025 Intel Corporation
+
+from __future__ import annotations
+
+import os
+import shutil
+import typing as T
+
+from . import coredata
+from . import mesonlib
+from . import mlog
+from .mesonlib import MachineChoice, Popen_safe, search_version, quote_arg, split_args
+from .programs import ExternalProgram
+
+
+def detect_gcovr(gcovr_exe: str = 'gcovr', min_version: str = '3.3', log: bool = False) \
+ -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]:
+ try:
+ p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
+ except (FileNotFoundError, PermissionError):
+ # Doesn't exist in PATH or isn't executable
+ return None, None
+ found = search_version(found)
+ if p.returncode == 0 and mesonlib.version_compare(found, '>=' + min_version):
+ if log:
+ mlog.log('Found gcovr-{} at {}'.format(found, quote_arg(shutil.which(gcovr_exe))))
+ return gcovr_exe, found
+ return None, None
+
+def detect_lcov(lcov_exe: str = 'lcov', log: bool = False) \
+ -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]:
+ try:
+ p, found = Popen_safe([lcov_exe, '--version'])[0:2]
+ except (FileNotFoundError, PermissionError):
+ # Doesn't exist in PATH or isn't executable
+ return None, None
+ found = search_version(found)
+ if p.returncode == 0 and found:
+ if log:
+ mlog.log('Found lcov-{} at {}'.format(found, quote_arg(shutil.which(lcov_exe))))
+ return lcov_exe, found
+ return None, None
+
+def detect_llvm_cov(suffix: T.Optional[str] = None) -> T.Optional[str]:
+ # If there's a known suffix or forced lack of suffix, use that
+ if suffix is not None:
+ if suffix == '':
+ tool = 'llvm-cov'
+ else:
+ tool = f'llvm-cov-{suffix}'
+ if shutil.which(tool) is not None:
+ return tool
+ else:
+ # Otherwise guess in the dark
+ tools = get_llvm_tool_names('llvm-cov')
+ for tool in tools:
+ if shutil.which(tool):
+ return tool
+ return None
+
+def compute_llvm_suffix(coredata: coredata.CoreData) -> T.Optional[str]:
+ # Check to see if the user is trying to do coverage for either a C or C++ project
+ compilers = coredata.compilers[MachineChoice.BUILD]
+ cpp_compiler_is_clang = 'cpp' in compilers and compilers['cpp'].id == 'clang'
+ c_compiler_is_clang = 'c' in compilers and compilers['c'].id == 'clang'
+ # Extract first the C++ compiler if available. If it's a Clang of some kind, compute the suffix if possible
+ if cpp_compiler_is_clang:
+ suffix = compilers['cpp'].version.split('.')[0]
+ return suffix
+
+ # Then the C compiler, again checking if it's some kind of Clang and computing the suffix
+ if c_compiler_is_clang:
+ suffix = compilers['c'].version.split('.')[0]
+ return suffix
+
+ # Neither compiler is a Clang, or no compilers are for C or C++
+ return None
+
+def detect_lcov_genhtml(lcov_exe: str = 'lcov', genhtml_exe: str = 'genhtml') \
+ -> T.Tuple[str, T.Optional[str], str]:
+ lcov_exe, lcov_version = detect_lcov(lcov_exe)
+ if shutil.which(genhtml_exe) is None:
+ genhtml_exe = None
+
+ return lcov_exe, lcov_version, genhtml_exe
+
+def find_coverage_tools(coredata: coredata.CoreData) -> T.Tuple[T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str]]:
+ gcovr_exe, gcovr_version = detect_gcovr()
+
+ llvm_cov_exe = detect_llvm_cov(compute_llvm_suffix(coredata))
+ # Some platforms may provide versioned clang but only non-versioned llvm utils
+ if llvm_cov_exe is None:
+ llvm_cov_exe = detect_llvm_cov('')
+
+ lcov_exe, lcov_version, genhtml_exe = detect_lcov_genhtml()
+
+ return gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe
+
+def detect_ninja(version: str = '1.8.2', log: bool = False) -> T.Optional[T.List[str]]:
+ r = detect_ninja_command_and_version(version, log)
+ return r[0] if r else None
+
+def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> T.Optional[T.Tuple[T.List[str], str]]:
+ env_ninja = os.environ.get('NINJA', None)
+ for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']:
+ prog = ExternalProgram(n, silent=True)
+ if not prog.found():
+ continue
+ try:
+ p, found = Popen_safe(prog.command + ['--version'])[0:2]
+ except (FileNotFoundError, PermissionError):
+ # Doesn't exist in PATH or isn't executable
+ continue
+ found = found.strip()
+ # Perhaps we should add a way for the caller to know the failure mode
+ # (not found or too old)
+ if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version):
+ if log:
+ name = os.path.basename(n)
+ if name.endswith('-' + found):
+ name = name[0:-1 - len(found)]
+ if name == 'ninja-build':
+ name = 'ninja'
+ if name == 'samu':
+ name = 'samurai'
+ mlog.log('Found {}-{} at {}'.format(name, found,
+ ' '.join([quote_arg(x) for x in prog.command])))
+ return (prog.command, found)
+ return None
+
+def get_llvm_tool_names(tool: str) -> T.List[str]:
+ # Ordered list of possible suffixes of LLVM executables to try. Start with
+ # base, then try newest back to oldest (3.5 is arbitrary), and finally the
+ # devel version. Please note that the development snapshot in Debian does
+ # not have a distinct name. Do not move it to the beginning of the list
+ # unless it becomes a stable release.
+ suffixes = [
+ '', # base (no suffix)
+ '-21.1', '21.1',
+ '-21', '21',
+ '-20.1', '20.1',
+ '-20', '20',
+ '-19.1', '19.1',
+ '-19', '19',
+ '-18.1', '18.1',
+ '-18', '18',
+ '-17', '17',
+ '-16', '16',
+ '-15', '15',
+ '-14', '14',
+ '-13', '13',
+ '-12', '12',
+ '-11', '11',
+ '-10', '10',
+ '-9', '90',
+ '-8', '80',
+ '-7', '70',
+ '-6.0', '60',
+ '-5.0', '50',
+ '-4.0', '40',
+ '-3.9', '39',
+ '-3.8', '38',
+ '-3.7', '37',
+ '-3.6', '36',
+ '-3.5', '35',
+ '-20', # Debian development snapshot
+ '-devel', # FreeBSD development snapshot
+ ]
+ names: T.List[str] = []
+ for suffix in suffixes:
+ names.append(tool + suffix)
+ return names
+
+def detect_scanbuild() -> T.List[str]:
+ """ Look for scan-build binary on build platform
+
+ First, if a SCANBUILD env variable has been provided, give it precedence
+ on all platforms.
+
+ For most platforms, scan-build is found is the PATH contains a binary
+ named "scan-build". However, some distribution's package manager (FreeBSD)
+ don't. For those, loop through a list of candidates to see if one is
+ available.
+
+ Return: a single-element list of the found scan-build binary ready to be
+ passed to Popen()
+ """
+ exelist: T.List[str] = []
+ if 'SCANBUILD' in os.environ:
+ exelist = split_args(os.environ['SCANBUILD'])
+
+ else:
+ tools = get_llvm_tool_names('scan-build')
+ for tool in tools:
+ which = shutil.which(tool)
+ if which is not None:
+ exelist = [which]
+ break
+
+ if exelist:
+ tool = exelist[0]
+ if os.path.isfile(tool) and os.access(tool, os.X_OK):
+ return [tool]
+ return []
+
+def detect_clangformat() -> T.List[str]:
+ """ Look for clang-format binary on build platform
+
+ Do the same thing as detect_scanbuild to find clang-format except it
+ currently does not check the environment variable.
+
+ Return: a single-element list of the found clang-format binary ready to be
+ passed to Popen()
+ """
+ tools = get_llvm_tool_names('clang-format')
+ for tool in tools:
+ path = shutil.which(tool)
+ if path is not None:
+ 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 []
diff --git a/run_mypy.py b/run_mypy.py
index 038e391b7..07f2c50a6 100755
--- a/run_mypy.py
+++ b/run_mypy.py
@@ -82,6 +82,7 @@ modules = [
'mesonbuild/options.py',
'mesonbuild/programs.py',
'mesonbuild/rewriter.py',
+ 'mesonbuild/tooldetect.py',
]
additional = [
'run_mypy.py',
diff --git a/run_tests.py b/run_tests.py
index 38c1e812b..05486713e 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -32,8 +32,9 @@ from mesonbuild import mesonlib
from mesonbuild import mesonmain
from mesonbuild import mtest
from mesonbuild import mlog
-from mesonbuild.environment import Environment, detect_ninja
+from mesonbuild.environment import Environment
from mesonbuild.envconfig import detect_machine_info
+from mesonbuild.tooldetect import detect_ninja
from mesonbuild.coredata import version as meson_version
from mesonbuild.options import backendlist
from mesonbuild.mesonlib import setup_vsenv
diff --git a/test cases/unit/116 empty project/expected_mods.json b/test cases/unit/116 empty project/expected_mods.json
index defd6ea2f..e479bcbb7 100644
--- a/test cases/unit/116 empty project/expected_mods.json
+++ b/test cases/unit/116 empty project/expected_mods.json
@@ -231,6 +231,7 @@
"mesonbuild.programs",
"mesonbuild.scripts",
"mesonbuild.scripts.meson_exe",
+ "mesonbuild.tooldetect",
"mesonbuild.utils",
"mesonbuild.utils.core",
"mesonbuild.utils.platform",
@@ -239,6 +240,6 @@
"mesonbuild.wrap",
"mesonbuild.wrap.wrap"
],
- "count": 70
+ "count": 71
}
}
diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py
index 21cdaa8be..cf3427aeb 100644
--- a/unittests/allplatformstests.py
+++ b/unittests/allplatformstests.py
@@ -2497,7 +2497,7 @@ class AllPlatformTests(BasePlatformTests):
self.assertIn('ERROR: compiler.has_header_symbol got unknown keyword arguments "prefixxx"', cm.exception.output)
def test_templates(self):
- ninja = mesonbuild.environment.detect_ninja()
+ ninja = mesonbuild.tooldetect.detect_ninja()
if ninja is None:
raise SkipTest('This test currently requires ninja. Fix this once "meson build" works.')
@@ -4290,14 +4290,14 @@ class AllPlatformTests(BasePlatformTests):
def test_coverage(self):
if mesonbuild.envconfig.detect_msys2_arch():
raise SkipTest('Skipped due to problems with coverage on MSYS2')
- gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr()
if not gcovr_exe:
raise SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_id() == 'clang':
- if not mesonbuild.environment.detect_llvm_cov():
+ if not mesonbuild.tooldetect.detect_llvm_cov():
raise SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise SkipTest('Test only applies to non-MSVC compilers')
@@ -4310,14 +4310,14 @@ class AllPlatformTests(BasePlatformTests):
def test_coverage_complex(self):
if mesonbuild.envconfig.detect_msys2_arch():
raise SkipTest('Skipped due to problems with coverage on MSYS2')
- gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr()
if not gcovr_exe:
raise SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '105 generatorcustom')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_id() == 'clang':
- if not mesonbuild.environment.detect_llvm_cov():
+ if not mesonbuild.tooldetect.detect_llvm_cov():
raise SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise SkipTest('Test only applies to non-MSVC compilers')
@@ -4330,14 +4330,14 @@ class AllPlatformTests(BasePlatformTests):
def test_coverage_html(self):
if mesonbuild.envconfig.detect_msys2_arch():
raise SkipTest('Skipped due to problems with coverage on MSYS2')
- gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr()
if not gcovr_exe:
raise SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_id() == 'clang':
- if not mesonbuild.environment.detect_llvm_cov():
+ if not mesonbuild.tooldetect.detect_llvm_cov():
raise SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise SkipTest('Test only applies to non-MSVC compilers')
@@ -4350,14 +4350,14 @@ class AllPlatformTests(BasePlatformTests):
def test_coverage_text(self):
if mesonbuild.envconfig.detect_msys2_arch():
raise SkipTest('Skipped due to problems with coverage on MSYS2')
- gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr()
if not gcovr_exe:
raise SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_id() == 'clang':
- if not mesonbuild.environment.detect_llvm_cov():
+ if not mesonbuild.tooldetect.detect_llvm_cov():
raise SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise SkipTest('Test only applies to non-MSVC compilers')
@@ -4370,14 +4370,14 @@ class AllPlatformTests(BasePlatformTests):
def test_coverage_xml(self):
if mesonbuild.envconfig.detect_msys2_arch():
raise SkipTest('Skipped due to problems with coverage on MSYS2')
- gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr()
if not gcovr_exe:
raise SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_id() == 'clang':
- if not mesonbuild.environment.detect_llvm_cov():
+ if not mesonbuild.tooldetect.detect_llvm_cov():
raise SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise SkipTest('Test only applies to non-MSVC compilers')
@@ -4390,14 +4390,14 @@ class AllPlatformTests(BasePlatformTests):
def test_coverage_escaping(self):
if mesonbuild.envconfig.detect_msys2_arch():
raise SkipTest('Skipped due to problems with coverage on MSYS2')
- gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
+ gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr()
if not gcovr_exe:
raise SkipTest('gcovr not found, or too old')
testdir = os.path.join(self.common_test_dir, '243 escape++')
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_id() == 'clang':
- if not mesonbuild.environment.detect_llvm_cov():
+ if not mesonbuild.tooldetect.detect_llvm_cov():
raise SkipTest('llvm-cov not found')
if cc.get_id() == 'msvc':
raise SkipTest('Test only applies to non-MSVC compilers')