1 /**
2 Date: 2015-2017, Joakim Brännström
3 License: MPL-2, Mozilla Public License 2.0
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module dextool.utility;
7 
8 import logger = std.experimental.logger;
9 
10 import dextool.compilation_db : CompileCommandDB, CompileCommand, orDefaultDb, fromFiles;
11 
12 public import dextool.type : AbsolutePath, DextoolVersion, ExitStatusType;
13 
14 version (unittest) {
15     import unit_threaded.assertions : shouldEqual;
16 }
17 
18 @safe:
19 
20 enum PreferLang : string {
21     none = "",
22     c = "-xc",
23     cpp = "-xc++"
24 }
25 
26 pure string[] prependDefaultFlags(const string[] in_cflags, const PreferLang lang) {
27     import std.algorithm : canFind;
28 
29     immutable syntax_only = "-fsyntax-only";
30     if (in_cflags.canFind(syntax_only)) {
31         return prependLangFlagIfMissing(in_cflags, lang);
32     }
33     return syntax_only ~ prependLangFlagIfMissing(in_cflags, lang);
34 }
35 
36 ///TODO move to clang module.
37 pure string[] prependLangFlagIfMissing(in string[] in_cflags, const PreferLang lang) {
38     import std.algorithm : findAmong;
39 
40     auto v = findAmong(in_cflags, [PreferLang.c, PreferLang.cpp]);
41 
42     if (v.length == 0) {
43         return [cast(string) lang] ~ in_cflags;
44     }
45 
46     return in_cflags.dup;
47 }
48 
49 @system unittest {
50     auto cflags = ["-DBEFORE", "-xc++", "-DAND_A_DEFINE", "-I/3906164"];
51     cflags.shouldEqual(prependLangFlagIfMissing(cflags, PreferLang.c));
52 }
53 
54 /** Apply the visitor on the clang AST derived from the input_file.
55  *
56  * Params:
57  *  input_file = path to a file to analyze
58  *  cflags = compiler flags to pass on to clang
59  *  visitor = to apply on the clang AST
60  *  ctx = $(D ClangContext)
61  *
62  * Returns: if the analyze was performed ok or errors occured
63  */
64 ExitStatusType analyzeFile(VisitorT, ClangContextT)(const AbsolutePath input_file,
65         const string[] cflags, VisitorT visitor, ref ClangContextT ctx) @trusted {
66     import std.file : exists;
67 
68     import libclang_ast.ast : ClangAST;
69     import libclang_ast.check_parse_result : hasParseErrors, logDiagnostic;
70 
71     if (!exists(input_file)) {
72         logger.errorf("File '%s' do not exist", input_file);
73         return ExitStatusType.Errors;
74     }
75 
76     logger.infof("Analyzing '%s'", input_file);
77 
78     auto translation_unit = ctx.makeTranslationUnit(input_file, cflags);
79     if (translation_unit.hasParseErrors) {
80         logDiagnostic(translation_unit);
81         logger.error("Compile error...");
82         return ExitStatusType.Errors;
83     }
84 
85     auto ast = ClangAST!VisitorT(translation_unit.cursor);
86     ast.accept(visitor);
87 
88     return ExitStatusType.Ok;
89 }
90 
91 // this is deprecated
92 public import dextool.compilation_db : fromArgCompileDb;
93 
94 /// Version derived from the git archive.
95 import std..string : strip;
96 
97 enum dextoolVersion = DextoolVersion(import("version.txt").strip);
98 
99 static assert(dextoolVersion.length > 0, "Failed to import version.txt at compile time");
100 
101 private long dextoolBinaryId_;
102 /// A unique identifier for this binary of dextool.
103 long dextoolBinaryId() @trusted nothrow {
104     import std.file : thisExePath;
105     import std.stdio : File;
106     import my.hash : BuildChecksum64, toLong;
107 
108     if (dextoolBinaryId_ != 0)
109         return dextoolBinaryId_;
110 
111     // just give up after 10 tries. No good reason. Something is just wrong
112     // then so... and assuming nothing else fataly fails if the version now and
113     // then is "0".
114     for (int i = 0; i < 10; ++i) {
115         try {
116             BuildChecksum64 h;
117             foreach (c; File(thisExePath).byChunk(8196)) {
118                 h.put(c);
119             }
120             dextoolBinaryId_ = cast(long) h.finish.toLong;
121             break;
122         } catch (Exception e) {
123         }
124     }
125 
126     return dextoolBinaryId_;
127 }
128 
129 /// Returns. true if `path` is inside `root`.
130 bool isPathInsideRoot(AbsolutePath root, AbsolutePath path) {
131     import std..string : startsWith;
132     import dextool.utility;
133 
134     return (cast(string) path).startsWith(cast(string) root);
135 }