1 /**
2    Command-line options
3  */
4 module dpp.runtime.options;
5 
6 @safe:
7 
8 version(Windows)
9     enum exeExtension = ".exe";
10 else
11     enum exeExtension = "";
12 
13 
14 struct Options {
15 
16     enum usage = "Usage: d++ [options] [D compiler options] <filename.dpp> [D compiler args]";
17 
18     string[] dppFileNames;
19     int indentation;
20     bool debugOutput;
21     string[] includePaths;
22     bool keepPreCppFiles;
23     bool keepDlangFiles;
24     bool parseAsCpp;
25     bool preprocessOnly;
26     string dlangCompiler = "dmd";
27     string[] dlangCompilerArgs;
28     string[] defines;
29     bool earlyExit;
30     bool hardFail;
31     string mscrtlib;
32     bool cppStdLib;
33     bool ignoreMacros;
34     bool detailedUntranslatable;
35     string[] ignoredNamespaces;
36     string[] ignoredCursors;
37     bool ignoreSystemPaths;
38     string[] ignoredPaths;
39     string[string] prebuiltHeaders;
40     bool alwaysScopedEnums;
41     string cppStandard = "c++17";
42     string[] clangOptions;
43     bool noSystemHeaders;
44     string cppPath;
45     string srcOutputPath;
46     bool functionMacros;
47 
48     this(string[] args) {
49 
50         import clang: systemPaths;
51         import std.exception: enforce;
52         import std.path: stripExtension, extension, buildPath, absolutePath;
53         import std.file: tempDir;
54         import std.algorithm: map, filter, canFind, startsWith;
55         import std.array: array;
56         import std.conv: text;
57 
58         parseArgs(args);
59         if(earlyExit) return;
60 
61         if(preprocessOnly)
62             keepDlangFiles = true;
63 
64         dppFileNames = args.filter!(a => a.extension == ".dpp").array;
65         enforce(dppFileNames.length != 0, "No .dpp input file specified\n" ~ usage);
66 
67         // Remove the name of this binary and the name of the .dpp input file from args
68         // so that a D compiler can use the remaining entries.
69         dlangCompilerArgs =
70             args[1..$].filter!(a => a.extension != ".dpp").array ~
71             dFileNames;
72 
73         // use msvcrtd.lib if no other libc is specified
74         bool addNODEFAULTLIB = false;
75         version(Windows) {
76             if(mscrtlib) {
77                 if(dlangCompiler == "dmd") {
78                     dlangCompilerArgs ~= mscrtlib ~ ".lib";
79                     addNODEFAULTLIB = true;
80                 } else if (dlangCompiler == "ldc2") {
81                     dlangCompilerArgs ~= "--mscrtlib=" ~ mscrtlib;
82                 }
83             }
84         } else {
85             assert(mscrtlib == "", "Using --mscrtlib flag on a non-windows platform is not allowed.");
86         }
87 
88         // if no -of option is given, default to the name of the .dpp file
89         if(!dlangCompilerArgs.canFind!(a => a.startsWith("-of")) && !dlangCompilerArgs.canFind("-c")) {
90             dlangCompilerArgs ~= ((dlangCompiler == "ldc2") ? "--of=" : "-of=") ~
91                 args.
92                 filter!(a => a.extension == ".dpp" || a.extension == ".d")
93                 .front
94                 .stripExtension
95                 ~ exeExtension;
96         }
97 
98         version(Windows) {
99             // append the arch flag if not provided manually
100             if(!dlangCompilerArgs.canFind!(a => a == "-m64" || a == "-m32" || a == "-m32mscoff")) {
101                 version(X86_64) {
102                     dlangCompilerArgs ~= "-m64";
103                 }
104                 version(x86) {
105                     dlangCompilerArgs ~= (dlangCompiler == "dmd") ? "-m32mscoff" : "-m32";
106                 }
107             }
108             if(addNODEFAULTLIB) {
109                 dlangCompilerArgs ~= "-L=\"/NODEFAULTLIB:libcmt\"";
110             }
111             assert(!cppStdLib, "C++ std lib functionality not implemented yet for Windows");
112         }
113 
114         if(cppStdLib) {
115             dlangCompilerArgs ~= "-L-lstdc++";
116             parseAsCpp = true;
117         }
118 
119         if (!noSystemHeaders)
120             includePaths = systemPaths ~ includePaths;
121     }
122 
123     string[] dFileNames() @safe pure const {
124         import std.algorithm: map;
125         import std.array: array;
126         return dppFileNames.map!(a => toDFileName(a)).array;
127     }
128 
129     string toDFileName(in string dppFileName) @safe pure nothrow const {
130         import std.path: stripExtension, dirName, isAbsolute, buildPath, baseName;
131 
132         const outputPath = srcOutputPath == ""
133             ? dppFileName.dirName
134             : srcOutputPath;
135         const fileName = dppFileName.baseName.stripExtension ~ ".d";
136 
137         return buildPath(outputPath, fileName);
138     }
139 
140     private void parseArgs(ref string[] args) {
141         import std.getopt: getopt, defaultGetoptPrinter, config;
142         import std.algorithm : map;
143         import std.array : split, join;
144         auto helpInfo =
145             getopt(
146                 args,
147                 config.passThrough,
148                 "print-cursors", "Print debug information", &debugOutput,
149                 "include-path", "Include paths", &includePaths,
150                 "keep-pre-cpp-files", "Do not delete the temporary pre-preprocessed file", &keepPreCppFiles,
151                 "keep-d-files", "Do not delete the temporary D file to be compiled", &keepDlangFiles,
152                 "preprocess-only", "Only transform the .dpp file into a .d file, don't compile", &preprocessOnly,
153                 "compiler", "D compiler to use", &dlangCompiler,
154                 "parse-as-cpp", "Parse header as C++", &parseAsCpp,
155                 "define", "C Preprocessor macro", &defines,
156                 "hard-fail", "Translate nothing if any part fails", &hardFail,
157                 "mscrtlib",
158                     "MS C runtime library to link (e.g. use `--mscrtlib=msvcrtd`, if there are missing references)",
159                      &mscrtlib,
160                 "c++-std-lib", "Link to the C++ standard library", &cppStdLib,
161                 "ignore-macros", "Ignore preprocessor macros", &ignoreMacros,
162                 "ignore-ns", "Ignore a C++ namespace", &ignoredNamespaces,
163                 "ignore-cursor", "Ignore a C++ cursor", &ignoredCursors,
164                 "ignore-path", "Ignore a file path, note it globs so you will want to use *", &ignoredPaths,
165                 "ignore-system-paths",
166                     "Adds system paths to the ignore-paths list (you can add them back individually with --include-path)",
167                     &ignoreSystemPaths,
168                 "prebuilt-header",
169                     "Declare a #include can be safely replaced with import. You should also ignore-path to prevent retranslating the file",
170                     &prebuiltHeaders,
171                 "detailed-untranslatables",
172                     "Show details about untranslatable cursors",
173                     &detailedUntranslatable,
174                 "scoped-enums", "Don't redeclare enums to mimic C", &alwaysScopedEnums,
175                 "c++-standard", "The C++ language standard (e.g. \"c++14\")", &cppStandard,
176                 "clang-option", "Pass option to libclang", &clangOptions,
177                 "no-sys-headers", "Don't include system headers by default", &noSystemHeaders,
178                 "cpp-path", "Path to the C preprocessor executable", &cppPath,
179                 "source-output-path", "Path to emit the resulting D files to", &srcOutputPath,
180                 "function-macros", "Emit templated functions for macros", &functionMacros,
181             );
182 
183         clangOptions = map!(e => e.split(" "))(clangOptions).join();
184 
185         if(helpInfo.helpWanted) {
186             () @trusted {
187                 defaultGetoptPrinter(usage, helpInfo.options);
188             }();
189             earlyExit = true;
190         }
191 
192         if(ignoreSystemPaths) {
193             import clang: systemPaths;
194             import std.algorithm: filter, canFind;
195             foreach(sp; systemPaths.filter!(p => !includePaths.canFind(p)))
196                 ignoredPaths ~= sp ~ "*";
197         }
198     }
199 
200     void indent() @safe pure nothrow {
201         indentation += 4;
202     }
203 
204     Options dup() @safe pure nothrow const {
205         Options ret;
206         foreach(i, ref elt; ret.tupleof) {
207             static if(__traits(compiles, this.tupleof[i].dup))
208                 elt = this.tupleof[i].dup;
209             else static if(is(typeof(this.tupleof[i]) == const(K[V]), K, V))
210             {
211                 try // surprised looping over the AA is not nothrow but meh
212                 foreach(k, v; this.tupleof[i])
213                     elt[k] = v;
214                 catch(Exception) assert(0);
215             }
216             else
217                 elt = this.tupleof[i];
218         }
219 
220         ret.includePaths = includePaths.dup;
221         ret.defines = defines.dup;
222 
223         return ret;
224     }
225 
226     void log(T...)(auto ref T args) @trusted const {
227         version(unittest) import unit_threaded.io: writeln = writelnUt;
228         else import std.stdio: writeln;
229 
230         version(unittest)
231             enum shouldLog = true;
232         else
233             const shouldLog = debugOutput;
234 
235         if(shouldLog)
236             writeln(indentationString, args);
237     }
238 
239     private auto indentationString() @safe pure nothrow const {
240         import std.array: appender;
241         auto app = appender!(char[]);
242         app.reserve(indentation);
243         foreach(i; 0 .. indentation) app ~= " ";
244         return app.data;
245     }
246 }