diff --git a/CMakeLists.txt b/CMakeLists.txt index 575ca613..0ea92cf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,10 +57,10 @@ if (NOT DEFINED SYSCONF_INSTALL_DIR) endif(CMAKE_INSTALL_PREFIX STREQUAL "/usr") endif (NOT DEFINED SYSCONF_INSTALL_DIR) -set(D_VERSION 2 CACHE STRING "D language version") -set(PROGRAM_PREFIX "" CACHE STRING "prepended to ldc binary name") -set(PROGRAM_SUFFIX "" CACHE STRING "appended to ldc binary name") -set(CONF_INST_DIR ${SYSCONF_INSTALL_DIR} CACHE PATH "Set ldc.conf directory for installation") +set(D_VERSION 2 CACHE STRING "D language version") +set(PROGRAM_PREFIX "" CACHE STRING "Prepended to ldc/ldmd binary names") +set(PROGRAM_SUFFIX "" CACHE STRING "Appended to ldc/ldmd binary names") +set(CONF_INST_DIR ${SYSCONF_INSTALL_DIR} CACHE PATH "Directory ldc.conf is installed to") # The following flags are currently not well tested, expect the build to fail. option(USE_BOEHM_GC "use the Boehm garbage collector internally") @@ -84,6 +84,9 @@ else(D_VERSION EQUAL 1) message(FATAL_ERROR "unsupported D version") endif(D_VERSION EQUAL 1) +set(LDC_EXE_NAME ${PROGRAM_PREFIX}${LDC_EXE}${PROGRAM_SUFFIX}) +set(LDMD_EXE_NAME ${PROGRAM_PREFIX}${LDMD_EXE}${PROGRAM_SUFFIX}) + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR} ${PROJECT_BINARY_DIR}/${DMDFE_PATH} @@ -205,7 +208,17 @@ set_source_files_properties( file(GLOB_RECURSE FE_SRC ${DMDFE_PATH}/*.c ${DMDFE_PATH}/*.h) file(GLOB_RECURSE GEN_SRC gen/*.cpp gen/*.h) file(GLOB IR_SRC ir/*.cpp ir/*.h) -file(GLOB_RECURSE DRV_SRC driver/*.cpp driver/*.h) +set(DRV_SRC + driver/cl_options.cpp + driver/configfile.cpp + driver/linker.h + driver/toobj.cpp + driver/cl_options.h + driver/configfile.h + driver/linker.cpp + driver/main.cpp + driver/toobj.h +) # exclude idgen and impcnvgen and generated sources, just in case list(REMOVE_ITEM FE_SRC ${PROJECT_SOURCE_DIR}/${DMDFE_PATH}/idgen.c @@ -279,7 +292,6 @@ endif(BUILD_SHARED) set(LDC_LIB LDCShared) add_library(${LDC_LIB} ${LDC_LIB_TYPE} ${LDC_SOURCE_FILES}) -# Build ldc set_target_properties( ${LDC_LIB} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin @@ -303,11 +315,8 @@ if(USE_BOEHM_GC) target_link_libraries(${LDC_LIB} ${PROJECT_SOURCE_DIR}/libgc.a) endif(USE_BOEHM_GC) -# Path where ldc executable will be put + add_executable(${LDC_EXE} ${DRV_SRC}) -# Name of ldc executable -set(LDC_EXE_NAME ${PROGRAM_PREFIX}${LDC_EXE}${PROGRAM_SUFFIX}) -# Build ldc set_target_properties( ${LDC_EXE} PROPERTIES OUTPUT_NAME ${LDC_EXE_NAME} @@ -316,23 +325,34 @@ set_target_properties( ) target_link_libraries(${LDC_EXE} ${LDC_LIB} ${LIBCONFIG_LDFLAGS} config++) - +# For use by the druntime/Phobos build system. get_target_property(LDC_LOC ${LDC_EXE} LOCATION) +# +# LDMD +# +set_source_files_properties(dmd2/root/response.c dmd2/root/man.c PROPERTIES LANGUAGE CXX) +add_executable(${LDMD_EXE} dmd2/root/response.c dmd2/root/man.c driver/ldmd.cpp) +set_target_properties(${LDMD_EXE} PROPERTIES + COMPILE_DEFINITIONS LDC_EXE_NAME="${LDC_EXE_NAME}" + COMPILE_FLAGS "${LLVM_CXXFLAGS}" + OUTPUT_NAME "${LDMD_EXE_NAME}" +) +target_link_libraries(${LDMD_EXE} "${LLVM_LDFLAGS} ${LLVM_LIBRARIES}") + # # Install target. # add_subdirectory(runtime) -install(TARGETS ${LDC_EXE} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install(TARGETS ${LDC_EXE} ${LDMD_EXE} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) if(${BUILD_SHARED}) # For now, only install libldc if explicitely building the shared library. # While it might theoretically be possible to use LDC as a static library # as well, for the time being this just bloats the normal packages. install(TARGETS ${LDC_LIB} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) endif() -install(PROGRAMS ${PROJECT_SOURCE_DIR}/bin/${LDMD_EXE} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(FILES ${PROJECT_BINARY_DIR}/bin/${LDC_EXE}_install.conf DESTINATION ${CONF_INST_DIR} RENAME ${LDC_EXE}.conf) install(FILES ${PROJECT_BINARY_DIR}/bin/${LDC_EXE}_install.rebuild.conf DESTINATION ${CONF_INST_DIR} RENAME ${LDC_EXE}.rebuild.conf) diff --git a/driver/ldmd.cpp b/driver/ldmd.cpp new file mode 100644 index 00000000..929cd5d4 --- /dev/null +++ b/driver/ldmd.cpp @@ -0,0 +1,858 @@ +/** + * Wrapper allowing use of LDC as drop-in replacement for DMD. + * + * Copyright (c) 2012 by the LDC Team. + * + * Command line parsing code originally + * Copyright (c) 1999-2012 by Digital Mars. + */ +// 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. +// +// 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 +#include +#include +#include +#include +#include +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/SystemUtils.h" +#include "llvm/Support/raw_ostream.h" + +namespace ls = llvm::sys; + +// We reuse DMD's response file parsing routine for maximum compatibilty - it +// handles quotes in a very peciuliar way. +int response_expand(int *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(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]; + snprintf(bStr, sizeof(bStr), "%d", b); + return concat(a, bStr); +} + +/** + * Runs the given executable, returning its error code. + */ +int execute(ls::Path exePath, const char** args) +{ + std::string errorMsg; + int rc = ls::Program::ExecuteAndWait(exePath, args, NULL, NULL, + NULL, NULL, &errorMsg); + if (!errorMsg.empty()) + { + error("Could not execute %s: %s", exePath.c_str(), errorMsg.c_str()); + } + return rc; +} + +/** + * Prints usage information to stdout. + */ +void printUsage(const char* argv0, ls::Path ldcPath) +{ + const char* args[] = { ldcPath.c_str(), "-version", NULL }; + execute(ldcPath, args); + printf("\n\ +Usage:\n\ + %s files.d ... { -switch }\n\ +\n\ + files.d D source files\n\ + @cmdfile read arguments from cmdfile\n\ + -c do not link\n\ + -cov do code coverage analysis\n\ + -D generate documentation\n\ + -Dddocdir write documentation file to docdir directory\n\ + -Dffilename write documentation file to filename\n\ + -d allow deprecated features\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 set default library to name\n\ + -deps=filename write module dependencies to filename\n\ + -fPIC generate position independent code\n\ + -g add symbolic debug info\n\ + -gc add symbolic debug info, pretend to be C\n\ + -gs always emit stack frame\n\ + -H generate 'header' file\n\ + -Hddirectory write 'header' file to directory\n\ + -Hffilename write 'header' file to filename\n\ + --help print help\n\ + -Ipath where to look for imports\n\ + -ignore ignore unsupported pragmas\n\ + -inline do function inlining\n\ + -Jpath where to look for string imports\n\ + -Llinkerflag pass linkerflag to link\n\ + -lib generate library rather than object files\n\ + -m32 generate 32 bit code\n\ + -m64 generate 64 bit code\n\ + -man open web browser on manual page\n\ + -map generate linker .map file\n" +#if DMDV2 +" -noboundscheck turns off array bounds checking for all functions\n" +#endif +" -nofloat do not emit reference to floating point\n\ + -O optimize\n\ + -o- do not write object file\n\ + -odobjdir write object & library files to directory objdir\n\ + -offilename name output file to filename\n\ + -op do not strip paths from source file\n\ + -profile profile runtime performance of generated code\n\ + -property enforce property syntax\n\ + -quiet suppress unnecessary messages\n\ + -release compile release version\n\ + -run srcfile args... run resulting program, passing args\n\ + -shared generate shared library\n\ + -unittest compile in unit tests\n\ + -v verbose\n\ + -version=level compile in version code >= level\n\ + -version=ident compile in version code identified by ident\n" +#if DMDV2 +" -vtls list all variables going into thread local storage\n" +#endif +" -w enable warnings\n\ + -wi enable informational warnings\n\ + -X generate JSON file\n\ + -Xffilename write JSON file to filename\n\n", 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& 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; + } + } + } +} + +struct Debug +{ + enum Type + { + none, + normal, + pretendC + }; +}; + +struct Model +{ + enum Type + { + automatic, + m32, + m64 + }; +}; + +struct Warnings +{ + enum Type + { + none, + asErrors, + informational + }; +}; + +struct Params +{ + bool allowDeprecated; + bool compileOnly; + bool coverage; + bool emitSharedLib; + bool pic; + bool emitMap; + bool multiObj; + Debug::Type debugInfo; + bool alwaysStackFrame; + Model::Type targetModel; + bool profile; + bool verbose; + bool logTlsUse; + Warnings::Type warnings; + bool optimize; + bool noObj; + char* objDir; + char* objName; + bool preservePaths; + bool generateDocs; + char* docDir; + char* docName; + bool generateHeaders; + char* headerDir; + char* headerName; + bool generateJson; + char* jsonName; + bool ignoreUnsupportedPragmas; + bool enforcePropertySyntax; + bool enableInline; + bool emitStaticLib; + bool noFloat; + bool quiet; + bool release; + bool noBoundsChecks; + bool emitUnitTests; + std::vector modulePaths; + std::vector importPaths; + bool debugFlag; + unsigned debugLevel; + std::vector debugIdentifiers; + unsigned versionLevel; + std::vector versionIdentifiers; + std::vector linkerSwitches; + char* defaultLibName; + char* debugLibName; + char* moduleDepsFile; + + bool hiddenDebugB; + bool hiddenDebugC; + bool hiddenDebugF; + bool hiddenDebugR; + bool hiddenDebugX; + bool hiddenDebugY; + + bool run; + std::vector files; + std::vector runArgs; +}; + +/** + * Parses the flags from the given command line and the DFLAGS environment + * variable into a Params struct. + */ +Params parseArgs(int originalArgc, char** originalArgv, ls::Path ldcPath) +{ + // Expand any response files present into the list of arguments. + int argc = originalArgc; + char** argv = originalArgv; + if (response_expand(&argc, &argv)) + { + error("Could not read response file."); + } + + std::vector args(argv, argv + argc); + + appendEnvVar("DFLAGS", args); + + Params result = Params(); + for (size_t i = 1; i < args.size(); i++) + { + char* p = args[i]; + if (*p == '-') + { + if (strcmp(p + 1, "d") == 0) + result.allowDeprecated = true; + else if (strcmp(p + 1, "c") == 0) + result.compileOnly = true; + else if (strcmp(p + 1, "cov") == 0) + result.coverage = true; + else if (strcmp(p + 1, "shared") == 0 + // backwards compatibility with old switch + || strcmp(p + 1, "dylib") == 0 + ) + result.emitSharedLib = true; + else if (strcmp(p + 1, "fPIC") == 0) + result.pic = true; + else if (strcmp(p + 1, "map") == 0) + result.emitMap = true; + else if (strcmp(p + 1, "multiobj") == 0) + result.multiObj = true; + else if (strcmp(p + 1, "g") == 0) + result.debugInfo = Debug::normal; + else if (strcmp(p + 1, "gc") == 0) + result.debugInfo = Debug::pretendC; + else if (strcmp(p + 1, "gs") == 0) + result.alwaysStackFrame = true; + else if (strcmp(p + 1, "gt") == 0) + error("use -profile instead of -gt\n"); + else if (strcmp(p + 1, "m32") == 0) + result.targetModel = Model::m32; + else if (strcmp(p + 1, "m64") == 0) + result.targetModel = Model::m64; + else if (strcmp(p + 1, "profile") == 0) + result.profile = true; + else if (strcmp(p + 1, "v") == 0) + result.verbose = true; +#if DMDV2 + else if (strcmp(p + 1, "vtls") == 0) + result.logTlsUse = true; +#endif + else if (strcmp(p + 1, "v1") == 0) + { +#if DMDV1 + // Just ignore, for backwards compatibility. +#else + error("use DMD 1.0 series compilers for -v1 switch"); + break; +#endif + } + else if (strcmp(p + 1, "w") == 0) + result.warnings = Warnings::asErrors; + else if (strcmp(p + 1, "wi") == 0) + result.warnings = Warnings::informational; + else if (strcmp(p + 1, "O") == 0) + result.optimize = true; + else if (p[1] == 'o') + { + switch (p[2]) + { + case '-': + result.noObj = true; + break; + + case 'd': + if (!p[3]) + goto Lnoarg; + result.objDir = p + 3; + break; + + case 'f': + if (!p[3]) + goto Lnoarg; + result.objName = p + 3; + break; + + case 'p': + if (p[3]) + goto Lerror; + result.preservePaths = 1; + break; + + case 0: + error("-o no longer supported, use -of or -od"); + break; + + default: + goto Lerror; + } + } + else if (p[1] == 'D') + { + result.generateDocs = true; + switch (p[2]) + { + case 'd': + if (!p[3]) + goto Lnoarg; + result.docDir = p + 3; + break; + case 'f': + if (!p[3]) + goto Lnoarg; + result.docName = p + 3; + break; + + case 0: + break; + + default: + goto Lerror; + } + } + else if (p[1] == 'H') + { + result.generateHeaders = true; + switch (p[2]) + { + case 'd': + if (!p[3]) + goto Lnoarg; + result.headerDir = p + 3; + break; + + case 'f': + if (!p[3]) + goto Lnoarg; + result.headerName = p + 3; + break; + + case 0: + break; + + default: + goto Lerror; + } + } + else if (p[1] == 'X') + { + result.generateJson = true; + switch (p[2]) + { + case 'f': + if (!p[3]) + goto Lnoarg; + result.jsonName = p + 3; + break; + + case 0: + break; + + default: + goto Lerror; + } + } + else if (strcmp(p + 1, "ignore") == 0) + result.ignoreUnsupportedPragmas = true; + else if (strcmp(p + 1, "property") == 0) + result.enforcePropertySyntax = true; + else if (strcmp(p + 1, "inline") == 0) + result.enableInline = true; + else if (strcmp(p + 1, "lib") == 0) + result.emitStaticLib = true; + else if (strcmp(p + 1, "nofloat") == 0) + result.noFloat = 1; + else if (strcmp(p + 1, "quiet") == 0) + result.quiet = 1; + else if (strcmp(p + 1, "release") == 0) + result.release = 1; +#if DMDV2 + else if (strcmp(p + 1, "noboundscheck") == 0) + result.noBoundsChecks = 1; +#endif + else if (strcmp(p + 1, "unittest") == 0) + result.emitUnitTests = 1; + else if (p[1] == 'I') + result.modulePaths.push_back(p + 2); + else if (p[1] == 'J') + result.importPaths.push_back(p + 2); + else if (memcmp(p + 1, "debug", 5) == 0 && p[6] != 'l') + { + // Parse: + // -debug + // -debug=number + // -debug=identifier + if (p[6] == '=') + { + if (isdigit((unsigned char)p[7])) + { long level; + + errno = 0; + level = strtol(p + 7, &p, 10); + if (*p || errno || level > INT_MAX) + goto Lerror; + result.debugLevel = (int)level; + } + result.debugIdentifiers.push_back(p + 7); + } + else if (p[6]) + goto Lerror; + else + result.debugFlag = true; + } + else if (memcmp(p + 1, "version", 5) == 0) + { + // Parse: + // -version=number + // -version=identifier + if (p[8] == '=') + { + if (isdigit((unsigned char)p[9])) + { long level; + + errno = 0; + level = strtol(p + 9, &p, 10); + if (*p || errno || level > INT_MAX) + goto Lerror; + result.versionLevel = (int)level; + } + result.versionIdentifiers.push_back(p + 9); + } + else + goto Lerror; + } + else if (strcmp(p + 1, "-b") == 0) + result.hiddenDebugB = 1; + else if (strcmp(p + 1, "-c") == 0) + result.hiddenDebugC = 1; + else if (strcmp(p + 1, "-f") == 0) + result.hiddenDebugF = 1; + else if (strcmp(p + 1, "-help") == 0) + { + printUsage(originalArgv[0], ldcPath); + exit(EXIT_SUCCESS); + } + else if (strcmp(p + 1, "-r") == 0) + result.hiddenDebugR = 1; + else if (strcmp(p + 1, "-x") == 0) + result.hiddenDebugX = 1; + else if (strcmp(p + 1, "-y") == 0) + result.hiddenDebugY = 1; + else if (p[1] == 'L') + { + result.linkerSwitches.push_back(p + 2); + } + else if (memcmp(p + 1, "defaultlib=", 11) == 0) + { + result.defaultLibName = p + 1 + 11; + } + else if (memcmp(p + 1, "debuglib=", 9) == 0) + { + result.debugLibName = p + 1 + 9; + } + else if (memcmp(p + 1, "deps=", 5) == 0) + { + result.moduleDepsFile = p + 1 + 5; + if (!result.moduleDepsFile[0]) + goto Lnoarg; + } + else if (memcmp(p + 1, "man", 3) == 0) + { + browse("http://ldc-developers.github.com"); + exit(EXIT_SUCCESS); + } + else if (strcmp(p + 1, "run") == 0) + { + result.run = true; + int runargCount = ((i >= originalArgc) ? argc : originalArgc) - i - 1; + if (runargCount) + { + result.files.push_back(argv[i + 1]); + result.runArgs = std::vector(argv + i + 2, argv + i + runargCount); + } + else + { + result.run = false; + goto Lnoarg; + } + } + else + { + Lerror: + error("unrecognized switch '%s'", argv[i]); + continue; + + Lnoarg: + error("argument expected for switch '%s'", argv[i]); + continue; + } + } + else + { +// FIXME: #if TARGET_WINDOS + llvm::StringRef ext = ls::path::extension(p); + if (ext.equals_lower("exe")) + { + result.objName = p; + continue; + } +// #endif + result.files.push_back(p); + } + } + if (result.files.empty()) + { + printUsage(originalArgv[0], ldcPath); + error("No source file specified."); + } + return result; +} + +void pushSwitches(const char* prefix, const std::vector& vals, std::vector& r) +{ + typedef std::vector::const_iterator It; + for (It it = vals.begin(), end = vals.end(); it != end; ++it) + { + r.push_back(concat(prefix, *it)); + } +} + +/** + * Appends the LDC command line parameters corresponding to the given set of + * parameters to r. + */ +void buildCommandLine(std::vector& r, const Params& p) +{ + if (p.allowDeprecated) r.push_back("-d"); + if (p.compileOnly) r.push_back("-c"); + if (p.coverage) warning("Coverage report generation not yet supported by LDC."); + if (p.pic) r.push_back("-relocation-model=pic"); + if (p.emitMap) warning("Map file generation not yet supported by LDC."); + if (!p.multiObj) r.push_back("--singleobj"); + if (p.debugInfo == Debug::normal) r.push_back("-g"); + else if (p.debugInfo == Debug::pretendC) r.push_back("-gc"); + if (p.alwaysStackFrame) r.push_back("-disable-fp-elim"); + if (p.targetModel == Model::m32) r.push_back("-m32"); + else if (p.targetModel == Model::m32) r.push_back("-m64"); + if (p.profile) warning("CPU profile generation not yet supported by LDC."); + if (p.verbose) r.push_back("-v"); + if (p.logTlsUse) warning("-vtls not yet supported by LDC."); + if (p.warnings == Warnings::asErrors) r.push_back("-w"); + else if (p.warnings == Warnings::informational) r.push_back("-wi"); + if (p.optimize) r.push_back("-O2"); + if (p.noObj) r.push_back("-c-"); + if (p.objDir) r.push_back(concat("-od=", p.objDir)); + if (p.objName) r.push_back(concat("-of=", p.objName)); + if (p.preservePaths) r.push_back("-op"); + if (p.generateDocs) r.push_back("-D"); + if (p.docDir) r.push_back(concat("-Dd=", p.docDir)); + if (p.docName) r.push_back(concat("-Df=", p.docName)); + if (p.generateHeaders) r.push_back("-H"); + if (p.headerDir) r.push_back(concat("-Hd=", p.headerDir)); + if (p.headerName) r.push_back(concat("-Hf=", p.headerName)); + if (p.generateJson) r.push_back("-X"); + if (p.jsonName) r.push_back(concat("-Xf=", p.jsonName)); + if (p.ignoreUnsupportedPragmas) r.push_back("-ignore"); + if (p.enforcePropertySyntax) r.push_back("-property"); + if (p.enableInline) r.push_back("-enable-inlining"); + if (p.emitStaticLib) r.push_back("-lib"); + if (p.noFloat) warning("-nofloat is ignored by LDC."); + if (p.quiet) r.push_back("-quiet"); // Undocumented. + if (p.noBoundsChecks) r.push_back("-disable-boundscheck"); + if (p.emitUnitTests) r.push_back("-unittest"); + pushSwitches("-I=", p.modulePaths, r); + pushSwitches("-J=", p.importPaths, r); + if (p.debugFlag) r.push_back("-d-debug"); + if (p.debugLevel) r.push_back(concat("-d-debug=", p.debugLevel)); + pushSwitches("-d-debug=", p.debugIdentifiers, r); + if (p.debugLevel) r.push_back(concat("-d-version=", p.versionLevel)); + pushSwitches("-d-version=", p.versionIdentifiers, r); + pushSwitches("-L=", p.linkerSwitches, r); + if (p.defaultLibName) r.push_back(concat("-defaultlib=", p.defaultLibName)); + if (p.debugLibName) r.push_back(concat("-deps=", p.moduleDepsFile)); + if (p.hiddenDebugB) r.push_back("-hidden-debug-b"); + if (p.hiddenDebugC) r.push_back("-hidden-debug-c"); + if (p.hiddenDebugF) r.push_back("-hidden-debug-f"); + if (p.hiddenDebugR) r.push_back("-hidden-debug-r"); + if (p.hiddenDebugX) r.push_back("-hidden-debug-x"); + if (p.hiddenDebugY) r.push_back("-hidden-debug-y"); + if (p.run) r.push_back("-run"); + r.insert(r.end(), p.files.begin(), p.files.end()); + r.insert(r.end(), p.runArgs.begin(), p.runArgs.end()); +} + +/** + * Returns the OS-dependent length limit for the command line when invoking + * subprocesses. + */ +size_t maxCommandLineLen() +{ +#ifdef WINDOWS + // http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx + return 32767; +#else + // 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; +#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. + */ +ls::Path locateBinary(std::string exeName, const char* argv0) +{ + ls::Path path = llvm::PrependMainExecutablePath(exeName, + argv0, (void*)&locateBinary); + if (path.canExecute()) return path; + + path = ls::Program::FindProgramByName(exeName); + if (path.canExecute()) return path; + + return ls::Path(); +} + +static size_t addStrlen(size_t acc, const char* str) +{ + if (!str) return acc; + return acc + strlen(str); +} + +int main(int argc, char *argv[]) +{ + ls::Path ldcPath = locateBinary(LDC_EXE_NAME, argv[0]); + if (!ldcPath.isValid()) + { + error("Could not locate "LDC_EXE_NAME" executable."); + } + + // We need to manually set up argv[0] and the terminating NULL. + std::vector args; + args.push_back(ldcPath.c_str()); + buildCommandLine(args, parseArgs(argc, argv, ldcPath)); + args.push_back(NULL); + + // 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::unique_file("ldmd-%%-%%-%%-%%.rsp", rspFd, rspPath) != + llvm::errc::success) + { + error("Could not open temporary response file."); + } + + { + llvm::raw_fd_ostream rspOut(rspFd, /*shouldClose=*/true); + typedef std::vector::const_iterator It; + for (It it = args.begin(), end = args.end(); it != end; ++it) + { + rspOut << *it << '\n'; + } + } + + std::string rspArg = "@"; + rspArg += rspPath.str(); + + std::vector newArgs; + newArgs.push_back(argv[0]); + newArgs.push_back(rspArg.c_str()); + newArgs.push_back(NULL); + + int rc = execute(ldcPath, &newArgs[0]); + + bool couldRemove; + if (ls::fs::remove(rspPath.str(), couldRemove) != llvm::errc::success || + !couldRemove) + { + warning("Could not remove response file."); + } + + return rc; + } + else + { + return execute(ldcPath, &args[0]); + } +}