Skip to content

Commit cbd8936

Browse files
committed
Environment initialization for binaries
CodeChecker calls various binaries during analysis: clang, clang-tidy, clang-extdef-mapping etc. If the binary is delivered within the CodeChecker package, LD_LIBRARY_PATH needs to be extended with it. Otherwise the environment of the caller shell should be used for excuting binaries.
1 parent 99fcaf4 commit cbd8936

File tree

14 files changed

+102
-66
lines changed

14 files changed

+102
-66
lines changed

analyzer/codechecker_analyzer/analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ def perform_analysis(args, skip_handlers, rs_handler: ReviewStatusHandler,
247247
enabled_checkers[analyzer].append(check)
248248

249249
version = analyzer_types.supported_analyzers[analyzer] \
250-
.get_binary_version(context.get_analyzer_env(analyzer))
250+
.get_binary_version()
251251
metadata_info['analyzer_statistics']['version'] = version
252252

253253
metadata_tool['analyzers'][analyzer] = metadata_info

analyzer/codechecker_analyzer/analyzer_context.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from codechecker_common.checker_labels import CheckerLabels
2222
from codechecker_common.singleton import Singleton
2323
from codechecker_common.util import load_json
24+
from pathlib import Path
2425

2526
from . import env
2627

@@ -62,8 +63,13 @@ def __init__(self):
6263
self.__package_build_date = None
6364
self.__package_git_hash = None
6465
self.__analyzers = {}
65-
self.__analyzer_envs = {}
66+
67+
# CodeChecker's current runtime environment
6668
self.__cc_env = None
69+
# cc_env extended with packaged LD_LIBRARY_PATH for packaged binaries
70+
self.__package_env = None
71+
# Original caller environment of CodeChecker for external binaries
72+
self.__original_env = None
6773

6874
self.logger_lib_dir_path = os.path.join(
6975
self._data_files_dir_path, 'ld_logger', 'lib')
@@ -155,6 +161,9 @@ def __init_env(self):
155161
self.env_vars['cc_logger_compiles'])
156162
self.ld_preload = os.environ.get(self.env_vars['ld_preload'])
157163
self.ld_lib_path = self.env_vars['env_ld_lib_path']
164+
self.__original_env = env.get_original_env()
165+
self.__package_env = env.extend(self.path_env_extra,
166+
self.ld_lib_path_extra)
158167

