diff --git a/source/dpp/runtime/app.d b/source/dpp/runtime/app.d index 787f40f0..80733948 100644 --- a/source/dpp/runtime/app.d +++ b/source/dpp/runtime/app.d @@ -11,9 +11,10 @@ import dpp.from; void run(in from!"dpp.runtime.options".Options options) @safe { import std.stdio: File; import std.exception: enforce; - import std.process: execute; + import std.process: spawnProcess, wait; import std.array: join; import std.file: remove; + import std.conv : text; foreach(dppFileName; options.dppFileNames) preprocess!File(options, dppFileName, options.toDFileName(dppFileName)); @@ -21,12 +22,12 @@ void run(in from!"dpp.runtime.options".Options options) @safe { if(options.preprocessOnly) return; const args = options.dlangCompiler ~ options.dlangCompilerArgs; - const res = execute(args); - enforce(res.status == 0, "Could not execute `" ~ args.join(" ") ~ "`:\n" ~ res.output); + const status = spawnProcess(args).wait(); if(!options.keepDlangFiles) { foreach(fileName; options.dFileNames) remove(fileName); } + enforce(status == 0, "Executing `" ~ args.join(" ") ~ "` failed with exit code\n" ~ status.text); } @@ -108,7 +109,7 @@ private string preamble() @safe pure { import core.stdc.config; import core.stdc.stdarg: va_list; struct __locale_data { int dummy; } // FIXME - #define __gnuc_va_list va_list + } ~ "#define __gnuc_va_list va_list\n" ~ q{ alias _Bool = bool; struct dpp { diff --git a/source/dpp/runtime/options.d b/source/dpp/runtime/options.d index 097f10da..79f93438 100644 --- a/source/dpp/runtime/options.d +++ b/source/dpp/runtime/options.d @@ -35,6 +35,7 @@ struct Options { import std.algorithm: map, filter, canFind, startsWith; import std.array: array; import std.conv: text; + import dpp.runtime.response : response_expand; parseArgs(args); if(earlyExit) return; @@ -46,23 +47,15 @@ struct Options { else enforce(args.length >= 2, "Not enough arguments\n" ~ usage); + args = response_expand(args); + dppFileNames = args.filter!(a => a.extension == ".dpp").array; - enforce(dppFileNames.length != 0, "No .dpp input file specified\n" ~ usage); // Remove the name of this binary and the name of the .dpp input file from args // so that a D compiler can use the remaining entries. - dlangCompilerArgs = - args[1..$].filter!(a => a.extension != ".dpp").array ~ - dFileNames; - - // if no -of option is given, default to the name of the .dpp file - if(!dlangCompilerArgs.canFind!(a => a.startsWith("-of")) && !dlangCompilerArgs.canFind("-c")) - dlangCompilerArgs ~= "-of" ~ - args. - filter!(a => a.extension == ".dpp" || a.extension == ".d") - .front - .stripExtension - ~ exeExtension; + dlangCompilerArgs = args[1..$] + .map!(a => a.extension == ".dpp" ? toDFileName(a) : a) + .array; includePaths = systemPaths ~ includePaths; } diff --git a/source/dpp/runtime/response.d b/source/dpp/runtime/response.d new file mode 100644 index 00000000..06485aa1 --- /dev/null +++ b/source/dpp/runtime/response.d @@ -0,0 +1,206 @@ +module dpp.runtime.response; +import core.stdc.stdio; +import core.stdc.stdlib; +import core.stdc.string; + +/// wrapper to make response_expand usable +/// with strings. Yes, it allocates a lot. +string[] response_expand(string[] args) @trusted +{ + import std.algorithm : map; + import std.array : array; + import std.string : fromStringz; + import std.exception : enforce; + + auto cargs = args + .map!toConstStringz + .array; + enforce(!response_expand(cargs), "expanding args failed"); + return cargs + .map!(s => s.fromStringz.idup) + .array; +} + +const(char)* toConstStringz(string s) @safe +{ + auto r = new char[](s.length + 1); + size_t i = 0; + while (i < s.length) + { + r[i] = s[i]; + if (r[i] == '\0') + break; + ++i; + } + if (i == s.length) + r[i] = '\0'; + return &r[0]; +} + +// ported from dmd's function of the same name. +// only modifications are to use builtin arrays +// and phobos in place of dmd's bespoke types +bool response_expand(ref const(char)*[] args) +{ + import std.algorithm : remove; + import std.file : readText; + import std.array : insertInPlace; + + const(char)* cp; + int recurse = 0; + for (size_t i = 0; i < args.length;) + { + cp = args[i]; + if (*cp != '@') + { + ++i; + continue; + } + args = args.remove(i); + char* buffer; + char* bufend; + cp++; + if (auto p = getenv(cp)) + { + buffer = strdup(p); + if (!buffer) + goto noexpand; + bufend = buffer + strlen(buffer); + } + else + { + auto s = cp[0 .. strlen(cp)].readText!(char[]); + buffer = s.ptr; + bufend = buffer + s.length; + } + // The logic of this should match that in setargv() + int comment = 0; + for (auto p = buffer; p < bufend; p++) + { + char* d; + char c, lastc; + ubyte instring; + int num_slashes, non_slashes; + switch (*p) + { + case 26: + /* ^Z marks end of file */ + goto L2; + case 0xD: + case '\n': + if (comment) + { + comment = 0; + } + goto case; + case 0: + case ' ': + case '\t': + continue; + // scan to start of argument + case '#': + comment = 1; + continue; + case '@': + if (comment) + { + continue; + } + recurse = 1; + goto default; + default: + /* start of new argument */ + if (comment) + { + continue; + } + args.insertInPlace(i, p); + ++i; + instring = 0; + c = 0; + num_slashes = 0; + for (d = p; 1; p++) + { + lastc = c; + if (p >= bufend) + { + *d = 0; + goto L2; + } + c = *p; + switch (c) + { + case '"': + /* + Yes this looks strange,but this is so that we are + MS Compatible, tests have shown that: + \\\\"foo bar" gets passed as \\foo bar + \\\\foo gets passed as \\\\foo + \\\"foo gets passed as \"foo + and \"foo gets passed as "foo in VC! + */ + non_slashes = num_slashes % 2; + num_slashes = num_slashes / 2; + for (; num_slashes > 0; num_slashes--) + { + d--; + *d = '\0'; + } + if (non_slashes) + { + *(d - 1) = c; + } + else + { + instring ^= 1; + } + break; + case 26: + *d = 0; // terminate argument + goto L2; + case 0xD: + // CR + c = lastc; + continue; + // ignore + case '@': + recurse = 1; + goto Ladd; + case ' ': + case '\t': + if (!instring) + { + case '\n': + case 0: + *d = 0; // terminate argument + goto Lnextarg; + } + goto default; + default: + Ladd: + if (c == '\\') + num_slashes++; + else + num_slashes = 0; + *d++ = c; + break; + } + } + break; + } + Lnextarg: + } + L2: + } + if (recurse) + { + /* Recursively expand @filename */ + if (response_expand(args)) + goto noexpand; + } + return false; /* success */ +noexpand: + /* error */ + /* BUG: any file buffers are not free'd */ + return true; +} diff --git a/tests/it/issues.d b/tests/it/issues.d index e9415d62..e4a710be 100644 --- a/tests/it/issues.d +++ b/tests/it/issues.d @@ -165,22 +165,6 @@ import it; ); } -@Tags("issue") -@("14") -@safe unittest { - import dpp.runtime.options: Options; - with(immutable IncludeSandbox()) { - - writeFile("foo.h", - q{ - typedef int foo; - }); - - runPreprocessOnly("foo.h").shouldThrowWithMessage( - "No .dpp input file specified\n" ~ Options.usage); - } -} - @Tags("issue", "preprocessor") @("22.0") @safe unittest { diff --git a/tests/test_main.d b/tests/test_main.d index 12c038fc..dee81996 100644 --- a/tests/test_main.d +++ b/tests/test_main.d @@ -10,6 +10,7 @@ int main(string[] args) { // unit tests "ut.type", + "ut.response", "it.issues", diff --git a/tests/ut/response.d b/tests/ut/response.d new file mode 100644 index 00000000..947c3f42 --- /dev/null +++ b/tests/ut/response.d @@ -0,0 +1,20 @@ +module ut.response; + +import dpp.test; +import dpp.runtime.response; + +@("toConstStringz") +@safe unittest +{ + import std.algorithm : until; + foreach (s; [null, "", "a", "áåâà", "fd\0fds"]) + { + // make sure we're not being saved by string + // literals being null-terminated + auto cstr = (s ~ 'a')[0 .. $ - 1] + .toConstStringz; + static assert(is(typeof(cstr) == const(char)*)); + (() @trusted => cstr[0 .. s.length + 1])().until('\0') + .shouldEqual(s.until('\0')); + } +}