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