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 }