diff options
| author | Paolo Bonzini <pbonzini@redhat.com> | 2025-10-07 18:22:06 +0200 |
|---|---|---|
| committer | Jussi Pakkanen <jussi.pakkanen@mailbox.org> | 2025-10-29 18:59:30 +0200 |
| commit | 795e7431ffafa0cc1257d64c5fdd0f2c3cb6d841 (patch) | |
| tree | 40ff7c80d4ae38cb1b144824a284e0115368e7c0 | |
| parent | 1614401329f11d3763a56ed5ce207aba511a6a9d (diff) | |
| download | meson-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.py | 15 | ||||
| -rw-r--r-- | mesonbuild/dependencies/dev.py | 2 | ||||
| -rw-r--r-- | mesonbuild/environment.py | 239 | ||||
| -rw-r--r-- | mesonbuild/mcompile.py | 2 | ||||
| -rw-r--r-- | mesonbuild/mdist.py | 3 | ||||
| -rw-r--r-- | mesonbuild/minit.py | 2 | ||||
| -rw-r--r-- | mesonbuild/minstall.py | 4 | ||||
| -rw-r--r-- | mesonbuild/mtest.py | 4 | ||||
| -rw-r--r-- | mesonbuild/scripts/clangformat.py | 2 | ||||
| -rw-r--r-- | mesonbuild/scripts/clangtidy.py | 2 | ||||
| -rw-r--r-- | mesonbuild/scripts/coverage.py | 6 | ||||
| -rw-r--r-- | mesonbuild/scripts/scanbuild.py | 2 | ||||
| -rw-r--r-- | mesonbuild/tooldetect.py | 248 | ||||
| -rwxr-xr-x | run_mypy.py | 1 | ||||
| -rwxr-xr-x | run_tests.py | 3 | ||||
| -rw-r--r-- | test cases/unit/116 empty project/expected_mods.json | 3 | ||||
| -rw-r--r-- | unittests/allplatformstests.py | 26 |
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') |
