diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 17b4986fd..fc88896fc 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2646,8 +2646,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.create_target_alias('meson-dist') def generate_scanbuild(self): - import shutil - if shutil.which('scan-build') is None: + if not environment.detect_scanbuild(): return cmd = self.environment.get_build_command() + \ ['--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir] + \ diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index e67e744ca..f5b24d852 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -163,6 +163,55 @@ def detect_ninja(version: str = '1.5', log: bool = False) -> str: mlog.log('Found {}-{} at {}'.format(name, found, quote_arg(n))) return n +def detect_scanbuild(): + """ 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. + Since this is a costly operation, limit it to the impacted platforms + (currently all non-linux platforms) + + Return: a single-element list of the found scan-build binary ready to be + passed to Popen() + """ + exelist = [] + if 'SCANBUILD' in os.environ: + exelist = split_args(os.environ['SCANBUILD']) + + elif shutil.which('scan-build') is not None: + exelist = [shutil.which('scan-build')] + + elif platform.system() != 'Linux': + tools = [ + 'scan-build', # base + 'scan-build-8.0', 'scan-build80', + 'scan-build-7.0', 'scan-build70', + 'scan-build-6.0', 'scan-build60', + 'scan-build-5.0', 'scan-build50', + 'scan-build-4.0', 'scan-build40', + 'scan-build-3.9', 'scan-build39', + 'scan-build-3.8', 'scan-build38', + 'scan-build-3.7', 'scan-build37', + 'scan-build-3.6', 'scan-build36', + 'scan-build-3.5', 'scan-build35', + 'scan-build-9.0', 'scan-build-devel', # development snapshot + ] + for tool in tools: + if shutil.which(tool) is not None: + exelist = [shutil.which(tool)] + break + + if exelist: + tool = exelist[0] + if os.path.isfile(tool) and os.access(tool, os.X_OK): + return [tool] + return [] + def detect_native_windows_arch(): """ The architecture of Windows itself: x86, amd64 or arm64 diff --git a/mesonbuild/scripts/scanbuild.py b/mesonbuild/scripts/scanbuild.py index 8c0f423d3..d83ca1231 100644 --- a/mesonbuild/scripts/scanbuild.py +++ b/mesonbuild/scripts/scanbuild.py @@ -16,9 +16,10 @@ import os import subprocess import shutil import tempfile -from ..environment import detect_ninja +from ..environment import detect_ninja, detect_scanbuild from ..mesonlib import Popen_safe, split_args + def scanbuild(exelist, srcdir, blddir, privdir, logdir, args): with tempfile.TemporaryDirectory(dir=privdir) as scandir: meson_cmd = exelist + args @@ -28,6 +29,7 @@ def scanbuild(exelist, srcdir, blddir, privdir, logdir, args): return rc return subprocess.call(build_cmd) + def run(args): srcdir = args[0] blddir = args[1] @@ -35,40 +37,10 @@ def run(args): privdir = os.path.join(blddir, 'meson-private') logdir = os.path.join(blddir, 'meson-logs/scanbuild') shutil.rmtree(logdir, ignore_errors=True) - tools = [ - 'scan-build', # base - 'scan-build-8.0', 'scan-build80', - 'scan-build-7.0', 'scan-build70', - 'scan-build-6.0', 'scan-build60', - 'scan-build-5.0', 'scan-build50', - 'scan-build-4.0', 'scan-build40', - 'scan-build-3.9', 'scan-build39', - 'scan-build-3.8', 'scan-build38', - 'scan-build-3.7', 'scan-build37', - 'scan-build-3.6', 'scan-build36', - 'scan-build-3.5', 'scan-build35', - 'scan-build-9.0', 'scan-build-devel', # development snapshot - ] - toolname = 'scan-build' - for tool in tools: - try: - p, out = Popen_safe([tool, '--help'])[:2] - except (FileNotFoundError, PermissionError): - continue - if p.returncode != 0: - continue - else: - toolname = tool - break - if 'SCANBUILD' in os.environ: - exelist = split_args(os.environ['SCANBUILD']) - else: - exelist = [toolname] - - try: - Popen_safe(exelist + ['--help']) - except OSError: + exelist = detect_scanbuild() + if not exelist: print('Could not execute scan-build "%s"' % ' '.join(exelist)) return 1 + return scanbuild(exelist, srcdir, blddir, privdir, logdir, meson_cmd)