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