1 /** 2 Code to make the executable do what it does at runtime. 3 */ 4 module dpp.runtime.app; 5 6 import dpp.from; 7 8 /** 9 The "real" main 10 */ 11 void run(in from!"dpp.runtime.options".Options options) @safe { 12 import std.stdio: File; 13 import std.exception: enforce; 14 import std.process: execute; 15 import std.array: join; 16 import std.file: remove; 17 18 foreach(dppFileName; options.dppFileNames) 19 preprocess(options, dppFileName, options.toDFileName(dppFileName)); 20 21 if(options.preprocessOnly) return; 22 23 // See #102. We need some C++ boilerplate to link to the application. 24 scope(exit) if(options.cppStdLib) remove(CppBoilerplateCode.objFileName); 25 if(options.cppStdLib) CppBoilerplateCode.generate; 26 const cppExtraArgs = options.cppStdLib ? [CppBoilerplateCode.objFileName] : []; 27 28 const args = options.dlangCompiler ~ options.dlangCompilerArgs ~ cppExtraArgs; 29 const res = execute(args); 30 enforce(res.status == 0, "Could not execute `" ~ args.join(" ") ~ "`:\n" ~ res.output); 31 32 if(!options.keepDlangFiles) { 33 foreach(fileName; options.dFileNames) 34 remove(fileName); 35 } 36 } 37 38 39 // See #102. We need some C++ boilerplate to link to the application. 40 private struct CppBoilerplateCode { 41 42 enum baseFileName = "cpp_boilerplate"; 43 enum srcFileName = baseFileName ~ ".cpp"; 44 version(Windows) 45 enum objFileName = baseFileName ~ ".obj"; 46 else 47 enum objFileName = baseFileName ~ ".o"; 48 49 50 static void generate() @safe { 51 52 import std.stdio: File; 53 import std.file: remove; 54 55 writeSrcFile; 56 scope(exit) remove(srcFileName); 57 compileSrcFile; 58 } 59 60 ~this() @safe { 61 import std.file: remove; 62 remove(objFileName); 63 } 64 65 static void writeSrcFile() @safe { 66 import std.stdio: File; 67 68 auto file = File(srcFileName, "w"); 69 file.writeln(`#include <vector>`); 70 file.writeln(`void cpp_stdlib_boilerplate_dpp() {`); 71 file.writeln(` std::vector<bool> v;`); 72 file.writeln(` (void) (v[0] == v[0]);`); 73 file.writeln(` (void) (v.begin() == v.begin());`); 74 file.writeln(`}`); 75 } 76 77 static void compileSrcFile() @safe { 78 import std.process: execute; 79 import std.file: exists; 80 import std.exception: enforce; 81 82 const args = ["-c", srcFileName]; 83 const gccArgs = "g++" ~ args; 84 const clangArgs = "clang++" ~ args; 85 86 scope(success) enforce(objFileName.exists, objFileName ~ " was expected to exist but did not"); 87 88 const gccRet = execute(gccArgs); 89 if(gccRet.status == 0) return; 90 91 const clangRet = execute(clangArgs); 92 if(clangRet.status != 0) 93 throw new Exception("Could not compile C++ boilerplate with either gcc or clang"); 94 } 95 } 96 97 98 /** 99 Preprocesses a .dpp file, expanding #include directives inline while 100 translating all definitions, and redefines any macros defined therein. 101 102 The output is a valid D file that can be compiled. 103 104 Params: 105 options = The runtime options. 106 inputFileName = the name of the file to read 107 outputFileName = the name of the file to write 108 */ 109 private void preprocess(in from!"dpp.runtime.options".Options options, 110 in string inputFileName, 111 in string outputFileName) 112 @safe 113 { 114 import std.file: remove, exists, mkdirRecurse, copy; 115 import std.stdio: File; 116 import std.path: dirName; 117 118 const outputPath = outputFileName.dirName; 119 if(!outputPath.exists) mkdirRecurse(outputPath); 120 121 const tmpFileName = outputFileName ~ ".tmp"; 122 scope(exit) if(!options.keepPreCppFiles) remove(tmpFileName); 123 124 { 125 auto outputFile = File(tmpFileName, "w"); 126 127 const translationText = translationText(options, inputFileName); 128 129 writeUndefLines(inputFileName, outputFile); 130 131 outputFile.writeln(translationText.moduleDeclaration); 132 outputFile.writeln(preamble(options.ignoreMacros)); 133 outputFile.writeln(translationText.dlangDeclarations); 134 135 // write original D code 136 writeDlangLines(inputFileName, outputFile); 137 } 138 139 if(options.ignoreMacros) 140 copy(tmpFileName, outputFileName); 141 else 142 runCPreProcessor(options.cppPath, tmpFileName, outputFileName); 143 } 144 145 private struct TranslationText { 146 string moduleDeclaration; 147 string dlangDeclarations; 148 } 149 150 // the translated D code from all #included files 151 private TranslationText translationText(in from!"dpp.runtime.options".Options options, 152 in string inputFileName) 153 @safe 154 { 155 156 import dpp.runtime.context: Context, Language; 157 version(dpp2) 158 import dpp2.expansion: expand, isCppHeader, getHeaderName; 159 else 160 import dpp.expansion: expand, isCppHeader, getHeaderName; 161 import clang.util: getTempFileName; 162 163 import std.algorithm: map, filter, find, startsWith; 164 import std.string: fromStringz; 165 import std.path: dirName; 166 import std.array: array, join; 167 import std.stdio: File; 168 169 auto inputFile = File(inputFileName); 170 const lines = () @trusted { return inputFile.byLine.map!(a => a.idup).array; }(); 171 auto moduleLines = () @trusted { return lines.filter!isModuleLine.array; }(); 172 auto nonModuleLines = lines.filter!(a => !isModuleLine(a)); 173 const includePaths = options.includePaths ~ inputFileName.dirName; 174 auto includes = nonModuleLines 175 .map!(a => getHeaderName(a, inputFileName, includePaths)) 176 .filter!(a => a != "") 177 ; 178 const includesFileName = getTempFileName(); 179 auto language = Language.C; 180 // write a temporary file with all #included files in it 181 () @trusted { 182 auto includesFile = File(includesFileName, "w"); 183 foreach(include; includes) { 184 auto paths = includePaths.find!(p => include.startsWith(p)); 185 if (paths.length) 186 { 187 includesFile.writeln(`#include <`, include[paths[0].length+1..$], `>`); 188 } 189 else 190 { 191 includesFile.writeln(`#include "`, include, `"`); 192 } 193 if(isCppHeader(options, include)) language = Language.Cpp; 194 } 195 }(); 196 197 /** 198 We remember the cursors already seen so as to not try and define 199 something twice (legal in C, illegal in D). 200 */ 201 auto context = Context(options.dup, language); 202 203 // parse all #includes at once and populate context with 204 // D definitions 205 expand(includesFileName, context, includePaths); 206 207 context.fixNames; 208 209 return TranslationText(moduleLines.join("\n"), context.translation); 210 } 211 212 // write the original D code that doesn't need translating 213 private void writeUndefLines(in string inputFileName, ref from!"std.stdio".File outputFile) 214 @trusted 215 { 216 import std.stdio: File; 217 import std.algorithm: filter, canFind; 218 219 auto lines = File(inputFileName).byLine.filter!(l => l.canFind("#undef")); 220 foreach(line; lines) { 221 outputFile.writeln(line); 222 } 223 } 224 225 226 // write the original D code that doesn't need translating 227 private void writeDlangLines(in string inputFileName, ref from!"std.stdio".File outputFile) 228 @trusted 229 { 230 231 import dpp.expansion: getHeaderName; 232 import std.stdio: File; 233 import std.algorithm: filter; 234 235 foreach(line; File(inputFileName).byLine.filter!(a => !isModuleLine(a))) { 236 if(getHeaderName(line) == "") { 237 // not an #include directive, just pass through 238 outputFile.writeln(line); 239 } // otherwise do nothing 240 } 241 } 242 243 private bool isModuleLine(in const(char)[] line) @safe pure { 244 import std.string: stripLeft; 245 import std.algorithm: startsWith; 246 return line.stripLeft.startsWith("module "); 247 } 248 249 private void runCPreProcessor(in string cppPath, in string tmpFileName, in string outputFileName) @safe { 250 251 import std.exception: enforce; 252 import std.process: execute, executeShell, Config; 253 import std.conv: text; 254 import std.string: join, splitLines; 255 import std.stdio: File; 256 import std.algorithm: filter; 257 import std.regex: matchFirst; 258 259 const cpp = cppPath == "" 260 ? (executeShell("clang-cpp --version").status == 0 ? "clang-cpp" : "cpp") 261 : cppPath; 262 263 const cppArgs = [cpp, "-w", "--comments", tmpFileName]; 264 const ret = () { 265 try { 266 string[string] env; 267 return execute(cppArgs, env, Config.stderrPassThrough); 268 } catch (Exception e) 269 { 270 import std.typecons : Tuple; 271 return Tuple!(int, "status", string, "output")(-1, e.msg); 272 } 273 }(); 274 enforce(ret.status == 0, text("Could not run `", cppArgs.join(" "), "`:\n", ret.output)); 275 276 { 277 auto outputFile = File(outputFileName, "w"); 278 auto lines = ret. 279 output 280 .splitLines 281 .filter!(a => !a.matchFirst(`^\s*#`)) 282 ; 283 284 foreach(line; lines) { 285 outputFile.writeln(line); 286 } 287 } 288 } 289 290 291 string preamble(bool ignoreMacros) @safe pure { 292 import std.array: replace, join; 293 import std.algorithm: map, filter; 294 import std.string: splitLines; 295 296 const vaArgs = ignoreMacros 297 ? "" 298 : " #define __gnuc_va_list va_list\n\n" ~ 299 " #define __is_empty(_Type) dpp.isEmpty!(_Type)\n"; 300 301 return q{ 302 303 import core.stdc.config; 304 import core.stdc.stdarg: va_list; 305 static import core.simd; 306 static import std.conv; 307 308 struct Int128 { long lower; long upper; } 309 struct UInt128 { ulong lower; ulong upper; } 310 311 struct __locale_data { int dummy; } // FIXME 312 } ~ 313 vaArgs ~ 314 315 q{ 316 alias _Bool = bool; 317 318 struct dpp { 319 320 static struct Opaque(int N) { 321 void[N] bytes; 322 } 323 324 // Replacement for the gcc/clang intrinsic 325 static bool isEmpty(T)() { 326 return T.tupleof.length == 0; 327 } 328 329 static struct Move(T) { 330 T* ptr; 331 } 332 333 // dmd bug causes a crash if T is passed by value. 334 // Works fine with ldc. 335 static auto move(T)(ref T value) { 336 return Move!T(&value); 337 } 338 339 mixin template EnumD(string name, T, string prefix) if(is(T == enum)) { 340 341 private static string _memberMixinStr(string member) { 342 import std.conv: text; 343 import std.array: replace; 344 return text(` `, member.replace(prefix, ""), ` = `, T.stringof, `.`, member, `,`); 345 } 346 347 private static string _enumMixinStr() { 348 import std.array: join; 349 350 string[] ret; 351 352 ret ~= "enum " ~ name ~ "{"; 353 354 static foreach(member; __traits(allMembers, T)) { 355 ret ~= _memberMixinStr(member); 356 } 357 358 ret ~= "}"; 359 360 return ret.join("\n"); 361 } 362 363 mixin(_enumMixinStr()); 364 } 365 } 366 } 367 .splitLines 368 .filter!(a => a != "") 369 .map!(a => a.length >= 8 ? a[8 .. $] : a) // get rid of leading spaces 370 .join("\n"); 371 }