1 /** 2 Copyright: Copyright (c) 2015-2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.intercept.frontend.intercept; 11 12 import std.typecons : Nullable; 13 14 import logger = std.experimental.logger; 15 16 import dextool.compilation_db; 17 import dextool.type; 18 import dextool.utility; 19 20 import dextool.plugin.types; 21 22 import dextool.plugin.intercept.frontend.raw_args : RawConfiguration, Symbols, 23 XmlConfig; 24 25 import dextool.plugin.intercept.backend.interface_ : Controller, Parameters, 26 Products, FileData; 27 28 /** Test double generation of C code. 29 * 30 * TODO Describe the options. 31 */ 32 class InterceptFrontend : Controller, Parameters, Products { 33 import std.regex : regex, Regex; 34 import std.typecons : Flag; 35 import dextool.compilation_db : CompileCommandFilter; 36 import dextool.type : StubPrefix, FileName, DirName; 37 import cpptooling.testdouble.header_filter : TestDoubleIncludes, 38 LocationType; 39 import dsrcgen.cpp : CppModule, CppHModule; 40 import dsrcgen.sh : ShScriptModule; 41 42 private { 43 static const hdrExt = ".hpp"; 44 static const implExt = ".cpp"; 45 static const xmlExt = ".xml"; 46 static const shExt = ".sh"; 47 48 StubPrefix prefix; 49 50 DirName output_dir; 51 FileName main_file_hdr; 52 FileName main_file_impl; 53 FileName script_file; 54 CustomHeader custom_hdr; 55 56 /// Data produced by the generatore intented to be written to specified file. 57 FileData[] file_data; 58 59 TestDoubleIncludes td_includes; 60 CompileCommandFilter compiler_flag_filter; 61 Symbols symbols; 62 } 63 64 static auto makeVariant(ref RawConfiguration args) { 65 // dfmt off 66 auto variant = new InterceptFrontend( 67 MainFileName(args.mainFileName), DirName(args.out_), 68 regex(args.stripInclude)) 69 .argPrefix(args.prefix) 70 .argForceTestDoubleIncludes(args.testDoubleInclude) 71 .argCustomHeader(args.header, args.headerFile) 72 .argXmlConfig(args.xmlConfig); 73 // dfmt on 74 75 return variant; 76 } 77 78 /** Design of c'tor. 79 * 80 * The c'tor has as paramters all the required configuration data. 81 * Assignment of members are used for optional configuration. 82 * 83 * Follows the design pattern "correct by construction". 84 * 85 * TODO document the parameters. 86 */ 87 this(MainFileName main_fname, DirName output_dir, Regex!char strip_incl) { 88 this.output_dir = output_dir; 89 this.td_includes = TestDoubleIncludes(strip_incl); 90 91 import std.path : baseName, buildPath, stripExtension; 92 93 string base_filename = cast(string) main_fname; 94 95 this.main_file_hdr = FileName(buildPath(cast(string) output_dir, base_filename ~ hdrExt)); 96 this.main_file_impl = FileName(buildPath(cast(string) output_dir, base_filename ~ implExt)); 97 this.script_file = FileName(buildPath(output_dir, base_filename ~ shExt)); 98 } 99 100 auto argPrefix(string s) { 101 this.prefix = StubPrefix(s); 102 return this; 103 } 104 105 /// Force the includes to be those supplied by the user. 106 auto argForceTestDoubleIncludes(string[] a) { 107 if (a.length != 0) { 108 td_includes.forceIncludes(a); 109 } 110 return this; 111 } 112 113 auto argCustomHeader(string header, string header_file) { 114 if (header.length != 0) { 115 this.custom_hdr = CustomHeader(header); 116 } else if (header_file.length != 0) { 117 import std.file : readText; 118 119 string content = readText(header_file); 120 this.custom_hdr = CustomHeader(content); 121 } 122 123 return this; 124 } 125 126 /** Ensure that the relevant information from the xml file is extracted. 127 * 128 * May overwrite information from the command line. 129 * TODO or should the command line have priority over the xml file? 130 */ 131 auto argXmlConfig(Nullable!XmlConfig conf) { 132 import dextool.compilation_db : defaultCompilerFlagFilter; 133 134 if (conf.isNull) { 135 compiler_flag_filter = CompileCommandFilter(defaultCompilerFlagFilter, 1); 136 return this; 137 } 138 139 compiler_flag_filter = CompileCommandFilter(conf.filterClangFlags, conf.skipCompilerArgs); 140 symbols = conf.symbols; 141 142 return this; 143 } 144 145 void processIncludes() { 146 td_includes.process(); 147 } 148 149 void finalizeIncludes() { 150 td_includes.finalize(); 151 } 152 153 ref CompileCommandFilter getCompileCommandFilter() { 154 return compiler_flag_filter; 155 } 156 157 /// Data produced by the generatore intented to be written to specified file. 158 ref FileData[] getProducedFiles() { 159 return file_data; 160 } 161 162 void putFile(FileName fname, string data) { 163 file_data ~= FileData(fname, data); 164 } 165 166 // -- Controller -- 167 168 /// If no symbols are specified to be intercept then all are intercepted. 169 bool doSymbol(string symbol) { 170 // fast path, assuming no symbol filter is the most common 171 if (!symbols.hasSymbols) { 172 return true; 173 } 174 175 return symbols.contains(symbol); 176 } 177 178 // -- Parameters -- 179 180 FileName[] getIncludes() { 181 import std.algorithm : map; 182 import std.array : array; 183 184 return td_includes.includes.map!(a => FileName(a)).array(); 185 } 186 187 DirName getOutputDirectory() { 188 return output_dir; 189 } 190 191 Parameters.Files getFiles() { 192 return Parameters.Files(main_file_hdr, main_file_impl, script_file); 193 } 194 195 StubPrefix getFilePrefix() { 196 return StubPrefix(""); 197 } 198 199 DextoolVersion getToolVersion() { 200 import dextool.utility : dextoolVersion; 201 202 return dextoolVersion; 203 } 204 205 CustomHeader getCustomHeader() { 206 return custom_hdr; 207 } 208 209 /// Defaults to the global if a specific prefix isn't provided. 210 StubPrefix symbolPrefix(string symbol) { 211 import dextool.plugin.intercept.type : SymbolName; 212 213 if (auto pref = SymbolName(symbol) in symbols.syms) { 214 return StubPrefix((*pref).prefix); 215 } 216 217 return prefix; 218 } 219 220 // -- Products -- 221 222 void putFile(FileName fname, CppHModule hdr_data) { 223 file_data ~= FileData(fname, hdr_data.render()); 224 } 225 226 void putFile(FileName fname, CppModule impl_data) { 227 file_data ~= FileData(fname, impl_data.render()); 228 } 229 230 void putFile(FileName fname, ShScriptModule data) { 231 file_data ~= FileData(fname, data.render()); 232 } 233 234 void putLocation(FileName fname, LocationType type) { 235 td_includes.put(fname, type); 236 } 237 } 238 239 /// TODO refactor, doing too many things. 240 ExitStatusType genIntercept(InterceptFrontend frontend, in string[] in_cflags, 241 CompileCommandDB compile_db, InFiles in_files) { 242 import std.conv : text; 243 import std.path : buildNormalizedPath, asAbsolutePath; 244 import std.typecons : Yes; 245 246 import dextool.io : writeFileData; 247 import dextool.plugin.intercept.backend.backend : Backend; 248 249 const auto user_cflags = prependDefaultFlags(in_cflags, PreferLang.none); 250 const auto total_files = in_files.length; 251 auto backend = Backend(frontend, frontend, frontend); 252 253 foreach (idx, in_file; in_files) { 254 logger.infof("File %d/%d ", idx + 1, total_files); 255 string[] use_cflags; 256 AbsolutePath abs_in_file; 257 258 if (compile_db.length > 0) { 259 auto db_search_result = compile_db.appendOrError(user_cflags, 260 in_file, frontend.getCompileCommandFilter); 261 if (db_search_result.isNull) { 262 return ExitStatusType.Errors; 263 } 264 use_cflags = db_search_result.get.cflags; 265 abs_in_file = db_search_result.get.absoluteFile; 266 } else { 267 use_cflags = user_cflags.dup; 268 abs_in_file = AbsolutePath(FileName(in_file)); 269 } 270 271 if (backend.analyzeFile(abs_in_file, use_cflags) == ExitStatusType.Errors) { 272 return ExitStatusType.Errors; 273 } 274 275 frontend.processIncludes; 276 } 277 278 frontend.finalizeIncludes; 279 280 // Analyse and generate interceptors 281 backend.process(); 282 283 return writeFileData(frontend.getProducedFiles); 284 }