Skip to content

Commit

Permalink
Configurability of analyzer versions
Browse files Browse the repository at this point in the history
The modification can be used to validate the version of each analyzer.
The user can specify all desired analyzers after the --analyzers flag, as was possible before. Also, the exact version number can be typed for each analyzer, separated by an '=' char. For example: 'CodeChecker analyze compile_commands.json -o reports --analyzers clangsa=14.0.0 cppcheck=2.7'.
If the version number was not the same as in the current environment, the analyzer would be disabled. The user can enumerate the analyzers with or without versions, for example: 'CodeChecker analyze compile_commands.json -o reports --analyzers clangsa=14.0.0 cppcheck'.
  • Loading branch information
cservakt committed Aug 28, 2023
1 parent 38f9315 commit 3d47ad2
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 37 deletions.
4 changes: 1 addition & 3 deletions analyzer/codechecker_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ def perform_analysis(args, skip_handlers, actions, metadata_tool,
in the given analysis context for the supplied build actions.
Additionally, insert statistical information into the metadata dict.
"""

context = analyzer_context.get_context()

ctu_reanalyze_on_failure = 'ctu_reanalyze_on_failure' in args and \
Expand All @@ -141,7 +140,6 @@ def perform_analysis(args, skip_handlers, actions, metadata_tool,
else analyzer_types.supported_analyzers
analyzers, errored = analyzer_types.check_supported_analyzers(analyzers)
analyzer_types.check_available_analyzers(analyzers, errored)

ctu_collect = False
ctu_analyze = False
ctu_dir = ''
Expand Down Expand Up @@ -231,7 +229,7 @@ def perform_analysis(args, skip_handlers, actions, metadata_tool,

# TODO: cppcheck may require a different environment than clang.
version = analyzer_types.supported_analyzers[analyzer] \
.get_version(context.analyzer_env)
.get_version(env=context.analyzer_env)
metadata_info['analyzer_statistics']['version'] = version

metadata_tool['analyzers'][analyzer] = metadata_info
Expand Down
9 changes: 9 additions & 0 deletions analyzer/codechecker_analyzer/analyzers/analyzer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ def version_compatible(cls, configured_binary, environ):
"""
raise NotImplementedError("Subclasses should implement this!")

@classmethod
@abstractmethod
def version_info(cls):
"""
A subclass should have a version_info method
to return with the analyzer's version number.
"""
pass

@classmethod
def construct_config_handler(cls, args):
""" Should return a subclass of AnalyzerConfigHandler."""
Expand Down
18 changes: 15 additions & 3 deletions analyzer/codechecker_analyzer/analyzers/analyzer_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ def print_unsupported_analyzers(errored):
for analyzer_binary, reason in errored:
LOG.warning("Analyzer '%s' is enabled but CodeChecker is failed to "
"execute analysis with it: '%s'. Please check your "
"'PATH' environment variable and the "
"'config/package_layout.json' file!",
"'PATH' environment variable, the "
"'config/package_layout.json' file "
"and the argument parameters!",
analyzer_binary, reason)


Expand Down Expand Up @@ -148,8 +149,11 @@ def check_supported_analyzers(analyzers):

enabled_analyzers = set()
failed_analyzers = set()

for analyzer_name in analyzers:
analyzer_name, analyzer_version = analyzer_name.split('=', 1) \
if len(analyzer_name.split('=', 1)) == 2 \
else [analyzer_name, None]

if analyzer_name not in supported_analyzers:
failed_analyzers.add((analyzer_name,
"Analyzer unsupported by CodeChecker!"))
Expand Down Expand Up @@ -184,6 +188,14 @@ def check_supported_analyzers(analyzers):
# Check version compatibility of the analyzer binary.
if analyzer_bin:
analyzer = supported_analyzers[analyzer_name]
bin_version = str(analyzer.version_info(env=context.analyzer_env))
if analyzer_version and analyzer_version != bin_version:
LOG.warning(
f"Given version: {analyzer_version}, found version "
f"for {analyzer_name} analyzer: {bin_version}"
)
failed_analyzers.add((analyzer_name, "Wrong version given."))
available_analyzer = False
if not analyzer.version_compatible(analyzer_bin, check_env):
failed_analyzers.add((analyzer_name,
"Incompatible version."))
Expand Down
7 changes: 4 additions & 3 deletions analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,10 @@ def __add_plugin_load_flags(cls, analyzer_cmd: List[str]):
analyzer_cmd.extend(["-load", plugin])

@classmethod
def get_version(cls, env=None):
def get_version(cls, analyzer_binary=None, env=None):
""" Get analyzer version information. """
version = [cls.analyzer_binary(), '--version']
version = [analyzer_binary, "--version"] if analyzer_binary \
else [cls.analyzer_binary(), '--version']
try:
output = subprocess.check_output(version,
env=env,
Expand All @@ -170,7 +171,7 @@ def get_version(cls, env=None):
return None

@classmethod
def version_info(cls):
def version_info(cls, analyzer_binary=None, env=None):
return version.get(cls.analyzer_binary())

@classmethod
Expand Down
5 changes: 5 additions & 0 deletions analyzer/codechecker_analyzer/analyzers/clangsa/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def __init__(self,
self.installed_dir = str(installed_dir)
self.vendor = str(vendor)

def __str__(self):
return f"{self.major_version}." \
f"{self.minor_version}." \
f"{self.patch_version}"


class ClangVersionInfoParser:
"""
Expand Down
22 changes: 20 additions & 2 deletions analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import subprocess
from typing import List, Tuple

from distutils.version import StrictVersion

import yaml

from codechecker_common.logger import get_logger
Expand Down Expand Up @@ -178,9 +180,10 @@ def analyzer_binary(cls):
.analyzer_binaries[cls.ANALYZER_NAME]

@classmethod
def get_version(cls, env=None):
def get_version(cls, analyzer_binary=None, env=None):
""" Get analyzer version information. """
version = [cls.analyzer_binary(), '--version']
version = [analyzer_binary, "--version"] if analyzer_binary \
else [cls.analyzer_binary(), '--version']
try:
output = subprocess.check_output(version,
env=env,
Expand All @@ -195,6 +198,21 @@ def get_version(cls, env=None):

return None

@classmethod
def version_info(cls, analyzer_binary=None, env=None):
"""
Run the Clang-tidy version command
parse the output
and return the analyzer's version.
"""
version_output = cls.get_version(analyzer_binary, env)
version_re = re.compile(r'(?P<version>[\d\.]+)')
match = version_re.match(version_output)
if match:
return StrictVersion(match.group('version'))
else:
return []

def add_checker_config(self, checker_cfg):
LOG.error("Not implemented yet")

Expand Down
39 changes: 14 additions & 25 deletions analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,6 @@ def parse_checkers(cppcheck_output):
return checkers


def parse_version(cppcheck_output):
"""
Parse cppcheck version output and return the version number.
"""
version_re = re.compile(r'^Cppcheck (?P<version>[\d\.]+)')
match = version_re.match(cppcheck_output)
if match:
return StrictVersion(match.group('version'))


class Cppcheck(analyzer_base.SourceAnalyzer):
"""
Constructs the Cppcheck analyzer commands.
Expand All @@ -82,9 +72,10 @@ def analyzer_binary(cls):
.analyzer_binaries[cls.ANALYZER_NAME]

@classmethod
def get_version(cls, env=None):
def get_version(cls, analyzer_binary=None, env=None):
""" Get analyzer version information. """
version = [cls.analyzer_binary(), '--version']
version = [analyzer_binary, "--version"] if analyzer_binary \
else [cls.analyzer_binary(), '--version']
try:
output = subprocess.check_output(version,
env=env,
Expand Down Expand Up @@ -331,20 +322,18 @@ def resolve_missing_binary(cls, configured_binary, env):
return cppcheck

@classmethod
def __get_analyzer_version(cls, analyzer_binary, env):
def version_info(cls, analyzer_binary=None, env=None):
"""
Return the analyzer version.
Run the Cppcheck version command
parse the output
and return the analyzer's version.
"""
command = [analyzer_binary, "--version"]

try:
result = subprocess.check_output(
command,
env=env,
encoding="utf-8",
errors="ignore")
return parse_version(result)
except (subprocess.CalledProcessError, OSError):
version_output = cls.get_version(analyzer_binary, env)
version_re = re.compile(r'^Cppcheck (?P<version>[\d\.]+)')
match = version_re.match(version_output)
if match:
return StrictVersion(match.group('version'))
else:
return []

@classmethod
Expand All @@ -353,7 +342,7 @@ def version_compatible(cls, configured_binary, environ):
Check the version compatibility of the given analyzer binary.
"""
analyzer_version = \
cls.__get_analyzer_version(configured_binary, environ)
cls.version_info(configured_binary, environ)

# The analyzer version should be above 1.80 because '--plist-output'
# argument was introduced in this release.
Expand Down
1 change: 0 additions & 1 deletion analyzer/codechecker_analyzer/cmd/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,6 @@ def add_arguments_to_parser(parser):
dest='analyzers',
metavar='ANALYZER',
required=False,
choices=analyzer_types.supported_analyzers,
default=argparse.SUPPRESS,
help="Run analysis only with the analyzers "
"specified. Currently supported analyzers "
Expand Down

0 comments on commit 3d47ad2

Please sign in to comment.