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