Skip to content

Commit

Permalink
Merge branch 'Ericsson:master' into phase-2
Browse files Browse the repository at this point in the history
  • Loading branch information
feyruzb authored Oct 16, 2024
2 parents 704de82 + a6663b3 commit de7f5be
Show file tree
Hide file tree
Showing 49 changed files with 1,267 additions and 368 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on: [push, pull_request]

permissions: read-all

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
# Note: UI related linter tests will run in the gui job.
lint:
Expand Down
12 changes: 5 additions & 7 deletions analyzer/codechecker_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,8 @@ def perform_analysis(args, skip_handlers, rs_handler: ReviewStatusHandler,
ctu_reanalyze_on_failure = 'ctu_reanalyze_on_failure' in args and \
args.ctu_reanalyze_on_failure

analyzers = args.analyzers if 'analyzers' in args \
else analyzer_types.supported_analyzers
analyzers, errored = analyzer_types.check_supported_analyzers(analyzers)
analyzer_types.check_available_analyzers(analyzers, errored)
analyzers, errored = \
analyzer_types.check_available_analyzers(args.analyzers)

ctu_collect = False
ctu_analyze = False
Expand Down Expand Up @@ -248,9 +246,8 @@ def perform_analysis(args, skip_handlers, rs_handler: ReviewStatusHandler,
if state == CheckerState.ENABLED:
enabled_checkers[analyzer].append(check)

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

metadata_tool['analyzers'][analyzer] = metadata_info
Expand Down Expand Up @@ -358,7 +355,8 @@ def perform_analysis(args, skip_handlers, rs_handler: ReviewStatusHandler,
end_time = time.time()
LOG.info("Analysis length: %s sec.", end_time - start_time)

analyzer_types.print_unsupported_analyzers(errored)
if args.analyzers:
analyzer_types.print_unsupported_analyzers(errored)

metadata_tool['timestamps'] = {'begin': start_time,
'end': end_time}
Expand Down
74 changes: 47 additions & 27 deletions analyzer/codechecker_analyzer/analyzer_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@
from argparse import ArgumentTypeError

import os
import platform
import sys

from pathlib import Path

from codechecker_analyzer.arg import analyzer_binary
from codechecker_common import logger
from codechecker_common.checker_labels import CheckerLabels
from codechecker_common.singleton import Singleton
from codechecker_common.util import load_json
from pathlib import Path

from . import env

Expand Down Expand Up @@ -65,20 +63,20 @@ def __init__(self):
self.__package_build_date = None
self.__package_git_hash = None
self.__analyzers = {}
self.__analyzer_env = None

machine = platform.uname().machine
# CodeChecker's current runtime environment
self.__cc_env = None
# cc_env extended with packaged LD_LIBRARY_PATH for packaged binaries
self.__package_env = None
# Original caller environment of CodeChecker for external binaries
self.__original_env = None

self.logger_lib_dir_path = os.path.join(
self._data_files_dir_path, 'ld_logger', 'lib', machine)
self._data_files_dir_path, 'ld_logger', 'lib')

if not os.path.exists(self.logger_lib_dir_path):
self.logger_lib_dir_path = os.path.join(
self._lib_dir_path,
'codechecker_analyzer',
'ld_logger',
'lib',
machine)
self._lib_dir_path, 'codechecker_analyzer', 'ld_logger', 'lib')

self.logger_bin = None
self.logger_file = None
Expand All @@ -93,9 +91,9 @@ def __init__(self):

def __parse_cc_analyzer_bin(self):
env_var_bins = {}
if 'CC_ANALYZER_BIN' in self.analyzer_env:
if 'CC_ANALYZER_BIN' in self.cc_env:
had_error = False
for value in self.__analyzer_env['CC_ANALYZER_BIN'].split(';'):
for value in self.__cc_env['CC_ANALYZER_BIN'].split(';'):
try:
analyzer_name, path = analyzer_binary(value)
except ArgumentTypeError as e:
Expand Down Expand Up @@ -163,6 +161,9 @@ def __init_env(self):
self.env_vars['cc_logger_compiles'])
self.ld_preload = os.environ.get(self.env_vars['ld_preload'])
self.ld_lib_path = self.env_vars['env_ld_lib_path']
self.__original_env = env.get_original_env()
self.__package_env = env.extend(self.path_env_extra,
self.ld_lib_path_extra)

