1 /**
2 Date: 2015-2016, 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 cpptooling.analyzer.clang.context;
7 
8 import std.typecons : Flag;
9 import logger = std.experimental.logger;
10 
11 version (unittest) {
12     import unit_threaded : Name, shouldEqual;
13 }
14 
15 @safe:
16 
17 /** Convenient context of items needed to practically create a clang AST.
18  *
19  * "Creating a clang AST" means calling $(D makeTranslationUnit).
20  *
21  * Assumes that the scope of a ClangContext is the same as that stated for a
22  * clang Index. Namely that those translation units that are created are the
23  * same as those that would be linked together into an executable or library.
24  *
25  * Items are:
26  *  - An index that all translation units use as input.
27  *  - A VFS providing access to the files that the translation unites are
28  *    derived from.
29  */
30 struct ClangContext {
31     import clang.Index : Index;
32     import clang.TranslationUnit : TranslationUnit;
33 
34     import cpptooling.utility.virtualfilesystem : VirtualFileSystem, FileName,
35         Content;
36 
37     import clang.c.Index : CXTranslationUnit_Flags;
38 
39     private {
40         Index index;
41         string[] internal_header_arg;
42         string[] syntax_only_arg;
43     }
44 
45     /** Access to the virtual filesystem used when instantiating translation
46      * units.
47      *
48      * Note:
49      * NOT using the abbreviation VFS because it is not commonly known. Better
50      * to be specific.
51      */
52     VirtualFileSystem virtualFileSystem;
53 
54     @disable this();
55 
56     // The context is "heavy" therefor disabling moving.
57     @disable this(this);
58 
59     /** Create an instance.
60      *
61      * The binary dextool has clang specified headers attached. Those are feed
62      * to the VFS and used when the flag useInternalHeaders is "yes". To make
63      * them accessable a "-I" parameter with their in-memory location is
64      * supplied to all instantiated translation units.
65      *
66      * Params:
67      *   useInternalHeaders = load the VFS with in-memory system headers.
68      *   prependParamSyntaxOnly = prepend the flag -fsyntax-only to instantiated translation units.
69      */
70     this(Flag!"useInternalHeaders" useInternalHeaders,
71             Flag!"prependParamSyntaxOnly" prependParamSyntaxOnly) {
72         index = Index(false, false);
73         virtualFileSystem = VirtualFileSystem();
74 
75         if (useInternalHeaders) {
76             import cpptooling.utility.virtualfilesystem : FileName, Content;
77             import clang.Compiler : Compiler;
78 
79             Compiler compiler;
80             internal_header_arg = ["-I" ~ compiler.extraIncludePath];
81             foreach (hdr; compiler.extraHeaders) {
82                 virtualFileSystem.openInMemory(cast(FileName) hdr.filename);
83                 virtualFileSystem.write(cast(FileName) hdr.filename, cast(Content) hdr.content);
84             }
85         }
86 
87         if (prependParamSyntaxOnly) {
88             syntax_only_arg = ["-fsyntax-only"];
89         }
90     }
91 
92     /** Create a translation unit from the context.
93      *
94      * The translation unit is NOT kept by the context.
95      */
96     auto makeTranslationUnit(in string sourceFilename, in string[] commandLineArgs = null,
97             uint options = CXTranslationUnit_Flags.detailedPreprocessingRecord) @safe {
98         import std.array : join;
99 
100         auto prependDefaultFlags(string[] in_cflags) {
101             import std.algorithm : canFind;
102 
103             if (in_cflags.canFind(syntax_only_arg)) {
104                 return in_cflags;
105             } else {
106                 return syntax_only_arg ~ in_cflags;
107             }
108         }
109 
110         debug logger.trace("Compiler flags: ", commandLineArgs.join(" "));
111 
112         string[] args = prependDefaultFlags(commandLineArgs ~ internal_header_arg);
113 
114         debug logger.trace("Internal compiler flags: ", args.join(" "));
115 
116         // ensure the file exist in the filesys layer.
117         // it has either been added as an in-memory file by the user or it is
118         // read from the filesystem.
119         virtualFileSystem.open(cast(FileName) sourceFilename);
120 
121         import cpptooling.utility.virtualfilesystem : toClangFiles;
122 
123         auto files = virtualFileSystem.toClangFiles;
124 
125         return TranslationUnit.parse(index, sourceFilename, args, files);
126     }
127 }
128 
129 @("shall be an instance")
130 unittest {
131     import std.typecons : Yes;
132 
133     auto ctx = ClangContext(Yes.useInternalHeaders, Yes.prependParamSyntaxOnly);
134 }