159168
def __set_version(self):
160169
"""
@@ -186,6 +195,29 @@ def __set_version(self):
186195
logger.DEBUG_ANALYZER):
187196
self.__package_git_tag = package_git_dirtytag
188197

198+
def get_env_for_bin(self, binary):
199+
"""
200+
binary must be a binary with full path
201+
202+
Returns the correct environment for binaries called by CodeChecker.
203+
For binaries packaged with CodeChecker the LD_LIBRARY path is extended.
204+
For non-packaged binaries, the original calling environment
205+
is returned.
206+
"""
207+
bin_path = Path(binary).resolve()
208+
if not bin_path.exists():
209+
LOG.error("Binary %s not found", binary)
210+
return None
211+
212+
codechecker_dir = Path(self._data_files_dir_path)
213+
214+
if str(bin_path).startswith(str(codechecker_dir)):
215+
LOG.debug("Package env is returned for %s", bin_path)
216+
return self.__package_env
217+
else:
218+
LOG.debug("Original env is returned for %s", bin_path)
219+
return self.__original_env
220+
189221
def __populate_analyzers(self):
190222
""" Set analyzer binaries for each registered analyzers. """
191223
cc_env = None
@@ -198,9 +230,8 @@ def __populate_analyzers(self):
198230
compiler_binaries = self.pckg_layout.get('analyzers')
199231
for name, value in compiler_binaries.items():
200232
if name in env_var_bin:
201-
# For non-packaged analyzers the original env is set.
233+
# env_var_bin has priority over package config and PATH
202234
self.__analyzers[name] = env_var_bin[name]
203-
self.__analyzer_envs[name] = env.get_original_env()
204235
continue
205236

206237
if analyzer_from_path:
@@ -210,10 +241,6 @@ def __populate_analyzers(self):
210241
# Check if it is a package relative path.
211242
self.__analyzers[name] = os.path.join(
212243
self._data_files_dir_path, value)
213-
# For packaged analyzers the ld_library path
214-
# must be extended with the packed libs.
215-
self.__analyzer_envs[name] =\
216-
env.extend(self.path_env_extra, self.ld_lib_path_extra)
217244
else:
218245
env_path = cc_env['PATH'] if cc_env else None
219246
compiler_binary = which(cmd=value, path=env_path)
@@ -224,17 +251,12 @@ def __populate_analyzers(self):
224251
continue
225252

226253
self.__analyzers[name] = os.path.realpath(compiler_binary)
227-
# For non-packaged analyzers the original env is set.
228-
self.__analyzer_envs[name] = env.get_original_env()
229254

230255
# If the compiler binary is a simlink to ccache, use the
231256
# original compiler binary.
232257
if self.__analyzers[name].endswith("/ccache"):
233258
self.__analyzers[name] = compiler_binary
234259

235-
def get_analyzer_env(self, analyzer_name):
236-
return self.__analyzer_envs.get(analyzer_name)
237-
238260
def __populate_replacer(self):
239261
""" Set clang-apply-replacements tool. """
240262
replacer_binary = self.pckg_layout.get('clang-apply-replacements')
@@ -243,12 +265,8 @@ def __populate_replacer(self):
243265
# Check if it is a package relative path.
244266
self.__replacer = os.path.join(self._data_files_dir_path,
245267
replacer_binary)
246-
self.__analyzer_envs['clang-apply-replacements'] =\
247-
env.extend(self.path_env_extra, self.ld_lib_path_extra)
248268
else:
249269
self.__replacer = which(replacer_binary)
250-
self.__analyzer_envs['clang-apply-replacements'] =\
251-
env.get_original_env()
252270

253271
@property
254272
def version(self):

analyzer/codechecker_analyzer/analyzers/analyzer_base.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def resolve_missing_binary(cls, configured_binary, environ):
5858

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

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

105-
def analyze(self, analyzer_cmd, res_handler, proc_callback=None, env=None):
105+
def analyze(self, analyzer_cmd, res_handler, proc_callback=None):
106106
"""
107107
Run the analyzer.
108108
"""
@@ -111,17 +111,12 @@ def analyze(self, analyzer_cmd, res_handler, proc_callback=None, env=None):
111111
LOG.debug_analyzer('\n%s',
112112
' '.join([shlex.quote(x) for x in analyzer_cmd]))
113113

114-
if env is None:
115-
env = analyzer_context.get_context()\
116-
.get_analyzer_env(self.ANALYZER_NAME)
117-
118114
res_handler.analyzer_cmd = analyzer_cmd
119115
try:
120116
ret_code, stdout, stderr \
121117
= SourceAnalyzer.run_proc(analyzer_cmd,
122118
res_handler.buildaction.directory,
123-
proc_callback,
124-
env)
119+
proc_callback)
125120
res_handler.analyzer_returncode = ret_code
126121
res_handler.analyzer_stdout = stdout
127122
res_handler.analyzer_stderr = stderr
@@ -145,7 +140,7 @@ def post_analyze(self, result_handler):
145140
"""
146141

147142
@staticmethod
148-
def run_proc(command, cwd=None, proc_callback=None, env=None):
143+
def run_proc(command, cwd=None, proc_callback=None):
149144
"""
150145
Just run the given command and return the return code
151146
and the stdout and stderr outputs of the process.
@@ -161,9 +156,9 @@ def signal_handler(signum, _):
161156

162157
signal.signal(signal.SIGINT, signal_handler)
163158

164-
if env is None:
165-
env = analyzer_context.get_context().cc_env
159+
env = analyzer_context.get_context().get_env_for_bin(command[0])
166160

161+
LOG.debug_analyzer('\nexecuting:%s\n', command)
167162
LOG.debug_analyzer('\nENV:\n')
168163
LOG.debug_analyzer(env)
169164

analyzer/codechecker_analyzer/analyzers/analyzer_types.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ def is_ignore_conflict_supported():
105105
stdout=subprocess.PIPE,
106106
stderr=subprocess.PIPE,
107107
env=context
108-
.get_analyzer_env(
109-
os.path.basename(context.replacer_binary)),
108+
.get_env_for_bin(
109+
context.replacer_binary),
110110
encoding="utf-8", errors="ignore")
111111
out, _ = proc.communicate()
112112
return '--ignore-insert-conflict' in out
@@ -159,7 +159,6 @@ def check_supported_analyzers(analyzers):
159159
"""
160160