def __set_version(self):
"""
Expand Down Expand Up @@ -194,18 +195,42 @@ def __set_version(self):
logger.DEBUG_ANALYZER):
self.__package_git_tag = package_git_dirtytag

def get_env_for_bin(self, binary):
"""
binary must be a binary with full path
Returns the correct environment for binaries called by CodeChecker.
For binaries packaged with CodeChecker the LD_LIBRARY path is extended.
For non-packaged binaries, the original calling environment
is returned.
"""
bin_path = Path(binary).resolve()
if not bin_path.exists():
LOG.error("Binary %s not found", binary)
return None

codechecker_dir = Path(self._data_files_dir_path)

if str(bin_path).startswith(str(codechecker_dir)):
LOG.debug("Package env is returned for %s", bin_path)
return self.__package_env
else:
LOG.debug("Original env is returned for %s", bin_path)
return self.__original_env

def __populate_analyzers(self):
""" Set analyzer binaries for each registered analyzers. """
analyzer_env = None
cc_env = None
analyzer_from_path = env.is_analyzer_from_path()
if not analyzer_from_path:
analyzer_env = self.analyzer_env
cc_env = self.cc_env

env_var_bin = self.__parse_cc_analyzer_bin()

compiler_binaries = self.pckg_layout.get('analyzers')
for name, value in compiler_binaries.items():
if name in env_var_bin:
# env_var_bin has priority over package config and PATH
self.__analyzers[name] = env_var_bin[name]
continue

Expand All @@ -217,7 +242,7 @@ def __populate_analyzers(self):
self.__analyzers[name] = os.path.join(
self._data_files_dir_path, value)
else:
env_path = analyzer_env['PATH'] if analyzer_env else None
env_path = cc_env['PATH'] if cc_env else None
compiler_binary = which(cmd=value, path=env_path)
if not compiler_binary:
LOG.debug("'%s' binary can not be found in your PATH!",
Expand Down Expand Up @@ -281,12 +306,8 @@ def path_logger_bin(self):
return os.path.join(self._bin_dir_path, 'ld_logger')

@property
def logger_lib_path(self):
"""
Returns the absolute path to the logger library.
"""
return str(Path(self.logger_lib_dir_path,
self.logger_lib_name).absolute())
def path_logger_lib(self):
return self.logger_lib_dir_path

@property
def logger_lib_name(self):
Expand Down Expand Up @@ -320,11 +341,10 @@ def ld_lib_path_extra(self):
return ld_paths

@property
def analyzer_env(self):
if not self.__analyzer_env:
self.__analyzer_env = \
env.extend(self.path_env_extra, self.ld_lib_path_extra)
return self.__analyzer_env
def cc_env(self):
if not self.__cc_env:
self.__cc_env = os.environ.copy()
return self.__cc_env

@property
def analyzer_binaries(self):
Expand Down
18 changes: 10 additions & 8 deletions analyzer/codechecker_analyzer/analyzers/analyzer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def resolve_missing_binary(cls, configured_binary, environ):

@classmethod
@abstractmethod
def get_binary_version(cls, environ, details=False) -> str:
def get_binary_version(cls, details=False) -> str:
"""
Return the version number of the binary that CodeChecker found, even
if its incompatible. If details is true, additional version information
Expand All @@ -68,7 +68,7 @@ def get_binary_version(cls, environ, details=False) -> str:
raise NotImplementedError("Subclasses should implement this!")

@classmethod
def is_binary_version_incompatible(cls, environ) -> Optional[str]:
def is_binary_version_incompatible(cls) -> Optional[str]:
"""
CodeChecker can only execute certain versions of analyzers.
Returns a error object (an optional string). If the return value is
Expand Down Expand Up @@ -102,7 +102,7 @@ def construct_result_handler(self, buildaction, report_output,
"""
raise NotImplementedError("Subclasses should implement this!")

def analyze(self, analyzer_cmd, res_handler, proc_callback=None, env=None):
def analyze(self, analyzer_cmd, res_handler, proc_callback=None):
"""
Run the analyzer.
"""
Expand All @@ -116,8 +116,7 @@ def analyze(self, analyzer_cmd, res_handler, proc_callback=None, env=None):
ret_code, stdout, stderr \
= SourceAnalyzer.run_proc(analyzer_cmd,
res_handler.buildaction.directory,
proc_callback,
env)
proc_callback)
res_handler.analyzer_returncode = ret_code
res_handler.analyzer_stdout = stdout
res_handler.analyzer_stderr = stderr
Expand All @@ -141,7 +140,7 @@ def post_analyze(self, result_handler):
"""

@staticmethod
def run_proc(command, cwd=None, proc_callback=None, env=None):
def run_proc(command, cwd=None, proc_callback=None):
"""
Just run the given command and return the return code
and the stdout and stderr outputs of the process.
Expand All @@ -157,8 +156,11 @@ def signal_handler(signum, _):

