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 }