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     bool cppStdLib;
32     bool ignoreMacros;
33     bool detailedUntranslatable;
34     string[] ignoredNamespaces;
35     string[] ignoredCursors;
36     bool ignoreSystemPaths;
37     string[] ignoredPaths;
38     string[string] prebuiltHeaders;
39     bool alwaysScopedEnums;
40     string cppStandard = "c++17";
41     string[] clangOptions;
42     bool noSystemHeaders;
43     string cppPath;
44 
45     this(string[] args) {
46 
47         import clang: systemPaths;
48         import std.exception: enforce;
49         import std.path: stripExtension, extension, buildPath, absolutePath;
50         import std.file: tempDir;
51         import std.algorithm: map, filter, canFind, startsWith;
52         import std.array: array;
53         import std.conv: text;
54 
55         parseArgs(args);
56         if(earlyExit) return;
57 
58         if(preprocessOnly)
59             keepDlangFiles = true;
60 
61         dppFileNames = args.filter!(a => a.extension == ".dpp").array;
62         enforce(dppFileNames.length != 0, "No .dpp input file specified\n" ~ usage);
63 
64         // Remove the name of this binary and the name of the .dpp input file from args
65         // so that a D compiler can use the remaining entries.
66         dlangCompilerArgs =
67             args[1..$].filter!(a => a.extension != ".dpp").array ~
68             dFileNames;
69 
70         // if no -of option is given, default to the name of the .dpp file
71         if(!dlangCompilerArgs.canFind!(a => a.startsWith("-of")) && !dlangCompilerArgs.canFind("-c"))
72             dlangCompilerArgs ~= "-of" ~
73                 args.
74                 filter!(a => a.extension == ".dpp" || a.extension == ".d")
75                 .front
76                 .stripExtension
77                 ~ exeExtension;
78 
79         version(Windows)
80             assert(!cppStdLib, "C++ std lib functionality not implemented yet for Windows");
81 
82         if(cppStdLib) {
83             dlangCompilerArgs ~= "-L-lstdc++";
84             parseAsCpp = true;
85         }
86 
87         if (!noSystemHeaders)
88             includePaths = systemPaths ~ includePaths;
89     }
90 
91     string[] dFileNames() @safe pure const {
92         import std.algorithm: map;
93         import std.array: array;
94         return dppFileNames.map!toDFileName.array;
95     }
96 
97     static string toDFileName(in string dppFileName) @safe pure nothrow {
98         import std.path: stripExtension;
99         return dppFileName.stripExtension ~ ".d";
100     }
101 
102     private void parseArgs(ref string[] args) {
103         import std.getopt: getopt, defaultGetoptPrinter, config;
104         import std.algorithm : map;
105         import std.array : split, join;
106         auto helpInfo =
107             getopt(
108                 args,
109                 config.passThrough,
110                 "print-cursors", "Print debug information", &debugOutput,
111                 "include-path", "Include paths", &includePaths,
112                 "keep-pre-cpp-files", "Do not delete the temporary pre-preprocessed file", &keepPreCppFiles,
113                 "keep-d-files", "Do not delete the temporary D file to be compiled", &keepDlangFiles,
114                 "preprocess-only", "Only transform the .dpp file into a .d file, don't compile", &preprocessOnly,
115                 "compiler", "D compiler to use", &dlangCompiler,
116                 "parse-as-cpp", "Parse header as C++", &parseAsCpp,
117                 "define", "C Preprocessor macro", &defines,
118                 "hard-fail", "Translate nothing if any part fails", &hardFail,
119                 "c++-std-lib", "Link to the C++ standard library", &cppStdLib,
120                 "ignore-macros", "Ignore preprocessor macros", &ignoreMacros,
121                 "ignore-ns", "Ignore a C++ namespace", &ignoredNamespaces,
122                 "ignore-cursor", "Ignore a C++ cursor", &ignoredCursors,
123                 "ignore-path", "Ignore a file path, note it globs so you will want to use *", &ignoredPaths,
124                 "ignore-system-paths", "Adds system paths to the ignore-paths list (you can add them back individually with --include-path)", &ignoreSystemPaths,
125                 "prebuilt-header", "Declare a #include can be safely replaced with import. You should also ignore-path to prevent retranslating the file", &prebuiltHeaders,
126                 "detailed-untranslatables", "Show details about untranslatable cursors", &detailedUntranslatable,
127                 "scoped-enums", "Don't redeclare enums to mimic C", &alwaysScopedEnums,
128                 "c++-standard", "The C++ language standard (e.g. \"c++14\")", &cppStandard,
129                 "clang-option", "Pass option to libclang", &clangOptions,
130                 "no-sys-headers", "Don't include system headers by default", &noSystemHeaders,
131                 "cpp-path", "Path to the C preprocessor executable", &cppPath,
132             );
133 
134         clangOptions = map!(e => e.split(" "))(clangOptions).join();
135 
136         if(helpInfo.helpWanted) {
137             () @trusted {
138                 defaultGetoptPrinter(usage, helpInfo.options);
139             }();
140             earlyExit = true;
141         }
142 
143         if(ignoreSystemPaths) {
144             import clang: systemPaths;
145             import std.algorithm: filter, canFind;
146             foreach(sp; systemPaths.filter!(p => !includePaths.canFind(p)))
147                 ignoredPaths ~= sp ~ "*";
148         }
149     }
150 
151     void indent() @safe pure nothrow {
152         indentation += 4;
153     }
154 
155     Options dup() @safe pure nothrow const {
156         Options ret;
157         foreach(i, ref elt; ret.tupleof) {
158             static if(__traits(compiles, this.tupleof[i].dup))
159                 elt = this.tupleof[i].dup;
160             else static if(is(typeof(this.tupleof[i]) == const(K[V]), K, V))
161             {
162                 try // surprised looping over the AA is not nothrow but meh
163                 foreach(k, v; this.tupleof[i])
164                     elt[k] = v;
165                 catch(Exception) assert(0);
166             }
167             else
168                 elt = this.tupleof[i];
169         }
170 
171         ret.includePaths = includePaths.dup;
172         ret.defines = defines.dup;
173 
174         return ret;
175     }
176 
177     void log(T...)(auto ref T args) @trusted const {
178         version(unittest) import unit_threaded.io: writeln = writelnUt;
179         else import std.stdio: writeln;
180 
181         version(unittest)
182             enum shouldLog = true;
183         else
184             const shouldLog = debugOutput;
185 
186         if(shouldLog)
187             writeln(indentationString, args);
188     }
189 
190     private auto indentationString() @safe pure nothrow const {
191         import std.array: appender;
192         auto app = appender!(char[]);
193         app.reserve(indentation);
194         foreach(i; 0 .. indentation) app ~= " ";
195         return app.data;
196     }
197 }