1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 This file contains helpers for interactive with the clang abstractions. 11 */ 12 module dextool.clang; 13 14 import std.typecons : Nullable; 15 import logger = std.experimental.logger; 16 17 import dextool.compilation_db : SearchResult, CompileCommandDB, 18 CompileCommandFilter, CompileCommand, parseFlag, DbCompiler = Compiler; 19 import dextool.type : FileName, AbsolutePath; 20 21 @safe: 22 23 private struct IncludeResult { 24 /// The entry that had an #include with the desired file 25 CompileCommand original; 26 27 /// The compile command derived from the original with adjusted file and 28 /// absoluteFile. 29 CompileCommand derived; 30 } 31 32 /** Find a CompileCommand that in any way have an `#include` which pull in fname. 33 * 34 * This is useful to find the flags needed to parse a header file which is used 35 * by the implementation. 36 * 37 * Note that the context will be expanded with the flags. 38 * 39 * Returns: The first CompileCommand object which _probably_ has the flags needed to parse fname. 40 */ 41 Nullable!IncludeResult findCompileCommandFromIncludes(ref CompileCommandDB compdb, FileName fname, 42 ref const CompileCommandFilter flag_filter, const string[] extra_flags, 43 const DbCompiler user_compiler = DbCompiler.init) @trusted { 44 import std.algorithm : filter; 45 import std.file : exists; 46 import std.path : baseName; 47 import std.typecons : Yes; 48 49 import cpptooling.analyzer.clang.check_parse_result : hasParseErrors, logDiagnostic; 50 import cpptooling.analyzer.clang.context : ClangContext; 51 import cpptooling.analyzer.clang.include_visitor : hasInclude; 52 53 auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly); 54 55 string find_file = fname.baseName; 56 57 bool isMatch(string include) { 58 return find_file == include.baseName; 59 } 60 61 Nullable!IncludeResult r; 62 63 foreach (entry; compdb.filter!(a => exists(a.absoluteFile))) { 64 auto flags = extra_flags ~ entry.parseFlag(flag_filter, user_compiler); 65 auto translation_unit = ctx.makeTranslationUnit(entry.absoluteFile, flags); 66 67 if (translation_unit.hasParseErrors) { 68 logger.warningf("Skipping '%s' because of compilation errors", entry.absoluteFile); 69 logDiagnostic(translation_unit); 70 continue; 71 } 72 73 auto found = translation_unit.cursor.hasInclude!isMatch(); 74 if (!found.isNull) { 75 r = IncludeResult(); 76 r.get.original = entry; 77 r.get.derived = entry; 78 r.get.derived.file = found.get; 79 r.get.derived.absoluteFile = CompileCommand.AbsoluteFileName(entry.directory, 80 found.get); 81 return r; 82 } 83 } 84 85 return r; 86 } 87 88 /// Find flags for fname by searching in the compilation DB. 89 Nullable!SearchResult findFlags(ref CompileCommandDB compdb, FileName fname, const string[] flags, 90 ref const CompileCommandFilter flag_filter, const DbCompiler user_compiler = DbCompiler 91 .init) { 92 import std.file : exists; 93 import std.path : baseName; 94 95 import dextool.compilation_db : appendOrError; 96 97 typeof(return) rval; 98 99 auto db_search_result = compdb.appendOrError(flags, fname, flag_filter); 100 if (!db_search_result.isNull) { 101 rval = SearchResult(db_search_result.get.flags, db_search_result.get.absoluteFile); 102 logger.trace(rval.get.flags); 103 return rval; 104 } 105 106 logger.warningf(`Analyzing all files in the compilation DB for one that has an '#include "%s"'`, 107 fname.baseName); 108 109 auto sres = compdb.findCompileCommandFromIncludes(fname, flag_filter, flags); 110 if (sres.isNull) { 111 logger.error("Unable to find any compiler flags for: ", fname); 112 return rval; 113 } 114 115 // check if the file from the user is directly accessable on the filesystem. 116 // in such a case assume that the located file is the one the user want to parse. 117 // otherwise derive it from the compile command DB. 118 auto p = AbsolutePath(fname); 119 120 if (!exists(p)) { 121 logger.tracef("Unable to locate '%s' on the filesystem", p); 122 p = sres.get.derived.absoluteFile; 123 logger.tracef("Using the filename from the compile DB instead '%s'", p); 124 } 125 126 logger.warningf(`Using compiler flags derived from '%s' because it has an '#include' for '%s'`, 127 sres.get.original.absoluteFile, sres.get.derived.absoluteFile); 128 129 rval = SearchResult(flags ~ sres.get.derived.parseFlag(flag_filter, user_compiler), p); 130 // the user may want to see the flags but usually uninterested 131 logger.trace(rval.get.flags); 132 133 return rval; 134 }