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 }