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.filterClangFlags, conf.skipCompilerArgs);
201 
202         return this;
203     }
204 
205     ref CompileCommandFilter getCompileCommandFilter() {
206         return compiler_flag_filter;
207     }
208 
209     /// Data produced by the generatore intented to be written to specified file.
210     ref FileData[] getProducedFiles() {
211         return file_data;
212     }
213 
214     void putFile(AbsolutePath fname, string data) {
215         file_data ~= FileData(fname, data);
216     }
217 
218     /// Signal that a file has finished analyzing.
219     void processIncludes() {
220         td_includes.process();
221     }
222 
223     /// Signal that all files have been analyzed.
224     void finalizeIncludes() {
225         td_includes.finalize();
226     }
227 
228     // -- Controller --
229 
230     bool doFile(in string filename, in string info) {
231         import dextool.plugin.regex_matchers : matchAny;
232 
233         bool restrict_pass = true;
234         bool exclude_pass = true;
235 
236         if (restrict.length > 0) {
237             restrict_pass = matchAny(filename, restrict);
238             debug {
239                 logger.tracef(!restrict_pass, "--file-restrict skipping %s", info);
240             }
241         }
242 
243         if (exclude.length > 0) {
244             exclude_pass = !matchAny(filename, exclude);
245             debug {
246                 logger.tracef(!exclude_pass, "--file-exclude skipping %s", info);
247             }
248         }
249 
250         return restrict_pass && exclude_pass;
251     }
252 
253     bool doGoogleMock() {
254         return gmock;
255     }
256 
257     bool doGoogleTestPODPrettyPrint() {
258         return gtestPP;
259     }
260 
261     bool doPreIncludes() {
262         return pre_incl;
263     }
264 
265     bool doIncludeOfPreIncludes() {
266         return pre_incl;
267     }
268 
269     bool doPostIncludes() {
270         return post_incl;
271     }
272 
273     bool doIncludeOfPostIncludes() {
274         return post_incl;
275     }
276 
277     bool doFreeFunction() {
278         return do_free_funcs;
279     }
280 
281     // -- Parameters --
282 
283     FileName[] getIncludes() {
284         import std.algorithm : map;
285         import std.array : array;
286 
287         return td_includes.includes.map!(a => FileName(a)).array();
288     }
289 
290     MainName getMainName() {
291         return main_name;
292     }
293 
294     MainNs getMainNs() {
295         return main_ns;
296     }
297 
298     MainInterface getMainInterface() {
299         return main_if;
300     }
301 
302     StubPrefix getArtifactPrefix() {
303         return prefix;
304     }
305 
306     DextoolVersion getToolVersion() {
307         import dextool.utility : dextoolVersion;
308 
309         return dextoolVersion;
310     }
311 
312     CustomHeader getCustomHeader() {
313         return custom_hdr;
314     }
315 
316     // -- Products --
317 
318     void putFile(AbsolutePath fname, CppHModule hdr_data) {
319         file_data ~= FileData(fname, hdr_data.render());
320     }
321 
322     void putFile(AbsolutePath fname, CppHModule data, WriteStrategy strategy) {
323         file_data ~= FileData(fname, data.render(), strategy);
324     }
325 
326     void putFile(AbsolutePath fname, CppModule impl_data) {
327         file_data ~= FileData(fname, impl_data.render());
328     }
329 
330     void putLocation(FileName fname, LocationType type) {
331         td_includes.put(fname, type);
332     }
333 }
334 
335 class FrontendTransform : Transform {
336     import std.path : buildPath;
337     import dextool.type : AbsolutePath, DirName, FileName, StubPrefix;
338 
339     static const hdrExt = ".hpp";
340     static const implExt = ".cpp";
341     static const xmlExt = ".xml";
342 
343     StubPrefix prefix;
344 
345     DirName output_dir;
346     MainFileName main_fname;
347 
348     this(MainFileName main_fname, DirName output_dir) {
349         this.main_fname = main_fname;
350         this.output_dir = output_dir;
351     }
352 
353     AbsolutePath createHeaderFile(string name) {
354         return AbsolutePath(FileName(buildPath(output_dir, main_fname ~ name ~ hdrExt)));
355     }
356 
357     AbsolutePath createImplFile(string name) {
358         return AbsolutePath(FileName(buildPath(output_dir, main_fname ~ name ~ implExt)));
359     }
360 
361     AbsolutePath createXmlFile(string name) {
362         return AbsolutePath(FileName(buildPath(output_dir, main_fname ~ name ~ xmlExt)));
363     }
364 }
365 
366 /// TODO refactor, doing too many things.
367 ExitStatusType genCpp(CppTestDoubleVariant variant, FrontendTransform transform,
368         string[] in_cflags, CompileCommandDB compile_db, InFiles in_files) {
369     import std.typecons : Yes;
370 
371     import dextool.clang : findFlags;
372     import dextool.compilation_db : ParseData = SearchResult;
373     import dextool.plugin.cpptestdouble.backend : Backend;
374     import dextool.io : writeFileData;
375     import dextool.type : AbsolutePath;
376     import dextool.utility : prependDefaultFlags, PreferLang;
377 
378     const auto user_cflags = prependDefaultFlags(in_cflags, PreferLang.cpp);
379     const auto total_files = in_files.length;
380     auto generator = Backend(variant, variant, variant, transform);
381 
382     foreach (idx, in_file; in_files) {
383         logger.infof("File %d/%d ", idx + 1, total_files);
384         ParseData pdata;
385 
386         if (compile_db.length > 0) {
387             auto tmp = compile_db.findFlags(FileName(in_file), user_cflags,
388                     variant.getCompileCommandFilter);
389             if (tmp.isNull) {
390                 return ExitStatusType.Errors;
391             }
392             pdata = tmp.get;
393         } else {
394             pdata.flags.prependCflags(user_cflags.dup);
395             pdata.absoluteFile = AbsolutePath(FileName(in_file));
396         }
397 
398         if (generator.analyzeFile(pdata.absoluteFile, pdata.cflags) == ExitStatusType.Errors) {
399             return ExitStatusType.Errors;
400         }
401 
402         variant.processIncludes;
403     }
404 
405     variant.finalizeIncludes;
406 
407     // All files analyzed, process and generate artifacts.
408     generator.process();
409 
410     return writeFileData(variant.file_data);
411 }