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