diff --git a/analyzer/codechecker_analyzer/analyzer.py b/analyzer/codechecker_analyzer/analyzer.py index d9fb71009c..fb4e521ae0 100644 --- a/analyzer/codechecker_analyzer/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzer.py @@ -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 \ @@ -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 = '' @@ -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 diff --git a/analyzer/codechecker_analyzer/analyzers/analyzer_base.py b/analyzer/codechecker_analyzer/analyzers/analyzer_base.py index 639e6eb0b9..a9f0be9b0d 100644 --- a/analyzer/codechecker_analyzer/analyzers/analyzer_base.py +++ b/analyzer/codechecker_analyzer/analyzers/analyzer_base.py @@ -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.""" diff --git a/analyzer/codechecker_analyzer/analyzers/analyzer_types.py b/analyzer/codechecker_analyzer/analyzers/analyzer_types.py index b1400b2258..4cc02debe3 100644 --- a/analyzer/codechecker_analyzer/analyzers/analyzer_types.py +++ b/analyzer/codechecker_analyzer/analyzers/analyzer_types.py @@ -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) @@ -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!")) @@ -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.")) diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py index 8fc231e9e8..274e5b51d8 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py @@ -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, @@ -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 diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/version.py b/analyzer/codechecker_analyzer/analyzers/clangsa/version.py index 3749766b93..104fc74c50 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/version.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/version.py @@ -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: """ diff --git a/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py b/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py index 7bc5d92b95..dac8862899 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py @@ -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 @@ -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, @@ -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[\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") diff --git a/analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py b/analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py index 8d4559bb81..d1c65a06d1 100644 --- a/analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py @@ -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[\d\.]+)') - match = version_re.match(cppcheck_output) - if match: - return StrictVersion(match.group('version')) - - class Cppcheck(analyzer_base.SourceAnalyzer): """ Constructs the Cppcheck analyzer commands. @@ -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, @@ -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[\d\.]+)') + match = version_re.match(version_output) + if match: + return StrictVersion(match.group('version')) + else: return [] @classmethod @@ -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. diff --git a/analyzer/codechecker_analyzer/cmd/analyze.py b/analyzer/codechecker_analyzer/cmd/analyze.py index 5daf73858a..b2a6894ad4 100644 --- a/analyzer/codechecker_analyzer/cmd/analyze.py +++ b/analyzer/codechecker_analyzer/cmd/analyze.py @@ -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 "