ldc/driver/ldmd.cpp
Martin d7f68dbeb3 Merge branch 'master' into merge-2.075
Conflicts:
	runtime/druntime
2017-10-01 18:28:44 +02:00

710 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===-- ldmd.cpp - Drop-in DMD replacement wrapper for LDC ----------------===//
//
// LDC the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license, except for the
// command line handling code, which originated from DMD. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
//
// Wrapper allowing use of LDC as drop-in replacement for DMD.
//
// Most command-line options are passed through to LDC; some with different
// names or semantics need to be translated.
//
// DMD also reads switches from the DFLAGS enviroment variable, if present. This
// is contrary to what C compilers do, where CFLAGS is usually handled by the
// build system.
//
//===----------------------------------------------------------------------===//
#ifndef LDC_EXE_NAME
#error "Please define LDC_EXE_NAME to the name of the LDC executable to use."
#endif
#include "driver/exe_path.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/SystemUtils.h"
#include "llvm/Support/raw_ostream.h"
#if _WIN32
#include "Windows.h"
#else
#include <sys/stat.h>
#endif
#include <cassert>
#include <cerrno>
#include <climits>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <numeric>
#include <vector>
#ifdef HAVE_SC_ARG_MAX
#include <unistd.h>
#endif
namespace ls = llvm::sys;
// We reuse DMD's response file parsing routine for maximum compatibility - it
// handles quotes in a very peculiar way.
int response_expand(size_t *pargc, char ***pargv);
// in ddmd/root/man.d
void browse(const char *url);
/**
* Prints a formatted error message to stderr and exits the program.
*/
void error(const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
fprintf(stderr, "Error: ");
vfprintf(stderr, fmt, argp);
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
va_end(argp);
}
/**
* Prints a formatted warning message to stderr.
*/
void warning(const char *fmt, ...) {
va_list argp;
va_start(argp, fmt);
fprintf(stderr, "Warning: ");
vfprintf(stderr, fmt, argp);
fprintf(stderr, "\n");
va_end(argp);
}
char *concat(const char *a, const char *b) {
size_t na = strlen(a);
size_t nb = strlen(b);
char *result = static_cast<char *>(malloc(na + nb + 1));
assert(result);
memcpy(result, a, na);
memcpy(result + na, b, nb + 1);
return result;
}
char *concat(const char *a, int b) {
char bStr[14];
#if defined(_MSC_VER)
_snprintf_s(bStr, _countof(bStr), sizeof(bStr), "%d", b);
#else
snprintf(bStr, sizeof(bStr), "%d", b);
#endif
return concat(a, bStr);
}
/**
* Runs the given executable, returning its error code.
*/
int execute(const std::string &exePath, const char **args) {
std::string errorMsg;
int rc = ls::ExecuteAndWait(exePath, args, nullptr,
#if LDC_LLVM_VER >= 600
{},
#else
nullptr,
#endif
0, 0, &errorMsg);
if (!errorMsg.empty()) {
error("Error executing %s: %s", exePath.c_str(), errorMsg.c_str());
}
return rc;
}
/**
* Prints usage information to stdout.
*/
void printUsage(const char *argv0, const std::string &ldcPath) {
// Print version information by actually invoking ldc -version.
const char *args[] = {ldcPath.c_str(), "-version", nullptr};
execute(ldcPath, args);
printf(
"\n\
Usage:\n\
%s [<option>...] <file>...\n\
%s [<option>...] -run <file> [<arg>...]\n\
\n\
Where:\n\
<file> D source file\n\
<arg> Argument to pass when running the resulting program\n\
\n\
<option>:\n\
@<cmdfile> read arguments from cmdfile\n\
-allinst generate code for all template instantiations\n\
-betterC omit generating some runtime information and helper functions\n\
-boundscheck=[on|safeonly|off] bounds checks on, in @safe only, or off\n\
-c do not link\n\
-color turn colored console output on\n\
-color=[on|off] force colored console output on or off\n\
-conf=<filename> use config file at filename\n\
-cov do code coverage analysis\n\
-cov=<nnn> require at least nnn%% code coverage\n\
-D generate documentation\n\
-Dd<directory> write documentation file to directory\n\
-Df<filename> write documentation file to filename\n\
-d silently allow deprecated features\n\
-dw show use of deprecated features as warnings (default)\n\
-de show use of deprecated features as errors (halt compilation)\n\
-debug compile in debug code\n\
-debug=<level> compile in debug code <= level\n\
-debug=<ident> compile in debug code identified by ident\n\
-debuglib=<name> set symbolic debug library to name\n\
-defaultlib=<name>\n\
set default library to name\n\
-deps print module dependencies (imports/file/version/debug/lib)\n\
-deps=<filename> write module dependencies to filename (only imports)\n\
-fPIC generate position independent code\n\
-dip25 implement http://wiki.dlang.org/DIP25 (experimental)\n\
-dip1000 implement http://wiki.dlang.org/DIP1000 (experimental)\n\
-g add symbolic debug info\n\
-gs always emit stack frame\n"
#if 0
" -gx add stack stomp code\n"
#endif
" -H generate 'header' file\n\
-Hd=<directory> write 'header' file to directory\n\
-Hf=<filename> write 'header' file to filename\n\
--help print help and exit\n\
-I=<directory> look for imports also in directory\n\
-ignore ignore unsupported pragmas\n\
-inline do function inlining\n\
-J=<directory> look for string imports also in directory\n\
-L=<linkerflag> pass linkerflag to link\n\
-lib generate library rather than object files\n\
-m32 generate 32 bit code\n"
#if 0
" -m32mscoff generate 32 bit code and write MS-COFF object files\n"
#endif
" -m64 generate 64 bit code\n\
-main add default main() (e.g. for unittesting)\n\
-man open web browser on manual page\n"
#if 0
" -map generate linker .map file\n"
#endif
" -mcpu=<id> generate instructions for architecture identified by 'id'\n\
-mcpu=? list all architecture options\n\
-mscrtlib=<name> MS C runtime library to reference from main/WinMain/DllMain\n\
-mv=<package.module>=<filespec> use <filespec> as source file for <package.module>\n\
-noboundscheck no array bounds checking (deprecated, use -boundscheck=off)\n\
-O optimize\n\
-o- do not write object file\n\
-od=<directory> write object & library files to directory\n\
-of=<filename> name output file to filename\n\
-op preserve source path for output files\n"
#if 0
" -profile profile runtime performance of generated code\n\
-profile=gc profile runtime allocations\n"
#endif
" -release compile release version\n\
-shared generate shared library (DLL)\n\
-transition=<id> help with language change identified by 'id'\n\
-transition=? list all language changes\n\
-unittest compile in unit tests\n\
-v verbose\n\
-vcolumns print character (column) numbers in diagnostics\n\
-vdmd print the command used to invoke the underlying compiler\n\
-verrors=<num> limit the number of error messages (0 means unlimited)\n\
-verrors=spec show errors from speculative compiles such as __traits(compiles,...)\n\
-vgc list all gc allocations including hidden ones\n\
-vtls list all variables going into thread local storage\n\
--version print compiler version and exit\n\
-version=<level> compile in version code >= level\n\
-version=<ident> compile in version code identified by ident\n\
-w warnings as errors (compilation will halt)\n\
-wi warnings as messages (compilation will continue)\n\
-X generate JSON file\n\
-Xf=<filename> write JSON file to filename\n\n",
argv0, argv0);
}
/**
* Parses an enviroment variable for flags and appends them to given list of
* arguments.
*
* This corresponds to getenv_setargv() in DMD, but we need to duplicate it
* here since it is defined in mars.c.
*/
void appendEnvVar(const char *envVarName, std::vector<char *> &args) {
char *env = getenv(envVarName);
if (!env) {
return;
}
env = strdup(env); // create our own writable copy
size_t j = 1; // leave argv[0] alone
while (1) {
switch (*env) {
case ' ':
case '\t':
env++;
break;
case 0:
return;
default:
args.push_back(env); // append
j++;
char *p = env;
int slash = 0;
int instring = 0;
char c = 0;
while (1) {
c = *env++;
switch (c) {
case '"':
p -= (slash >> 1);
if (slash & 1) {
p--;
goto Laddc;
}
instring ^= 1;
slash = 0;
continue;
case ' ':
case '\t':
if (instring) {
goto Laddc;
}
*p = 0;
break;
case '\\':
slash++;
*p++ = c;
continue;
case 0:
*p = 0;
return;
default:
Laddc:
slash = 0;
*p++ = c;
continue;
}
break;
}
}
}
}
/**
* Translates the LDMD command-line args (incl. DFLAGS environment variable)
* to LDC args.
* `ldcArgs` needs to be initialized with the path to the LDC executable.
*/
void translateArgs(size_t originalArgc, char **originalArgv,
std::vector<const char *> &ldcArgs) {
// Expand any response files present into the list of arguments.
size_t argc = originalArgc;
char **argv = originalArgv;
if (response_expand(&argc, &argv)) {
error("Could not read response file.");
}
std::vector<char *> args(argv, argv + argc);
std::vector<char *> dflags;
appendEnvVar("DFLAGS", dflags);
if (!dflags.empty()) {
// append, but before a first potential '-run'
size_t runIndex = 0;
for (size_t i = 1; i < args.size(); ++i) {
if (strcmp(args[i], "-run") == 0) {
runIndex = i;
break;
}
}
args.insert(runIndex == 0 ? args.end() : args.begin() + runIndex,
dflags.begin(), dflags.end());
}
assert(ldcArgs.size() == 1);
const std::string ldcPath = ldcArgs[0];
ldcArgs.push_back("-ldmd");
bool vdmd = false;
bool noFiles = true;
for (size_t i = 1; i < args.size(); i++) {
char *p = args[i];
if (*p == '-') {
if (strcmp(p + 1, "vdmd") == 0) {
vdmd = true;
}
/* Most args are handled directly by LDC.
* Order corresponds to parsing order in dmd's mars.d.
*
* -allinst
* -de
* -d
* -dw
* -c
*/
else if (strncmp(p + 1, "color", 5) == 0) {
bool color = true;
// Parse:
// -color
// -color=on|off
if (p[6] == '=') {
if (strcmp(p + 7, "off") == 0) {
color = false;
} else if (strcmp(p + 7, "on") != 0) {
goto Lerror;
}
} else if (p[6]) {
goto Lerror;
}
ldcArgs.push_back(color ? "-enable-color" : "-disable-color");
}
/* -conf
* -cov
* -shared
*/
else if (strcmp(p + 1, "dylib") == 0) {
ldcArgs.push_back("-shared");
} else if (strcmp(p + 1, "fPIC") == 0) {
ldcArgs.push_back("-relocation-model=pic");
} else if (strcmp(p + 1, "map") == 0) {
goto Lnot_in_ldc;
} else if (strcmp(p + 1, "multiobj") == 0) {
goto Lnot_in_ldc;
}
/* -g
* -gc
*/
else if (strcmp(p + 1, "gs") == 0) {
ldcArgs.push_back("-disable-fp-elim");
} else if (strcmp(p + 1, "gx") == 0) {
goto Lnot_in_ldc;
} else if (strcmp(p + 1, "gt") == 0) {
error("use -profile instead of -gt\n");
}
/* -m32
* -m64
*/
else if (strcmp(p + 1, "m32mscoff") == 0) {
ldcArgs.push_back("-m32");
}
/* -mscrtlib
*/
else if (strcmp(p + 1, "profile") == 0) {
goto Lnot_in_ldc;
}
/* -v
*/
else if (strcmp(p + 1, "vtls") == 0) {
ldcArgs.push_back("-transition=tls");
}
/* -vcolumns
* -vgc
*/
else if (memcmp(p + 1, "verrors", 7) == 0) {
if (p[8] == '=' && isdigit(static_cast<unsigned char>(p[9]))) {
ldcArgs.push_back(p);
} else if (memcmp(p + 9, "spec", 4) == 0) {
ldcArgs.push_back("-verrors-spec");
} else {
goto Lerror;
}
} else if (strcmp(p + 1, "mcpu=?") == 0) {
const char *mcpuargs[] = {ldcPath.c_str(), "-mcpu=help", nullptr};
execute(ldcPath, mcpuargs);
exit(EXIT_SUCCESS);
} else if (memcmp(p + 1, "mcpu=", 5) == 0) {
if (strcmp(p + 6, "baseline") == 0) {
// ignore
} else if (strcmp(p + 6, "avx") == 0) {
ldcArgs.push_back("-mattr=+avx");
} else if (strcmp(p + 6, "native") == 0) {
ldcArgs.push_back(p);
} else {
goto Lerror;
}
} else if (strcmp(p + 1, "transition=?") == 0) {
const char *transitionargs[] = {ldcPath.c_str(), p, nullptr};
execute(ldcPath, transitionargs);
exit(EXIT_SUCCESS);
}
/* -transition=<id>
* -w
* -wi
* -O
* -o-
* -od
* -of
* -op
*/
else if (strcmp(p + 1, "o") == 0) {
error("-o no longer supported, use -of or -od");
}
/* -D
* -Dd
* -Df
* -H
* -Hd
* -Hf
* -X
* -Xf
* -ignore
* -property
*/
else if (strcmp(p + 1, "inline") == 0) {
ldcArgs.push_back("-enable-inlining");
ldcArgs.push_back("-Hkeep-all-bodies");
}
/* -dip25
* -dip1000
*/
else if (strcmp(p + 1, "lib") == 0) {
ldcArgs.push_back(p);
// DMD seems to emit objects directly into the static lib being
// generated. No object files are created and therefore they never
// collide due to duplicate .d filenames (in different dirs).
// Approximate that behavior by naming the object files uniquely via -oq
// and instructing LDC to remove the object files on success.
ldcArgs.push_back("-oq");
ldcArgs.push_back("-cleanup-obj");
} else if (strcmp(p + 1, "nofloat") == 0) {
goto Lnot_in_ldc;
} else if (strcmp(p + 1, "quiet") == 0) {
// ignore
}
/* -release
* -betterC
*/
else if (strcmp(p + 1, "noboundscheck") == 0) {
ldcArgs.push_back("-boundscheck=off");
}
/* -boundscheck
* -unittest
* -I
* -J
*/
else if (memcmp(p + 1, "debug", 5) == 0 && p[6] != 'l') {
// Parse:
// -debug
// -debug=number
// -debug=identifier
if (p[6] == '=') {
if (isdigit(static_cast<unsigned char>(p[7]))) {
long level;
errno = 0;
level = strtol(p + 7, &p, 10);
if (*p || errno || level > INT_MAX) {
goto Lerror;
}
ldcArgs.push_back(concat("-d-debug=", static_cast<int>(level)));
} else {
ldcArgs.push_back(concat("-d-debug=", p + 7));
}
} else if (p[6]) {
goto Lerror;
} else {
ldcArgs.push_back("-d-debug");
}
} else if (memcmp(p + 1, "version", 7) == 0) {
// Parse:
// -version=number
// -version=identifier
if (p[8] == '=') {
if (isdigit(static_cast<unsigned char>(p[9]))) {
long level;
errno = 0;
level = strtol(p + 9, &p, 10);
if (*p || errno || level > INT_MAX) {
goto Lerror;
}
ldcArgs.push_back(concat("-d-version=", static_cast<int>(level)));
} else {
ldcArgs.push_back(concat("-d-version=", p + 9));
}
} else {
goto Lerror;
}
} else if (strcmp(p + 1, "-b") == 0 || strcmp(p + 1, "-c") == 0 ||
strcmp(p + 1, "-f") == 0 || strcmp(p + 1, "-r") == 0 ||
strcmp(p + 1, "-x") == 0 || strcmp(p + 1, "-y") == 0) {
ldcArgs.push_back(concat("-hidden-debug-", p + 2));
} else if (strcmp(p + 1, "-help") == 0 || strcmp(p + 1, "h") == 0) {
printUsage(originalArgv[0], ldcPath);
exit(EXIT_SUCCESS);
} else if (strcmp(p + 1, "-version") == 0) {
// Print version information by actually invoking ldc -version.
const char *versionargs[] = {ldcPath.c_str(), "-version", nullptr};
execute(ldcPath, versionargs);
exit(EXIT_SUCCESS);
}
/* -L
* -defaultlib
* -debuglib
* -deps
* -main
*/
else if (memcmp(p + 1, "man", 3) == 0) {
browse("http://wiki.dlang.org/LDC");
exit(EXIT_SUCCESS);
} else if (strcmp(p + 1, "run") == 0) {
ldcArgs.insert(ldcArgs.end(), args.begin() + i, args.end());
noFiles = (i == args.size() - 1);
break;
} else if (p[1] == 'C') {
ldcArgs.push_back(concat("-", p + 2));
} else {
Lerror:
ldcArgs.push_back(p);
continue;
Lnot_in_ldc:
warning("command-line option '%s' not yet supported by LDC.", p);
continue;
}
} else {
const auto ext = ls::path::extension(p);
if (ext.equals_lower(".exe")) {
// should be for Windows targets only
ldcArgs.push_back(concat("-of=", p));
continue;
}
#ifdef _WIN32
else if (strcmp(p, "/?") == 0) {
printUsage(originalArgv[0], ldcPath);
exit(EXIT_SUCCESS);
}
#endif
ldcArgs.push_back(p);
noFiles = false;
}
}
if (noFiles) {
printUsage(originalArgv[0], ldcPath);
if (originalArgc == 1)
exit(EXIT_FAILURE); // compatible with DMD
else
error("No source file specified.");
}
if (vdmd) {
printf(" -- Invoking:");
for (const auto &arg : ldcArgs) {
printf(" %s", arg);
}
puts("");
}
}
/**
* Returns the OS-dependent length limit for the command line when invoking
* subprocesses.
*/
size_t maxCommandLineLen() {
#if defined(HAVE_SC_ARG_MAX)
// http://www.in-ulm.de/~mascheck/various/argmax the factor 2 is just
// a wild guess to account for the enviroment.
return sysconf(_SC_ARG_MAX) / 2;
#elif defined(_WIN32)
// http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx
return 32767;
#else
#error "Do not know how to determine maximum command line length."
#endif
}
/**
* Tries to locate an executable with the given name, or an invalid path if
* nothing was found. Search paths: 1. Directory where this binary resides.
* 2. System PATH.
*/
std::string locateBinary(std::string exeName) {
std::string path = exe_path::prependBinDir(exeName.c_str());
if (ls::fs::can_execute(path)) {
return path;
}
llvm::ErrorOr<std::string> res = ls::findProgramByName(exeName);
path = res ? res.get() : std::string();
if (ls::fs::can_execute(path)) {
return path;
}
return "";
}
// In driver/main.d
int main(int argc, char **argv);
int cppmain(int argc, char **argv) {
exe_path::initialize(argv[0]);
std::string ldcExeName = LDC_EXE_NAME;
#ifdef _WIN32
ldcExeName += ".exe";
#endif
const std::string ldcPath = locateBinary(ldcExeName);
if (ldcPath.empty()) {
error("Could not locate " LDC_EXE_NAME " executable.");
}
// We need to manually set up argv[0] and the terminating NULL.
std::vector<const char *> args;
args.push_back(ldcPath.c_str());
translateArgs(argc, argv, args);
args.push_back(nullptr);
// Check if we can get away without a response file.
const size_t totalLen = std::accumulate(
args.begin(), args.end() - 1,
args.size() * 3, // quotes + space
[](size_t acc, const char *arg) { return acc + strlen(arg); });
if (totalLen <= maxCommandLineLen()) {
return execute(ldcPath, args.data());
}
int rspFd;
llvm::SmallString<128> rspPath;
if (ls::fs::createUniqueFile("ldmd-%%-%%-%%-%%.rsp", rspFd, rspPath)) {
error("Could not open temporary response file.");
}
{
llvm::raw_fd_ostream rspOut(rspFd, /*shouldClose=*/true);
// skip argv[0] and terminating NULL
for (auto it = args.begin() + 1, end = args.end() - 1; it != end; ++it) {
rspOut << *it << '\n';
}
}
std::string rspArg = "@";
rspArg += rspPath.str();
args.resize(1);
args.push_back(rspArg.c_str());
args.push_back(nullptr);
int rc = execute(ldcPath, args.data());
if (ls::fs::remove(rspPath.str())) {
warning("Could not remove response file.");
}
return rc;
}