161161
context = analyzer_context.get_context()
162-
check_env = context.cc_env
163162

164163
analyzer_binaries = context.analyzer_binaries
165164

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

187187
# found_bin is an absolute path, an executable in one of the
188188
# PATH folders.
@@ -201,7 +201,7 @@ def check_supported_analyzers(analyzers):
201201
# Check version compatibility of the analyzer binary.
202202
if analyzer_bin:
203203
analyzer = supported_analyzers[analyzer_name]
204-
error = analyzer.is_binary_version_incompatible(check_env)
204+
error = analyzer.is_binary_version_incompatible()
205205
if error:
206206
failed_analyzers.add((analyzer_name,
207207
f"Incompatible version: {error} "
@@ -211,7 +211,7 @@ def check_supported_analyzers(analyzers):
211211
available_analyzer = False
212212

213213
if not analyzer_bin or \
214-
not host_check.check_analyzer(analyzer_bin, check_env):
214+
not host_check.check_analyzer(analyzer_bin):
215215
# Analyzers unavailable under absolute paths are deliberately a
216216
# configuration problem.
217217
failed_analyzers.add((analyzer_name,

analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def parse_clang_help_page(
5252
command,
5353
stderr=subprocess.STDOUT,
5454
env=analyzer_context.get_context()
55-
.get_analyzer_env(ClangSA.ANALYZER_NAME),
55+
.get_env_for_bin(command[0]),
5656
universal_newlines=True,
5757
encoding="utf-8",
5858
errors="ignore")
@@ -172,8 +172,11 @@ def __add_plugin_load_flags(cls, analyzer_cmd: List[str]):
172172
analyzer_cmd.extend(["-load", plugin])
173173

174174
@classmethod
175-
def get_binary_version(cls, environ, details=False) -> str:
175+
def get_binary_version(cls, details=False) -> str:
176176
# No need to LOG here, we will emit a warning later anyway.
177+
178+
environ = analyzer_context.get_context().get_env_for_bin(
179+
cls.analyzer_binary())
177180
if not cls.analyzer_binary():
178181
return None
179182

@@ -209,7 +212,7 @@ def ctu_capability(cls):
209212
cls.__ctu_autodetection = CTUAutodetection(
210213
cls.analyzer_binary(),
211214
analyzer_context.get_context()
212-
.get_analyzer_env(ClangSA.ANALYZER_NAME))
215+
.get_env_for_bin(cls.analyzer_binary()))
213216

214217
return cls.__ctu_autodetection
215218

@@ -587,7 +590,7 @@ def resolve_missing_binary(cls, configured_binary, environ):
587590
return clang
588591

589592
@classmethod
590-
def is_binary_version_incompatible(cls, environ):
593+
def is_binary_version_incompatible(cls):
591594
"""
592595
We support pretty much every ClangSA version.
593596
"""
@@ -609,7 +612,8 @@ def construct_result_handler(self, buildaction, report_output,
609612
def construct_config_handler(cls, args):
610613

611614
context = analyzer_context.get_context()
612-
environ = context.get_analyzer_env(ClangSA.ANALYZER_NAME)
615+
environ = context.get_env_for_bin(
616+
cls.analyzer_binary())
613617

614618
handler = config_handler.ClangSAConfigHandler(environ)
615619

analyzer/codechecker_analyzer/analyzers/clangsa/ctu_manager.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,10 @@ def generate_ast(triple_arch, action, source, config):
126126
os.makedirs(ast_dir)
127127
except OSError:
128128
pass
129-
130129
cmdstr = ' '.join(cmd)
131130
LOG.debug_analyzer("Generating AST using '%s'", cmdstr)
132131
ret_code, _, err = \
133-
analyzer_base.SourceAnalyzer.run_proc(cmd, action.directory)
132+
analyzer_base.SourceAnalyzer.run_proc(cmd, action.directory, None)
134133

135134
if ret_code != 0:
136135
LOG.error("Error generating AST.\n\ncommand:\n\n%s\n\nstderr:\n\n%s",

analyzer/codechecker_analyzer/analyzers/clangsa/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def get(clang_binary):
7777
"""
7878
compiler_version = subprocess.check_output(
7979
[clang_binary, '--version'],
80-
env=analyzer_context.get_context().get_analyzer_env("clangsa"),
80+
env=analyzer_context.get_context().get_env_for_bin(clang_binary),
8181
encoding="utf-8",
8282
errors="ignore")
8383
version_parser = ClangVersionInfoParser(clang_binary)

analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,10 @@ def analyzer_binary(cls):
236236
.analyzer_binaries[cls.ANALYZER_NAME]
237237

238238
@classmethod
239-
def get_binary_version(cls, environ, details=False) -> str:
239+
def get_binary_version(cls, details=False) -> str:
240240
# No need to LOG here, we will emit a warning later anyway.
241+
environ = analyzer_context.get_context().get_env_for_bin(
242+
cls.analyzer_binary())
241243
if not cls.analyzer_binary():
242244
return None
243245

@@ -271,7 +273,7 @@ def get_analyzer_checkers(cls):
271273
return cls.__analyzer_checkers
272274

273275
environ = analyzer_context\
274-
.get_context().get_analyzer_env(cls.ANALYZER_NAME)
276+
.get_context().get_env_for_bin(cls.analyzer_binary())
275277
result = subprocess.check_output(
276278
[cls.analyzer_binary(), "-list-checks", "-checks=*"],
277279
env=environ,
@@ -299,7 +301,7 @@ def get_checker_config(cls):
299301
result = subprocess.check_output(
300302
[cls.analyzer_binary(), "-dump-config", "-checks=*"],
301303
env=analyzer_context.get_context()
302-
.get_analyzer_env(cls.ANALYZER_NAME),
304+
.get_env_for_bin(cls.analyzer_binary()),
303305
universal_newlines=True,
304306
encoding="utf-8",
305307
errors="ignore")
@@ -316,7 +318,7 @@ def get_analyzer_config(cls):
316318
result = subprocess.check_output(
317319
[cls.analyzer_binary(), "-dump-config", "-checks=*"],
318320
env=analyzer_context.get_context()
319-
.get_analyzer_env(cls.ANALYZER_NAME),
321+
.get_env_for_bin(cls.analyzer_binary()),
320322
universal_newlines=True,
321323
encoding="utf-8",
322324
errors="ignore")
@@ -567,7 +569,7 @@ def resolve_missing_binary(cls, configured_binary, environ):
567569
return clangtidy
568570

569571
@classmethod
570-
def is_binary_version_incompatible(cls, environ):
572+
def is_binary_version_incompatible(cls):
571573
"""
572574
We support pretty much every Clang-Tidy version.
573575
"""

analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ def analyzer_binary(cls):
8282
.analyzer_binaries[cls.ANALYZER_NAME]
8383

8484
@classmethod
85-
def get_binary_version(cls, environ, details=False) -> str:
85+
def get_binary_version(cls, details=False) -> str:
8686
""" Get analyzer version information. """
8787
# No need to LOG here, we will emit a warning later anyway.
88+
environ = analyzer_context.get_context().get_env_for_bin(
89+
cls.analyzer_binary())
8890
if not cls.analyzer_binary():
8991
return None
9092
version = [cls.analyzer_binary(), '--version']
@@ -324,11 +326,11 @@ def resolve_missing_binary(cls, configured_binary, environ):
324326
return cppcheck
325327

326328
@classmethod
327-
def is_binary_version_incompatible(cls, environ):
329+
def is_binary_version_incompatible(cls):
328330
"""
329331
Check the version compatibility of the given analyzer binary.
330332
"""
331-
analyzer_version = cls.get_binary_version(environ)
333+
analyzer_version = cls.get_binary_version()
332334

333335
# The analyzer version should be above 1.80 because '--plist-output'
334336
# argument was introduced in this release.

0 commit comments

Comments
 (0)