ldc/driver/ldmd.cpp
Martin cecb040ff0 Add command-line option -transition=safe
Also fix `ldmd2 -transition=?` regression and don't display an error
message when invoking LDMD without command-line args.
2017-02-12 18:47:53 +01:00

709 lines
22 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.
//
// The reason why full command line parsing is required instead of just
// rewriting the names of a few switches is an annoying impedance mismatch
// between the way how DMD handles arguments and the LLVM command line library:
// DMD allows all switches to be specified multiple times in case of
// conflicts, the last one takes precedence. There is no easy way to replicate
// this behavior with LLVM, save parsing the switches and re-emitting a cleaned
// up string.
//
// 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. Thus, conflicts like mentioned above occur quite frequently in
// practice in makefiles written for DMD, as DFLAGS is also a natural name for
// handling flags there.
//
// In order to maintain backwards compatibility with earlier versions of LDMD,
// unknown switches are passed through verbatim to LDC. Finding a better
// solution for this is tricky, as some of the LLVM arguments can be
// intentionally specified multiple times to get a certain effect (e.g. pass,
// linker options).
//
// Just as with the old LDMD script, arguments can be passed through unmodified
// to LDC by using -Csomearg.
//
// If maintaining this wrapper is deemed too messy at some point, an alternative
// would be to either extend the LLVM command line library to support the DMD
// semantics (unlikely to happen), or to abandon it altogether (except for
// passing the LLVM-defined flags to the various passes).
//
// Note: This program inherited ugly C-style string handling and memory leaks
// from DMD, but this should not be a problem due to the short-livedness of
// the process.
//
//===----------------------------------------------------------------------===//
#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 compatibilty - it
// handles quotes in a very peculiar way.
int response_expand(size_t *pargc, char ***pargv);
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, nullptr, 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\
-g add symbolic debug info\n\
-gc add symbolic debug info, optimize for non D debuggers\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
" -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];
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) {
warning("command-line option '-map' not yet supported by LDC.");
} else if (strcmp(p + 1, "multiobj") == 0) {
// TODO
}
/* -g
* -gc
*/
else if (strcmp(p + 1, "gs") == 0) {
ldcArgs.push_back("-disable-fp-elim");
} else if (strcmp(p + 1, "gx") == 0) {
warning("command-line option '-gx' not yet supported by 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");
} else if (strcmp(p + 1, "profile") == 0) {
warning("command-line option '-profile' not yet supported by 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, "transition=?") == 0) {
const char *transitionargs[] = {ldcPath.c_str(), p, nullptr};
execute(ldcPath, transitionargs);
exit(EXIT_SUCCESS);
}
/* -transition=<id>
* -w
* -wi
* -O
* -o-
*/
else if (strcmp(p + 1, "od") == 0) {
ldcArgs.push_back(p);
// DMD creates static libraries in the objects directory (unless using
// an absolute output path via `-of`).
ldcArgs.push_back("-create-static-lib-in-objdir");
}
/* -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
*/
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) {
warning("command-line option '-nofloat' not yet supported by 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);
}
} 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;
}
#if LDC_LLVM_VER >= 306
llvm::ErrorOr<std::string> res = ls::findProgramByName(exeName);
path = res ? res.get() : std::string();
#else
path = ls::FindProgramByName(exeName);
#endif
if (ls::fs::can_execute(path)) {
return path;
}
return "";
}
static size_t addStrlen(size_t acc, const char *str) {
return acc + (str ? strlen(str) : 0);
}
// 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 need to write out a response file.
size_t totalLen = std::accumulate(args.begin(), args.end(), 0, addStrlen);
if (totalLen > maxCommandLineLen()) {
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);
for (auto arg : args) {
rspOut << arg << '\n';
}
}
std::string rspArg = "@";
rspArg += rspPath.str();
std::vector<const char *> newArgs;
newArgs.push_back(argv[0]);
newArgs.push_back(rspArg.c_str());
newArgs.push_back(nullptr);
int rc = execute(ldcPath, &newArgs[0]);
if (ls::fs::remove(rspPath.str())) {
warning("Could not remove response file.");
}
return rc;
}
return execute(ldcPath, &args[0]);
}