From ba974e1c86f0ca700ac37f71af6c8a8311154f50 Mon Sep 17 00:00:00 2001 From: bruntib Date: Tue, 15 Feb 2022 15:13:41 +0100 Subject: [PATCH] [analyzer] Proper handling of multi-target build CodeChecker collects implicit compiler info, such as implicit include paths, default target, etc. If a compilation_database.json contains build actions with different target compilations (or different sysroot, etc.) then the implicit infos of the first action will be applied to all build actions. With this patch every build action gets its own implicit info. --- .../codechecker_analyzer/analysis_manager.py | 2 +- analyzer/codechecker_analyzer/analyzer.py | 2 +- .../analyzers/clangsa/analyzer.py | 13 +- .../analyzers/clangsa/ctu_triple_arch.py | 11 +- .../analyzers/clangsa/statistics.py | 14 +- .../analyzers/clangtidy/analyzer.py | 11 +- .../buildlog/build_action.py | 2 +- .../buildlog/log_parser.py | 224 ++++++++---------- .../tests/functional/analyze/test_analyze.py | 14 +- analyzer/tests/unit/test_option_parser.py | 27 +-- 10 files changed, 136 insertions(+), 184 deletions(-) diff --git a/analyzer/codechecker_analyzer/analysis_manager.py b/analyzer/codechecker_analyzer/analysis_manager.py index da38a9a9ea..40dbb26812 100644 --- a/analyzer/codechecker_analyzer/analysis_manager.py +++ b/analyzer/codechecker_analyzer/analysis_manager.py @@ -272,7 +272,7 @@ def handle_reproducer(source_analyzer, rh, zip_file, actions_map): for of in other_files: mentioned_file = os.path.abspath(os.path.join(action.directory, of)) - key = mentioned_file, action.target[action.lang] + key = mentioned_file, action.target mentioned_file_action = actions_map.get(key) if mentioned_file_action is not None: buildactions.append({ diff --git a/analyzer/codechecker_analyzer/analyzer.py b/analyzer/codechecker_analyzer/analyzer.py index 0260e0a486..767938e2eb 100644 --- a/analyzer/codechecker_analyzer/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzer.py @@ -60,7 +60,7 @@ def create_actions_map(actions, manager): result = manager.dict() for act in actions: - key = act.source, act.target[act.lang] + key = act.source, act.target if key in result: LOG.debug("Multiple entires in compile database " "with the same (source, target) pair: (%s, %s)", diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py index 1064e3c919..13d2daaa77 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py @@ -274,19 +274,16 @@ def construct_analyzer_cmd(self, result_handler): analyzer_cmd.extend(['-x', compile_lang]) if not has_flag('--target', analyzer_cmd) and \ - self.buildaction.target.get(compile_lang, "") != "": - analyzer_cmd.append("--target=" + - self.buildaction.target.get(compile_lang)) + self.buildaction.target != "": + analyzer_cmd.append(f"--target={self.buildaction.target}") if not has_flag('-arch', analyzer_cmd) and \ self.buildaction.arch != "": analyzer_cmd.extend(["-arch ", self.buildaction.arch]) if not has_flag('-std', analyzer_cmd) and \ - self.buildaction.compiler_standard.get(compile_lang, "") \ - != "": - analyzer_cmd.append( - self.buildaction.compiler_standard[compile_lang]) + self.buildaction.compiler_standard != "": + analyzer_cmd.append(self.buildaction.compiler_standard) analyzer_cmd.extend(config.analyzer_extra_arguments) @@ -294,7 +291,7 @@ def construct_analyzer_cmd(self, result_handler): analyzer_cmd.extend(prepend_all( '-isystem', - self.buildaction.compiler_includes[compile_lang])) + self.buildaction.compiler_includes)) analyzer_cmd.append(self.source_file) diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py b/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py index f3cfd8a730..6a3d0165c9 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/ctu_triple_arch.py @@ -21,13 +21,10 @@ def get_compile_command(action, config, source='', output=''): cmd = [config.analyzer_binary] - compile_lang = action.lang + if not has_flag('--target', cmd) and action.target != "": + cmd.append(f"--target={action.target}") - if not has_flag('--target', cmd) and \ - action.target[compile_lang] != "": - cmd.append("--target=" + action.target[compile_lang]) - - cmd.extend(prepend_all('-isystem', action.compiler_includes[compile_lang])) + cmd.extend(prepend_all('-isystem', action.compiler_includes)) cmd.append('-c') if not has_flag('-x', cmd): cmd.extend(['-x', action.lang]) @@ -40,7 +37,7 @@ def get_compile_command(action, config, source='', output=''): cmd.append(source) if not has_flag('-std', cmd) and not has_flag('--std', cmd): - cmd.append(action.compiler_standard.get(compile_lang, "")) + cmd.append(action.compiler_standard) return cmd diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py b/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py index 35ae27e730..ba202dc2c8 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/statistics.py @@ -59,23 +59,19 @@ def build_stat_coll_cmd(action, config, source): return [], False for coll_check in collector_checkers: - cmd.extend(['-Xclang', - '-analyzer-checker=' + coll_check]) + cmd.extend(['-Xclang', f'-analyzer-checker={coll_check}']) compile_lang = action.lang if not has_flag('-x', cmd): cmd.extend(['-x', compile_lang]) - if not has_flag('--target', cmd) and \ - action.target.get(compile_lang, "") != "": - cmd.append("--target=" + action.target[compile_lang]) + if not has_flag('--target', cmd) and action.target != "": + cmd.append(f"--target={action.target}") if not has_flag('-std', cmd) and not has_flag('--std', cmd): - cmd.append(action.compiler_standard.get(compile_lang, "")) + cmd.append(action.compiler_standard) - cmd.extend(prepend_all( - '-isystem', - action.compiler_includes.get(compile_lang, []))) + cmd.extend(prepend_all('-isystem', action.compiler_includes)) if source: cmd.append(source) diff --git a/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py b/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py index 3cd768ffae..62d5268c9e 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py @@ -242,10 +242,8 @@ def construct_analyzer_cmd(self, result_handler): analyzer_cmd.extend(['-x', compile_lang]) if not has_flag('--target', analyzer_cmd) and \ - self.buildaction.target.get(compile_lang, "") != "": - analyzer_cmd.append( - "--target=" + self.buildaction.target.get(compile_lang, - "")) + self.buildaction.target != "": + analyzer_cmd.append(f"--target={self.buildaction.target}") if not has_flag('-arch', analyzer_cmd) and \ self.buildaction.arch != "": @@ -255,12 +253,11 @@ def construct_analyzer_cmd(self, result_handler): analyzer_cmd.extend(prepend_all( '-isystem', - self.buildaction.compiler_includes[compile_lang])) + self.buildaction.compiler_includes)) if not has_flag('-std', analyzer_cmd) and not \ has_flag('--std', analyzer_cmd): - analyzer_cmd.append( - self.buildaction.compiler_standard.get(compile_lang, "")) + analyzer_cmd.append(self.buildaction.compiler_standard) analyzer_cmd.extend(compiler_warnings) diff --git a/analyzer/codechecker_analyzer/buildlog/build_action.py b/analyzer/codechecker_analyzer/buildlog/build_action.py index 36fea112b2..a0b35b53d9 100644 --- a/analyzer/codechecker_analyzer/buildlog/build_action.py +++ b/analyzer/codechecker_analyzer/buildlog/build_action.py @@ -73,7 +73,7 @@ def __hash__(self): hash_content = [] hash_content.extend(self.analyzer_options) hash_content.append(str(self.analyzer_type)) - hash_content.append(self.target[self.lang]) + hash_content.append(self.target) hash_content.append(self.source) return hash(''.join(hash_content)) diff --git a/analyzer/codechecker_analyzer/buildlog/log_parser.py b/analyzer/codechecker_analyzer/buildlog/log_parser.py index f05ce5149d..fc243e14ea 100644 --- a/analyzer/codechecker_analyzer/buildlog/log_parser.py +++ b/analyzer/codechecker_analyzer/buildlog/log_parser.py @@ -7,7 +7,7 @@ # ------------------------------------------------------------------------- -from collections import defaultdict +from collections import namedtuple # pylint: disable=no-name-in-module from distutils.spawn import find_executable from enum import Enum @@ -21,6 +21,7 @@ import sys import tempfile import traceback +from typing import Dict, List, Optional from codechecker_report_converter.util import load_json_or_empty @@ -288,13 +289,25 @@ def filter_compiler_includes_extra_args(compiler_flags): class ImplicitCompilerInfo: """ - This class helps to fetch and set some additional compiler flags which are - implicitly added when using GCC. + C/C++ compilers have implicit assumptions about the environment. Especially + GCC has some built-in options which make build process non-portable to + other compilers. For example it comes with a set of include paths that are + implicitly added to all build actions. The list of these paths is also + configurable by some compiler flags (--sysroot, -x, build target related + flags, etc.) The goal of this class is to gather and maintain this implicit + information. """ - # TODO: This dict is mapping compiler to the corresponding information. - # It may not be enough to use the compiler as a key, because the implicit - # information depends on other data like language or target architecture. - compiler_info = defaultdict(dict) + + # Implicit compiler settings (include paths, target triple, etc.) depend on + # these attributes, so we use them as a dictionary key which maps these + # attributes to the implicit settings. In the future we may find that some + # other attributes are also dependencies of implicit compiler info in which + # case this tuple should be extended. + ImplicitInfoSpecifierKey = namedtuple( + 'ImplicitInfoSpecifierKey', + ['compiler', 'language', 'compiler_flags']) + + compiler_info: Dict[ImplicitInfoSpecifierKey, dict] = {} compiler_isexecutable = {} # Store the already detected compiler version information. # If the value is False the compiler is not clang otherwise the value @@ -318,14 +331,14 @@ def is_executable_compiler(compiler): return ImplicitCompilerInfo.compiler_isexecutable[compiler] @staticmethod - def __get_compiler_err(cmd): + def __get_compiler_err(cmd: List[str]) -> Optional[str]: """ Returns the stderr of a compiler invocation as string or None in case of error. """ try: proc = subprocess.Popen( - shlex.split(cmd), + cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -333,21 +346,34 @@ def __get_compiler_err(cmd): encoding="utf-8", errors="ignore") + # The parameter is usually a compile command in this context which + # gets a dash ("-") as a compiler flag. This flag makes gcc + # expecting the source code from the standard input. This is given + # to communicate() function. _, err = proc.communicate("") return err except OSError as oerr: - LOG.error("Error during process execution: " + cmd + '\n' + - oerr.strerror + "\n") + # TODO: shlex.join(cmd) would be more elegant after upgrading to + # Python 3.8. + LOG.error( + "Error during process execution: %s\n%s\n", + ' '.join(map(shlex.quote, cmd)), oerr.strerror) @staticmethod - def __parse_compiler_includes(lines): + def __parse_compiler_includes(compile_cmd: List[str]): """ - Parse the compiler include paths from a string + "gcc -v -E -" prints a set of information about the execution of the + preprocessor. This output contains the implicitly included paths. This + function collects and returns these paths from the output of the gcc + command above. The actual build command is the parameter of this + function because the list of implicit include paths is affected by some + compiler flags (e.g. --sysroot, -x, etc.) """ start_mark = "#include <...> search starts here:" end_mark = "End of search list." include_paths = [] + lines = ImplicitCompilerInfo.__get_compiler_err(compile_cmd) if not lines: return include_paths @@ -383,14 +409,15 @@ def get_compiler_includes(compiler, language, compiler_flags): language -- The programming language being compiled (e.g. 'c' or 'c++') compiler_flags -- the flags used for compilation """ - extra_opts = filter_compiler_includes_extra_args(compiler_flags) - cmd = compiler + " " + ' '.join(extra_opts) \ - + " -E -x " + language + " - -v " + cmd = [compiler, *compiler_flags, '-E', '-x', language, '-', '-v'] - LOG.debug("Retrieving default includes via '" + cmd + "'") + # TODO: shlex.join(cmd) would be more elegant after upgrading to + # Python 3.8. + LOG.debug( + "Retrieving default includes via %s", + ' '.join(map(shlex.quote, cmd))) ICI = ImplicitCompilerInfo - include_dirs = \ - ICI.__parse_compiler_includes(ICI.__get_compiler_err(cmd)) + include_dirs = ICI.__parse_compiler_includes(cmd) return list(map(os.path.normpath, include_dirs)) @@ -402,7 +429,7 @@ def get_compiler_target(compiler): compiler -- The compiler binary of which the target architecture is fetched. """ - lines = ImplicitCompilerInfo.__get_compiler_err(compiler + ' -v') + lines = ImplicitCompilerInfo.__get_compiler_err([compiler, '-v']) if lines is None: return "" @@ -478,7 +505,7 @@ def get_compiler_standard(compiler, language): f.write(VERSION_C if language == 'c' else VERSION_CPP) err = ImplicitCompilerInfo.\ - __get_compiler_err(" ".join([compiler, source.name])) + __get_compiler_err([compiler, source.name]) if err is not None: finding = re.search('CC_FOUND_STANDARD_VER#(.+)', err) @@ -497,47 +524,26 @@ def get_compiler_standard(compiler, language): return standard @staticmethod - def load_compiler_info(filename, compiler): - """Load compiler information from a file.""" - contents = load_json_or_empty(filename, {}) - compiler_info = contents.get(compiler) - if compiler_info is None: - LOG.error("Could not find compiler %s in file %s", - compiler, filename) - return + def dump_compiler_info(file_path: str): + dumpable = { + json.dumps(k): v for k, v + in ImplicitCompilerInfo.compiler_info.items()} + with open(file_path, 'w', encoding="utf-8", errors="ignore") as f: + LOG.debug("Writing compiler info into: %s", file_path) + json.dump(dumpable, f) + + @staticmethod + def load_compiler_info(file_path: str): + """Load compiler information from a file.""" ICI = ImplicitCompilerInfo + ICI.compiler_info = {} - if not ICI.compiler_info.get(compiler): - ICI.compiler_info[compiler] = defaultdict(dict) - - # Load for language C - ICI.compiler_info[compiler][ICI.c()]['compiler_includes'] = [] - c_lang_data = compiler_info.get(ICI.c()) - if c_lang_data: - for element in map(shlex.split, - c_lang_data.get("compiler_includes")): - element = [x for x in element if x != '-isystem'] - ICI.compiler_info[compiler][ICI.c()]['compiler_includes'] \ - .extend(element) - ICI.compiler_info[compiler][ICI.c()]['compiler_standard'] = \ - c_lang_data.get('compiler_standard') - ICI.compiler_info[compiler][ICI.c()]['target'] = \ - c_lang_data.get('target') - - # Load for language C++ - ICI.compiler_info[compiler][ICI.cpp()]['compiler_includes'] = [] - cpp_lang_data = compiler_info.get(ICI.cpp()) - if cpp_lang_data: - for element in map(shlex.split, - cpp_lang_data.get('compiler_includes')): - element = [x for x in element if x != '-isystem'] - ICI.compiler_info[compiler][ICI.cpp()]['compiler_includes'] \ - .extend(element) - ICI.compiler_info[compiler][ICI.cpp()]['compiler_standard'] = \ - cpp_lang_data.get('compiler_standard') - ICI.compiler_info[compiler][ICI.cpp()]['target'] = \ - cpp_lang_data.get('target') + contents = load_json_or_empty(file_path, {}) + for k, v in contents.items(): + k = json.loads(k) + ICI.compiler_info[ + ICI.ImplicitInfoSpecifierKey(k[0], k[1], tuple(k[2]))] = v @staticmethod def set(details, compiler_info_file=None): @@ -546,61 +552,32 @@ def set(details, compiler_info_file=None): If compiler_info_file is available the implicit compiler information will be loaded and set from it. """ + def compiler_info_key(details): + extra_opts = tuple(sorted(filter_compiler_includes_extra_args( + details['analyzer_options']))) + + return ICI.ImplicitInfoSpecifierKey( + details['compiler'], details['lang'], extra_opts) + ICI = ImplicitCompilerInfo - compiler = details['compiler'] + iisk = compiler_info_key(details) + if compiler_info_file and os.path.exists(compiler_info_file): # Compiler info file exists, load it. - ICI.load_compiler_info(compiler_info_file, compiler) + ICI.load_compiler_info(compiler_info_file) else: - # Invoke compiler to gather implicit compiler info. - # Independently of the actual compilation language in the - # compile command collect the iformation for C and C++. - if not ICI.compiler_info.get(compiler): - ICI.compiler_info[compiler] = defaultdict(dict) - - # Collect for C - ICI.compiler_info[compiler][ICI.c()]['compiler_includes'] = \ - ICI.get_compiler_includes(compiler, ICI.c(), - details['analyzer_options']) - ICI.compiler_info[compiler][ICI.c()]['target'] = \ - ICI.get_compiler_target(compiler) - ICI.compiler_info[compiler][ICI.c()]['compiler_standard'] = \ - ICI.get_compiler_standard(compiler, ICI.c()) - - # Collect for C++ - ICI.compiler_info[compiler][ICI.cpp()]['compiler_includes'] = \ - ICI.get_compiler_includes(compiler, ICI.cpp(), - details['analyzer_options']) - ICI.compiler_info[compiler][ICI.cpp()]['target'] = \ - ICI.get_compiler_target(compiler) - ICI.compiler_info[compiler][ICI.cpp()]['compiler_standard'] = \ - ICI.get_compiler_standard(compiler, ICI.cpp()) - - def set_details_from_ICI(key, lang): - """Set compiler related information in the 'details' dictionary. - - If the language dependent value is not set yet, get the compiler - information from ICI. - """ - - parsed_value = details[key].get(lang) - if parsed_value: - details[key][lang] = parsed_value - else: - # Only set what is available from ICI. - compiler_data = ICI.compiler_info.get(compiler) - if compiler_data: - language_data = compiler_data.get(lang) - if language_data: - details[key][lang] = language_data.get(key) - - set_details_from_ICI('compiler_includes', ICI.c()) - set_details_from_ICI('compiler_standard', ICI.c()) - set_details_from_ICI('target', ICI.c()) - - set_details_from_ICI('compiler_includes', ICI.cpp()) - set_details_from_ICI('compiler_standard', ICI.cpp()) - set_details_from_ICI('target', ICI.cpp()) + if iisk not in ICI.compiler_info: + ICI.compiler_info[iisk] = { + 'compiler_includes': ICI.get_compiler_includes( + iisk.compiler, iisk.language, iisk.compiler_flags), + 'compiler_standard': ICI.get_compiler_standard( + iisk.compiler, iisk.language), + 'target': ICI.get_compiler_target(iisk.compiler) + } + + for k, v in ICI.compiler_info.get(iisk, {}).items(): + if not details.get(k): + details[k] = v @staticmethod def get(): @@ -954,8 +931,8 @@ def parse_options(compilation_db_entry, """ details = { 'analyzer_options': [], - 'compiler_includes': defaultdict(dict), # For each language c/cpp. - 'compiler_standard': defaultdict(dict), # For each language c/cpp. + 'compiler_includes': [], + 'compiler_standard': '', 'compilation_target': '', # Compilation target in the compilation cmd. 'analyzer_type': -1, 'original_command': '', @@ -963,7 +940,7 @@ def parse_options(compilation_db_entry, 'output': '', 'lang': None, 'arch': '', # Target in the compile command set by -arch. - 'target': defaultdict(str), + 'target': '', 'source': ''} if 'arguments' in compilation_db_entry: @@ -1074,7 +1051,7 @@ def parse_options(compilation_db_entry, # Option parser detects target architecture but does not know about the # language during parsing. Set the collected compilation target for the # language detected language. - details['target'][lang] = details['compilation_target'] + details['target'] = details['compilation_target'] # With gcc-toolchain a non default compiler toolchain can be set. Clang # will search for include paths and libraries based on the gcc-toolchain @@ -1097,14 +1074,14 @@ def parse_options(compilation_db_entry, ImplicitCompilerInfo.set(details, compiler_info_file) if not keep_gcc_include_fixed: - for lang, includes in details['compiler_includes'].items(): - details['compiler_includes'][lang] = \ - list(filter(__is_not_include_fixed, includes)) + details['compiler_includes'] = list(filter( + __is_not_include_fixed, + details['compiler_includes'])) if not keep_gcc_intrin: - for lang, includes in details['compiler_includes'].items(): - details['compiler_includes'][lang] = \ - list(filter(__contains_no_intrinsic_headers, includes)) + details['compiler_includes'] = list(filter( + __contains_no_intrinsic_headers, + details['compiler_includes'])) # filter out intrin directories aop_without_intrin = [] @@ -1356,11 +1333,8 @@ def parse_unique_log(compilation_database, compile_uniqueing) sys.exit(1) - compiler_info_out = os.path.join(report_dir, "compiler_info.json") - with open(compiler_info_out, 'w', - encoding="utf-8", errors="ignore") as f: - LOG.debug("Writing compiler info into:"+compiler_info_out) - json.dump(ImplicitCompilerInfo.get(), f) + ImplicitCompilerInfo.dump_compiler_info( + os.path.join(report_dir, "compiler_info.json")) LOG.debug('Parsing log file done.') return list(uniqued_build_actions.values()), skipped_cmp_cmd_count diff --git a/analyzer/tests/functional/analyze/test_analyze.py b/analyzer/tests/functional/analyze/test_analyze.py index b3f1ceed31..1656de55b5 100644 --- a/analyzer/tests/functional/analyze/test_analyze.py +++ b/analyzer/tests/functional/analyze/test_analyze.py @@ -213,15 +213,11 @@ def test_compiler_info_file_is_loaded(self): with open(compiler_info_file, 'w', encoding="utf-8", errors="ignore") as source: - source.write('''{ - "clang++": { - "c++": { - "compiler_standard": "-std=FAKE_STD", - "target": "FAKE_TARGET", - "compiler_includes": [ - "-isystem /FAKE_INCLUDE_DIR" - ] - } + source.write(r'''{ + "[\"clang++\", \"c++\", []]": { + "compiler_includes": ["/FAKE_INCLUDE_DIR"], + "compiler_standard": "-std=FAKE_STD", + "target": "FAKE_TARGET" } }''') diff --git a/analyzer/tests/unit/test_option_parser.py b/analyzer/tests/unit/test_option_parser.py index b7da207f10..9ee792d1ea 100644 --- a/analyzer/tests/unit/test_option_parser.py +++ b/analyzer/tests/unit/test_option_parser.py @@ -328,7 +328,7 @@ def test_target_parsing_clang(self): res = log_parser.parse_options(warning_action_clang) self.assertEqual(["-B/tmp/dir"], res.analyzer_options) - self.assertEqual("compilation-target", res.target['c++']) + self.assertEqual("compilation-target", res.target) def test_ignore_xclang_flags_clang(self): """Skip some specific xclang constructs""" @@ -501,10 +501,10 @@ def test_compiler_gcc_implicit_includes(self): # fail. res = log_parser.parse_options(action, keep_gcc_include_fixed=False) self.assertFalse(any([x.endswith('include-fixed') - for x in res.compiler_includes['c++']])) + for x in res.compiler_includes])) res = log_parser.parse_options(action, keep_gcc_include_fixed=True) self.assertTrue(any([x.endswith('include-fixed') - for x in res.compiler_includes['c++']])) + for x in res.compiler_includes])) def test_compiler_intrin_headers(self): """ Include directories with *intrin.h files should be skipped.""" @@ -554,10 +554,10 @@ def contains_intrinsic_headers(dirname): res = log_parser.parse_options(action, keep_gcc_intrin=False) self.assertFalse(any(map(contains_intrinsic_headers, - res.compiler_includes['c++']))) + res.compiler_includes))) res = log_parser.parse_options(action, keep_gcc_intrin=True) self.assertTrue(any(map(contains_intrinsic_headers, - res.compiler_includes['c++']))) + res.compiler_includes))) def test_compiler_include_file(self): action = { @@ -570,19 +570,14 @@ def test_compiler_include_file(self): suffix='.json', encoding='utf-8') as info_file_tmp: - info_file_tmp.write('''{ - "g++": { - "c++": { - "compiler_standard": "-std=FAKE_STD", - "target": "FAKE_TARGET", - "compiler_includes": [ - "-isystem /FAKE_INCLUDE_DIR" - ] - } + info_file_tmp.write(r'''{ + "[\"g++\", \"c++\", []]": { + "compiler_includes": ["/FAKE_INCLUDE_DIR"], + "compiler_standard": "-std=FAKE_STD", + "target": "FAKE_TARGET" } }''') info_file_tmp.flush() res = log_parser.parse_options(action, info_file_tmp.name) - self.assertEqual(res.compiler_includes['c++'], - ['/FAKE_INCLUDE_DIR']) + self.assertEqual(res.compiler_includes, ['/FAKE_INCLUDE_DIR'])