1 /**
2 Copyright: Copyright (c) 2015-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 module dextool.plugin.intercept.frontend.intercept;
11 
12 import std.typecons : Nullable;
13 
14 import logger = std.experimental.logger;
15 
16 import dextool.compilation_db;
17 import dextool.type;
18 import dextool.utility;
19 
20 import dextool.plugin.types;
21 
22 import dextool.plugin.intercept.frontend.raw_args : RawConfiguration, Symbols,
23     XmlConfig;
24 
25 import dextool.plugin.intercept.backend.interface_ : Controller, Parameters,
26     Products, FileData;
27 
28 /** Test double generation of C code.
29  *
30  * TODO Describe the options.
31  */
32 class InterceptFrontend : Controller, Parameters, Products {
33     import std.regex : regex, Regex;
34     import std.typecons : Flag;
35     import dextool.compilation_db : CompileCommandFilter;
36     import dextool.type : StubPrefix, FileName, DirName;
37     import cpptooling.testdouble.header_filter : TestDoubleIncludes,
38         LocationType;
39     import dsrcgen.cpp : CppModule, CppHModule;
40     import dsrcgen.sh : ShScriptModule;
41 
42     private {
43         static const hdrExt = ".hpp";
44         static const implExt = ".cpp";
45         static const xmlExt = ".xml";
46         static const shExt = ".sh";
47 
48         StubPrefix prefix;
49 
50         DirName output_dir;
51         FileName main_file_hdr;
52         FileName main_file_impl;
53         FileName script_file;
54         CustomHeader custom_hdr;
55 
56         /// Data produced by the generatore intented to be written to specified file.
57         FileData[] file_data;
58 
59         TestDoubleIncludes td_includes;
60         CompileCommandFilter compiler_flag_filter;
61         Symbols symbols;
62     }
63 
64     static auto makeVariant(ref RawConfiguration args) {
65         // dfmt off
66         auto variant = new InterceptFrontend(
67                 MainFileName(args.mainFileName), DirName(args.out_),
68                 regex(args.stripInclude))
69             .argPrefix(args.prefix)
70             .argForceTestDoubleIncludes(args.testDoubleInclude)
71             .argCustomHeader(args.header, args.headerFile)
72             .argXmlConfig(args.xmlConfig);
73         // dfmt on
74 
75         return variant;
76     }
77 
78     /** Design of c'tor.
79      *
80      * The c'tor has as paramters all the required configuration data.
81      * Assignment of members are used for optional configuration.
82      *
83      * Follows the design pattern "correct by construction".
84      *
85      * TODO document the parameters.
86      */
87     this(MainFileName main_fname, DirName output_dir, Regex!char strip_incl) {
88         this.output_dir = output_dir;
89         this.td_includes = TestDoubleIncludes(strip_incl);
90 
91         import std.path : baseName, buildPath, stripExtension;
92 
93         string base_filename = cast(string) main_fname;
94 
95         this.main_file_hdr = FileName(buildPath(cast(string) output_dir, base_filename ~ hdrExt));
96         this.main_file_impl = FileName(buildPath(cast(string) output_dir, base_filename ~ implExt));
97         this.script_file = FileName(buildPath(output_dir, base_filename ~ shExt));
98     }
99 
100     auto argPrefix(string s) {
101         this.prefix = StubPrefix(s);
102         return this;
103     }
104 
105     /// Force the includes to be those supplied by the user.
106     auto argForceTestDoubleIncludes(string[] a) {
107         if (a.length != 0) {
108             td_includes.forceIncludes(a);
109         }
110         return this;
111     }
112 
113     auto argCustomHeader(string header, string header_file) {
114         if (header.length != 0) {
115             this.custom_hdr = CustomHeader(header);
116         } else if (header_file.length != 0) {
117             import std.file : readText;
118 
119             string content = readText(header_file);
120             this.custom_hdr = CustomHeader(content);
121         }
122 
123         return this;
124     }
125 
126     /** Ensure that the relevant information from the xml file is extracted.
127      *
128      * May overwrite information from the command line.
129      * TODO or should the command line have priority over the xml file?
130      */
131     auto argXmlConfig(Nullable!XmlConfig conf) {
132         import dextool.compilation_db : defaultCompilerFlagFilter;
133 
134         if (conf.isNull) {
135             compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 1);
136             return this;
137         }
138 
139         compiler_flag_filter = CompileCommandFilter(conf.filterClangFlags, conf.skipCompilerArgs);
140         symbols = conf.symbols;
141 
142         return this;
143     }
144 
145     void processIncludes() {
146         td_includes.process();
147     }
148 
149     void finalizeIncludes() {
150         td_includes.finalize();
151     }
152 
153     ref CompileCommandFilter getCompileCommandFilter() {
154         return compiler_flag_filter;
155     }
156 
157     /// Data produced by the generatore intented to be written to specified file.
158     ref FileData[] getProducedFiles() {
159         return file_data;
160     }
161 
162     void putFile(FileName fname, string data) {
163         file_data ~= FileData(fname, data);
164     }
165 
166     // -- Controller --
167 
168     /// If no symbols are specified to be intercept then all are intercepted.
169     bool doSymbol(string symbol) {
170         // fast path, assuming no symbol filter is the most common
171         if (!symbols.hasSymbols) {
172             return true;
173         }
174 
175         return symbols.contains(symbol);
176     }
177 
178     // -- Parameters --
179 
180     FileName[] getIncludes() {
181         import std.algorithm : map;
182         import std.array : array;
183 
184         return td_includes.includes.map!(a => FileName(a)).array();
185     }
186 
187     DirName getOutputDirectory() {
188         return output_dir;
189     }
190 
191     Parameters.Files getFiles() {
192         return Parameters.Files(main_file_hdr, main_file_impl, script_file);
193     }
194 
195     StubPrefix getFilePrefix() {
196         return StubPrefix("");
197     }
198 
199     DextoolVersion getToolVersion() {
200         import dextool.utility : dextoolVersion;
201 
202         return dextoolVersion;
203     }
204 
205     CustomHeader getCustomHeader() {
206         return custom_hdr;
207     }
208 
209     /// Defaults to the global if a specific prefix isn't provided.
210     StubPrefix symbolPrefix(string symbol) {
211         import dextool.plugin.intercept.type : SymbolName;
212 
213         if (auto pref = SymbolName(symbol) in symbols.syms) {
214             return StubPrefix((*pref).prefix);
215         }
216 
217         return prefix;
218     }
219 
220     // -- Products --
221 
222     void putFile(FileName fname, CppHModule hdr_data) {
223         file_data ~= FileData(fname, hdr_data.render());
224     }
225 
226     void putFile(FileName fname, CppModule impl_data) {
227         file_data ~= FileData(fname, impl_data.render());
228     }
229 
230     void putFile(FileName fname, ShScriptModule data) {
231         file_data ~= FileData(fname, data.render());
232     }
233 
234     void putLocation(FileName fname, LocationType type) {
235         td_includes.put(fname, type);
236     }
237 }
238 
239 /// TODO refactor, doing too many things.
240 ExitStatusType genIntercept(InterceptFrontend frontend, in string[] in_cflags,
241         CompileCommandDB compile_db, InFiles in_files) {
242     import std.conv : text;
243     import std.path : buildNormalizedPath, asAbsolutePath;
244     import std.typecons : Yes;
245 
246     import dextool.io : writeFileData;
247     import dextool.plugin.intercept.backend.backend : Backend;
248 
249     const auto user_cflags = prependDefaultFlags(in_cflags, PreferLang.none);
250     const auto total_files = in_files.length;
251     auto backend = Backend(frontend, frontend, frontend);
252 
253     foreach (idx, in_file; in_files) {
254         logger.infof("File %d/%d ", idx + 1, total_files);
255         string[] use_cflags;
256         AbsolutePath abs_in_file;
257 
258         if (compile_db.length > 0) {
259             auto db_search_result = compile_db.appendOrError(user_cflags,
260                     in_file, frontend.getCompileCommandFilter);
261             if (db_search_result.isNull) {
262                 return ExitStatusType.Errors;
263             }
264             use_cflags = db_search_result.get.cflags;
265             abs_in_file = db_search_result.get.absoluteFile;
266         } else {
267             use_cflags = user_cflags.dup;
268             abs_in_file = AbsolutePath(FileName(in_file));
269         }
270 
271         if (backend.analyzeFile(abs_in_file, use_cflags) == ExitStatusType.Errors) {
272             return ExitStatusType.Errors;
273         }
274 
275         frontend.processIncludes;
276     }
277 
278     frontend.finalizeIncludes;
279 
280     // Analyse and generate interceptors
281     backend.process();
282 
283     return writeFileData(frontend.getProducedFiles);
284 }