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 This file contains the frontend for generating a C++ test double.
7 
8 Responsible for:
9  - Receiving the call from the main to start working.
10  - User interaction.
11     - Error reporting in a way that the user understand the error.
12     - Writing files to the filesystem.
13     - Parsing arguments and other interaction information from the user.
14     - Configuration file handling.
15  - Provide user data to the backend via the interface the backend own.
16 */
17 module dextool.plugin.cpptestdouble.frontend.frontend;
18 
19 import std.typecons : Nullable;
20 
21 import logger = std.experimental.logger;
22 
23 import dextool.compilation_db : CompileCommandDB;
24 import dextool.type : AbsolutePath, CustomHeader, DextoolVersion, ExitStatusType,
25     FileName, InFiles, MainFileName, MainName, MainNs, WriteStrategy;
26 
27 import dextool.plugin.cpptestdouble.backend : Controller, Parameters, Products, Transform;
28 import dextool.plugin.cpptestdouble.frontend.raw_args : Config_YesNo, RawConfiguration, XmlConfig;
29 
30 struct FileData {
31     import dextool.type : WriteStrategy;
32 
33     AbsolutePath filename;
34     string data;
35     WriteStrategy strategy;
36 }
37 
38 /** Test double generation of C++ code.
39  *
40  * TODO Describe the options.
41  * TODO implement --in=...
42  */
43 class CppTestDoubleVariant : Controller, Parameters, Products {
44     import std.string : toLower;
45     import std.regex : regex, Regex;
46     import std.typecons : Flag;
47     import dextool.compilation_db : CompileCommandFilter;
48     import dextool.type : StubPrefix, FileName, MainInterface, DirName;
49     import dextool.utility;
50     import cpptooling.testdouble.header_filter : TestDoubleIncludes, LocationType;
51     import dsrcgen.cpp;
52 
53     private {
54         StubPrefix prefix;
55 
56         CustomHeader custom_hdr;
57 
58         MainName main_name;
59         MainNs main_ns;
60         MainInterface main_if;
61         Flag!"FreeFunction" do_free_funcs;
62         Flag!"Gmock" gmock;
63         Flag!"GtestPODPrettyPrint" gtestPP;
64         Flag!"PreInclude" pre_incl;
65         Flag!"PostInclude" post_incl;
66 
67         Nullable!XmlConfig xmlConfig;
68         CompileCommandFilter compiler_flag_filter;
69 
70         Regex!char[] exclude;
71         Regex!char[] restrict;
72 
73         /// Data produced by the generatore intented to be written to specified file.
74         FileData[] file_data;
75 
76         TestDoubleIncludes td_includes;
77     }
78 
79     static auto makeVariant(ref RawConfiguration args) {
80         // dfmt off
81         auto variant = new CppTestDoubleVariant(regex(args.stripInclude))
82             .argPrefix(args.prefix)
83             .argMainName(args.mainName)
84             .argGenFreeFunction(args.doFreeFuncs)
85             .argGmock(args.gmock)
86             .argGtestPODPrettyPrint(args.gtestPODPrettyPrint)
87             .argPreInclude(args.generatePreInclude)
88             .argPostInclude(args.genPostInclude)
89             .argForceTestDoubleIncludes(args.testDoubleInclude)
90             .argFileExclude(args.fileExclude)
91             .argFileRestrict(args.fileRestrict)
92             .argCustomHeader(args.header, args.headerFile)
93             .argXmlConfig(args.xmlConfig);
94         // dfmt on
95 
96         return variant;
97     }
98 
99     /** Design of c'tor.
100      *
101      * The c'tor has as paramters all the required configuration data.
102      * Assignment of members are used for optional configuration.
103      *
104      * Follows the design pattern "correct by construction".
105      *
106      * TODO document the parameters.
107      */
108     this(Regex!char strip_incl) {
109         this.td_includes = TestDoubleIncludes(strip_incl);
110     }
111 
112     auto argFileExclude(string[] a) {
113         import std.array : array;
114         import std.algorithm : map;
115 
116         this.exclude = a.map!(a => regex(a)).array();
117         return this;
118     }
119 
120     auto argFileRestrict(string[] a) {
121         import std.array : array;
122         import std.algorithm : map;
123 
124         this.restrict = a.map!(a => regex(a)).array();
125         return this;
126     }
127 
128     auto argPrefix(string s) {
129         this.prefix = StubPrefix(s);
130         return this;
131     }
132 
133     auto argMainName(string s) {
134         this.main_name = MainName(s);
135         this.main_ns = MainNs(s);
136         this.main_if = MainInterface("I_" ~ s);
137         return this;
138     }
139 
140     /// Force the includes to be those supplied by the user.
141     auto argForceTestDoubleIncludes(string[] a) {
142         if (a.length != 0) {
143             td_includes.forceIncludes(a);
144         }
145         return this;
146     }
147 
148     auto argCustomHeader(string header, string header_file) {
149         if (header.length != 0) {
150             this.custom_hdr = CustomHeader(header);
151         } else if (header_file.length != 0) {
152             import std.file : readText;
153 
154             string content = readText(header_file);
155             this.custom_hdr = CustomHeader(content);
156         }
157 
158         return this;
159     }
160 
161     auto argGenFreeFunction(bool a) {
162         this.do_free_funcs = cast(Flag!"FreeFunction") a;
163         return this;
164     }
165 
166     auto argGmock(bool a) {
167         this.gmock = cast(Flag!"Gmock") a;
168         return this;
169     }
170 
171     auto argGtestPODPrettyPrint(Config_YesNo a) {
172         this.gtestPP = cast(Flag!"GtestPODPrettyPrint")(cast(bool) a);
173         return this;
174     }
175 
176     auto argPreInclude(bool a) {
177         this.pre_incl = cast(Flag!"PreInclude") a;
178         return this;
179     }
180 
181     auto argPostInclude(bool a) {
182         this.post_incl = cast(Flag!"PostInclude") a;
183         return this;
184     }
185 
186     /** Ensure that the relevant information from the xml file is extracted.
187      *
188      * May overwrite information from the command line.
189      * TODO or should the command line have priority over the xml file?
190      */
191     auto argXmlConfig(Nullable!XmlConfig conf) {
192         import dextool.compilation_db : defaultCompilerFlagFilter;
193 
194         if (conf.isNull) {
195             compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 1);
196             return this;
197         }
198 
199         xmlConfig = conf;
200         compiler_flag_filter = CompileCommandFilter(conf.get.filterClangFlags,
201                 conf.get.skipCompilerArgs);
202 
203         return this;
204     }
205 
206     ref CompileCommandFilter getCompileCommandFilter() {
207         return compiler_flag_filter;
208     }
209 
210     /// Data produced by the generatore intented to be written to specified file.
211     ref FileData[] getProducedFiles() {
212         return file_data;
213     }
214 
215     void putFile(AbsolutePath fname, string data) {
216         file_data ~= FileData(fname, data);
217     }
218 
219     /// Signal that a file has finished analyzing.
220     void processIncludes() {
221         td_includes.process();
222     }
223 
224     /// Signal that all files have been analyzed.
225     void finalizeIncludes() {
226         td_includes.finalize();
227     }
228 
229     // -- Controller --
230 
231     bool doFile(in string filename, in string info) {
232         import dextool.plugin.regex_matchers : matchAny;
233 
234         bool restrict_pass = true;
235         bool exclude_pass = true;
236 
237         if (restrict.length > 0) {
238             restrict_pass = matchAny(filename, restrict);
239             debug {
240                 logger.tracef(!restrict_pass, "--file-restrict skipping %s", info);
241             }
242         }
243 
244         if (exclude.length > 0) {
245             exclude_pass = !matchAny(filename, exclude);
246             debug {
247                 logger.tracef(!exclude_pass, "--file-exclude skipping %s", info);
248             }
249         }
250 
251         return restrict_pass && exclude_pass;
252     }
253 
254     bool doGoogleMock() {
255         return gmock;
256     }
257 
258     bool doGoogleTestPODPrettyPrint() {
259         return gtestPP;
260     }
261 
262     bool doPreIncludes() {
263         return pre_incl;
264     }
265 
266     bool doIncludeOfPreIncludes() {
267         return pre_incl;
268     }
269 
270     bool doPostIncludes() {
271         return post_incl;
272     }
273 
274     bool doIncludeOfPostIncludes() {
275         return post_incl;
276     }
277 
278     bool doFreeFunction() {
279         return do_free_funcs;
280     }
281 
282     // -- Parameters --
283 
284     FileName[] getIncludes() {
285         import std.algorithm : map;
286         import std.array : array;
287 
288         return td_includes.includes.map!(a => FileName(a)).array();
289     }
290 
291     MainName getMainName() {
292         return main_name;
293     }
294 
295     MainNs getMainNs() {
296         return main_ns;
297     }
298 
299     MainInterface getMainInterface() {
300         return main_if;
301     }
302 
303     StubPrefix getArtifactPrefix() {
304         return prefix;
305     }
306 
307     DextoolVersion getToolVersion() {
308         import dextool.utility : dextoolVersion;
309 
310         return dextoolVersion;
311     }
312 
313     CustomHeader getCustomHeader() {
314         return custom_hdr;
315     }
316 
317     // -- Products --
318 
319     void putFile(AbsolutePath fname, CppHModule hdr_data) {
320         file_data ~= FileData(fname, hdr_data.render());
321     }
322 
323     void putFile(AbsolutePath fname, CppHModule data, WriteStrategy strategy) {
324         file_data ~= FileData(fname, data.render(), strategy);
325     }
326 
327     void putFile(AbsolutePath fname, CppModule impl_data) {
328         file_data ~= FileData(fname, impl_data.render());
329     }
330 
331     void putLocation(FileName fname, LocationType type) {
332         td_includes.put(fname, type);
333     }
334 }
335 
336 class FrontendTransform : Transform {
337     import std.path : buildPath;
338     import dextool.type : AbsolutePath, DirName, FileName, StubPrefix;
339 
340     static const hdrExt = ".hpp";
341     static const implExt = ".cpp";
342     static const xmlExt = ".xml";
343 
344     StubPrefix prefix;
345 
346     DirName output_dir;
347     MainFileName main_fname;
348 
349     this(MainFileName main_fname, DirName output_dir) {
350         this.main_fname = main_fname;
351         this.output_dir = output_dir;
352     }
353 
354     AbsolutePath createHeaderFile(string name) {
355         return AbsolutePath(FileName(buildPath(output_dir, main_fname ~ name ~ hdrExt)));
356     }
357 
358     AbsolutePath createImplFile(string name) {
359         return AbsolutePath(FileName(buildPath(output_dir, main_fname ~ name ~ implExt)));
360     }
361 
362     AbsolutePath createXmlFile(string name) {
363         return AbsolutePath(FileName(buildPath(output_dir, main_fname ~ name ~ xmlExt)));
364     }
365 }
366 
367 /// TODO refactor, doing too many things.
368 ExitStatusType genCpp(CppTestDoubleVariant variant, FrontendTransform transform,
369         string[] in_cflags, CompileCommandDB compile_db, InFiles in_files) {
370     import std.typecons : Yes;
371 
372     import dextool.clang : findFlags;
373     import dextool.compilation_db : ParseData = SearchResult;
374     import dextool.plugin.cpptestdouble.backend : Backend;
375     import dextool.io : writeFileData;
376     import dextool.type : AbsolutePath;
377     import dextool.utility : prependDefaultFlags, PreferLang;
378 
379     const auto user_cflags = prependDefaultFlags(in_cflags, PreferLang.cpp);
380     const auto total_files = in_files.length;
381     auto generator = Backend(variant, variant, variant, transform);
382 
383     foreach (idx, in_file; in_files) {
384         logger.infof("File %d/%d ", idx + 1, total_files);
385         ParseData pdata;
386 
387         if (compile_db.length > 0) {
388             auto tmp = compile_db.findFlags(FileName(in_file), user_cflags,
389                     variant.getCompileCommandFilter);
390             if (tmp.isNull) {
391                 return ExitStatusType.Errors;
392             }
393             pdata = tmp.get;
394         } else {
395             pdata.flags.prependCflags(user_cflags.dup);
396             pdata.absoluteFile = AbsolutePath(FileName(in_file));
397         }
398 
399         if (generator.analyzeFile(pdata.absoluteFile, pdata.cflags) == ExitStatusType.Errors) {
400             return ExitStatusType.Errors;
401         }
402 
403         variant.processIncludes;
404     }
405 
406     variant.finalizeIncludes;
407 
408     // All files analyzed, process and generate artifacts.
409     generator.process();
410 
411     return writeFileData(variant.file_data);
412 }