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'])