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; 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, 42 FileName fname, ref const CompileCommandFilter flag_filter, const string[] extra_flags) { 43 import std.algorithm : filter; 44 import std.file : exists; 45 import std.path : baseName; 46 import std.typecons : Yes; 47 48 import cpptooling.analyzer.clang.check_parse_result : hasParseErrors, 49 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); 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.original = entry; 77 r.derived = entry; 78 r.derived.file = found.get; 79 r.derived.absoluteFile = CompileCommand.AbsoluteFileName(entry.directory, found.get); 80 return r; 81 } 82 } 83 84 return r; 85 } 86 87 /// Find flags for fname by searching in the compilation DB. 88 Nullable!SearchResult findFlags(ref CompileCommandDB compdb, FileName fname, 89 const string[] flags, ref const CompileCommandFilter flag_filter) { 90 import std.file : exists; 91 import std.path : baseName; 92 import std..string : join; 93 94 import dextool.compilation_db : appendOrError; 95 96 typeof(return) rval; 97 98 auto db_search_result = compdb.appendOrError(flags, fname, flag_filter); 99 if (!db_search_result.isNull) { 100 rval = SearchResult(db_search_result.cflags, db_search_result.absoluteFile); 101 logger.trace("Compiler flags: ", rval.cflags.join(" ")); 102 return rval; 103 } 104 105 logger.warningf(`Analyzing all files in the compilation DB for one that has an '#include "%s"'`, 106 fname.baseName); 107 108 auto sres = compdb.findCompileCommandFromIncludes(fname, flag_filter, flags); 109 if (sres.isNull) { 110 logger.error("Unable to find any compiler flags for: ", fname); 111 return rval; 112 } 113 114 // check if the file from the user is directly accessable on the filesystem. 115 // in such a case assume that the located file is the one the user want to parse. 116 // otherwise derive it from the compile command DB. 117 auto p = AbsolutePath(fname); 118 119 if (!exists(p)) { 120 logger.tracef("Unable to locate '%s' on the filesystem", p); 121 p = sres.derived.absoluteFile; 122 logger.tracef("Using the filename from the compile DB instead '%s'", p); 123 } 124 125 logger.warningf(`Using compiler flags derived from '%s' because it has an '#include' for '%s'`, 126 sres.original.absoluteFile, sres.derived.absoluteFile); 127 128 rval = SearchResult(flags ~ sres.derived.parseFlag(flag_filter), p); 129 // the user may want to see the flags but usually uninterested 130 logger.trace("Compiler flags: ", rval.cflags.join(" ")); 131 132 return rval; 133 }