signal.signal(signal.SIGINT, signal_handler)

if env is None:
env = analyzer_context.get_context().analyzer_env
env = analyzer_context.get_context().get_env_for_bin(command[0])

LOG.debug('\nexecuting:%s\n', command)
LOG.debug('\nENV:\n')
LOG.debug(env)

proc = subprocess.Popen(
command,
Expand Down
43 changes: 29 additions & 14 deletions analyzer/codechecker_analyzer/analyzers/analyzer_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ def is_ignore_conflict_supported():
proc = subprocess.Popen([context.replacer_binary, '--help'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=context.analyzer_env,
env=context
.get_env_for_bin(
context.replacer_binary),
encoding="utf-8", errors="ignore")
out, _ = proc.communicate()
return '--ignore-insert-conflict' in out
Expand All @@ -113,22 +115,35 @@ def is_ignore_conflict_supported():
def print_unsupported_analyzers(errored):
""" Print error messages which occured during analyzer detection. """
for analyzer_binary, reason in errored:
LOG.warning("Analyzer '%s' is enabled but CodeChecker is failed to "
LOG.warning("Analyzer '%s' is enabled but CodeChecker failed to "
"execute analysis with it: '%s'. Please check your "
"'PATH' environment variable and the "
"'config/package_layout.json' file!",
analyzer_binary, reason)


def check_available_analyzers(analyzers, errored):
""" Handle use case when no analyzer can be found on the user machine. """
if analyzers:
return
def check_available_analyzers(args_analyzers=None):
"""
Handle use case when no analyzer can be found or a supported, explicitly
given analyzer cannot be found on the user machine.
"""

print_unsupported_analyzers(errored)
LOG.error("Failed to run command because no analyzers can be found on "
"your machine!")
sys.exit(1)
if args_analyzers:
analyzers, errored = check_supported_analyzers(args_analyzers)
if errored:
print_unsupported_analyzers(errored)
LOG.error("Failed to run command because the given analyzer(s) "
"cannot be found on your machine!")
sys.exit(1)

else:
analyzers, errored = check_supported_analyzers(supported_analyzers)
if not analyzers:
print_unsupported_analyzers(errored)
LOG.error("Failed to run command because no analyzers can be "
"found on your machine!")
sys.exit(1)
return analyzers, errored


def check_supported_analyzers(analyzers):
Expand All @@ -144,7 +159,6 @@ def check_supported_analyzers(analyzers):
"""

context = analyzer_context.get_context()
check_env = context.analyzer_env

analyzer_binaries = context.analyzer_binaries

Expand All @@ -167,7 +181,8 @@ def check_supported_analyzers(analyzers):
elif not os.path.isabs(analyzer_bin):
# If the analyzer is not in an absolute path, try to find it...
found_bin = supported_analyzers[analyzer_name].\
resolve_missing_binary(analyzer_bin, check_env)
resolve_missing_binary(analyzer_bin,
context.get_env_for_bin(analyzer_bin))

# found_bin is an absolute path, an executable in one of the
# PATH folders.
Expand All @@ -186,7 +201,7 @@ def check_supported_analyzers(analyzers):
# Check version compatibility of the analyzer binary.
if analyzer_bin:
analyzer = supported_analyzers[analyzer_name]
error = analyzer.is_binary_version_incompatible(check_env)
error = analyzer.is_binary_version_incompatible()
if error:
failed_analyzers.add((analyzer_name,
f"Incompatible version: {error} "
Expand All @@ -196,7 +211,7 @@ def check_supported_analyzers(analyzers):
available_analyzer = False

if not analyzer_bin or \
not host_check.check_analyzer(analyzer_bin, check_env):
not host_check.check_analyzer(analyzer_bin):
# Analyzers unavailable under absolute paths are deliberately a
# configuration problem.
failed_analyzers.add((analyzer_name,
Expand Down
Loading

0 comments on commit de7f5be

Please sign in to comment.