/** Compiler settings and abstraction. Copyright: © 2013-2016 rejectedsoftware e.K. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Sönke Ludwig */ module dub.compilers.compiler; public import dub.compilers.buildsettings; public import dub.platform : BuildPlatform, matchesSpecification; import dub.compilers.dmd; import dub.compilers.gdc; import dub.compilers.ldc; import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; import std.algorithm; import std.array; import std.conv; import std.exception; import std.process; static this() { registerCompiler(new DMDCompiler); registerCompiler(new GDCCompiler); registerCompiler(new LDCCompiler); } /** Returns a compiler handler for a given binary name. The name will be compared against the canonical name of each registered compiler handler. If no match is found, the sub strings "dmd", "gdc" and "ldc", in this order, will be searched within the name. If this doesn't yield a match either, an exception will be thrown. */ Compiler getCompiler(string name) { foreach (c; s_compilers) if (c.name == name) return c; // try to match names like gdmd or gdc-2.61 if (name.canFind("dmd")) return getCompiler("dmd"); if (name.canFind("gdc")) return getCompiler("gdc"); if (name.canFind("ldc")) return getCompiler("ldc"); throw new Exception("Unknown compiler: "~name); } /** Returns the binary name of the default compiler for the system. When calling this function for the first time, it will search the PATH environment variable for files named "dmd", "gdc", "gdmd", "ldc2", "ldmd2" (in that order) and return the first match. If no match is found, "dmd" is returned. */ @property string defaultCompiler() { static string name; if (!name.length) name = findCompiler(); return name; } private string findCompiler() { import std.process : env=environment; version (Windows) enum sep = ";", exe = ".exe"; version (Posix) enum sep = ":", exe = ""; auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; auto paths = env.get("PATH", "").splitter(sep).map!Path; auto res = compilers.find!(bin => paths.canFind!(p => existsFile(p ~ (bin~exe)))); return res.empty ? compilers[0] : res.front; } /** Registers a new compiler handler. Note that by default `DMDCompiler`, `GDCCompiler` and `LDCCompiler` are already registered at startup. */ void registerCompiler(Compiler c) { s_compilers ~= c; } /** Searches the given list of compiler flags for ones that have a generic equivalent. Certain compiler flags should, instead of using compiler-specfic syntax, be specified as build options (`BuildOptions`) or built requirements (`BuildRequirements`). This function will output warning messages to assist the user in making the best choice. */ void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name) { struct SpecialFlag { string[] flags; string alternative; } static immutable SpecialFlag[] s_specialFlags = [ {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"}, {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, {["-unittest", "-funittest"], "Call dub with --build=unittest"}, {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, {["-D"], "Call dub with --build=docs or --build=ddox"}, {["-X"], "Call dub with --build=ddox"}, {["-cov"], "Call dub with --build=cov or --build=unittest-cov"}, {["-profile"], "Call dub with --build=profile"}, {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, {["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`} ]; struct SpecialOption { BuildOption[] flags; string alternative; } static immutable SpecialOption[] s_specialOptions = [ {[BuildOption.debugMode], "Call DUB with --build=debug"}, {[BuildOption.releaseMode], "Call DUB with --build=release"}, {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"}, {[BuildOption.debugInfo], "Call DUB with --build=debug"}, {[BuildOption.inline], "Call DUB with --build=release"}, {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"}, {[BuildOption.optimize], "Call DUB with --build=release"}, {[BuildOption.profile], "Call DUB with --build=profile"}, {[BuildOption.unittests], "Call DUB with --build=unittest"}, {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, {[BuildOption.property], "This flag is deprecated and has no effect"} ]; bool got_preamble = false; void outputPreamble() { if (got_preamble) return; got_preamble = true; logWarn(""); if (config_name.empty) logWarn("## Warning for package %s ##", package_name); else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); logWarn(""); logWarn("The following compiler flags have been specified in the package description"); logWarn("file. They are handled by DUB and direct use in packages is discouraged."); logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); logWarn("to the compiler, or use one of the suggestions below:"); logWarn(""); } foreach (f; compiler_flags) { foreach (sf; s_specialFlags) { if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { outputPreamble(); logWarn("%s: %s", f, sf.alternative); break; } } } foreach (sf; s_specialOptions) { foreach (f; sf.flags) { if (options & f) { outputPreamble(); logWarn("%s: %s", f, sf.alternative); break; } } } if (got_preamble) logWarn(""); } /** Alters the build options to comply with the specified build requirements. */ void enforceBuildRequirements(ref BuildSettings settings) { settings.addOptions(BuildOption.warningsAsErrors); if (settings.requirements & BuildRequirement.allowWarnings) { settings.options &= ~BuildOption.warningsAsErrors; settings.options |= BuildOption.warnings; } if (settings.requirements & BuildRequirement.silenceWarnings) settings.options &= ~(BuildOption.warningsAsErrors|BuildOption.warnings); if (settings.requirements & BuildRequirement.disallowDeprecations) { settings.options &= ~(BuildOption.ignoreDeprecations|BuildOption.deprecationWarnings); settings.options |= BuildOption.deprecationErrors; } if (settings.requirements & BuildRequirement.silenceDeprecations) { settings.options &= ~(BuildOption.deprecationErrors|BuildOption.deprecationWarnings); settings.options |= BuildOption.ignoreDeprecations; } if (settings.requirements & BuildRequirement.disallowInlining) settings.options &= ~BuildOption.inline; if (settings.requirements & BuildRequirement.disallowOptimization) settings.options &= ~BuildOption.optimize; if (settings.requirements & BuildRequirement.requireBoundsCheck) settings.options &= ~BuildOption.noBoundsCheck; if (settings.requirements & BuildRequirement.requireContracts) settings.options &= ~BuildOption.releaseMode; if (settings.requirements & BuildRequirement.relaxProperties) settings.options &= ~BuildOption.property; } /** Replaces each referenced import library by the appropriate linker flags. This function tries to invoke "pkg-config" if possible and falls back to direct flag translation if that fails. */ void resolveLibs(ref BuildSettings settings) { import std.string : format; if (settings.libs.length == 0) return; if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { logDiagnostic("Ignoring all import libraries for static library build."); settings.libs = null; version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; } version (Posix) { try { enum pkgconfig_bin = "pkg-config"; bool exists(string lib) { return execute([pkgconfig_bin, "--exists", lib]).status == 0; } auto pkgconfig_libs = settings.libs.partition!(l => !exists(l)); pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length] .partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array; settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length]; if (pkgconfig_libs.length) { logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", ")); auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs); enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output)); foreach (f; libflags.output.split()) { if (f.startsWith("-L-L")) { settings.addLFlags(f[2 .. $]); } else if (f.startsWith("-defaultlib")) { settings.addDFlags(f); } else if (f.startsWith("-L-defaultlib")) { settings.addDFlags(f[2 .. $]); } else if (f.startsWith("-pthread")) { settings.addLFlags("-lpthread"); } else if (f.startsWith("-L-l")) { settings.addLFlags(f[2 .. $].split(",")); } else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); else settings.addLFlags(f); } } if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", ")); } catch (Exception e) { logDiagnostic("pkg-config failed: %s", e.msg); logDiagnostic("Falling back to direct -l... flags."); } } } interface Compiler { @property string name() const; BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); /// Replaces high level fields with low level fields and converts /// dmd flags to compiler-specific flags void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const; /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. void extractBuildOptions(ref BuildSettings settings) const; /// Adds the appropriate flag to set a target path void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const; /// Invokes the compiler using the given flags void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback); /// Invokes the underlying linker directly void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback); /// Convert linker flags to compiler format string[] lflagsToDFlags(in string[] lflags) const; protected final void invokeTool(string[] args, void delegate(int, string) output_callback) { import std.string; int status; if (output_callback) { auto result = executeShell(escapeShellCommand(args)); output_callback(result.status, result.output); status = result.status; } else { auto compiler_pid = spawnShell(escapeShellCommand(args)); status = compiler_pid.wait(); } version (Posix) if (status == -9) { throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory.", args[0], status)); } enforce(status == 0, format("%s failed with exit code %s.", args[0], status)); } } string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) { assert(settings.targetName.length > 0, "No target name set."); final switch (settings.targetType) { case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); case TargetType.none: return null; case TargetType.sourceLibrary: return null; case TargetType.executable: if( platform.platform.canFind("windows") ) return settings.targetName ~ ".exe"; else return settings.targetName; case TargetType.library: case TargetType.staticLibrary: if (platform.platform.canFind("windows") && platform.compiler == "dmd") return settings.targetName ~ ".lib"; else return "lib" ~ settings.targetName ~ ".a"; case TargetType.dynamicLibrary: if( platform.platform.canFind("windows") ) return settings.targetName ~ ".dll"; else return "lib" ~ settings.targetName ~ ".so"; case TargetType.object: if (platform.platform.canFind("windows")) return settings.targetName ~ ".obj"; else return settings.targetName ~ ".o"; } } bool isLinkerFile(string f) { import std.path; switch (extension(f)) { default: return false; version (Windows) { case ".lib", ".obj", ".res", ".def": return true; } else { case ".a", ".o", ".so", ".dylib": return true; } } } /// Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...) Path generatePlatformProbeFile() { import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.data.json; import dub.internal.utils; auto path = getTempFile("dub_platform_probe", ".d"); auto fil = openFile(path, FileMode.createTrunc); scope (failure) { fil.close(); } // NOTE: This must be kept in sync with the dub.platform module fil.write(q{ module dub_platform_probe; template toString(int v) { enum toString = v.stringof; } pragma(msg, `{`); pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`); pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`); pragma(msg, ` "platform": [`); pragma(msg, ` ` ~ determinePlatform()); pragma(msg, ` ],`); pragma(msg, ` "architecture": [`); pragma(msg, ` ` ~ determineArchitecture()); pragma(msg, ` ],`); pragma(msg, `}`); string determinePlatform() { string ret; version(Windows) ret ~= `"windows", `; version(linux) ret ~= `"linux", `; version(Posix) ret ~= `"posix", `; version(OSX) ret ~= `"osx", `; version(FreeBSD) ret ~= `"freebsd", `; version(OpenBSD) ret ~= `"openbsd", `; version(NetBSD) ret ~= `"netbsd", `; version(DragonFlyBSD) ret ~= `"dragonflybsd", `; version(BSD) ret ~= `"bsd", `; version(Solaris) ret ~= `"solaris", `; version(AIX) ret ~= `"aix", `; version(Haiku) ret ~= `"haiku", `; version(SkyOS) ret ~= `"skyos", `; version(SysV3) ret ~= `"sysv3", `; version(SysV4) ret ~= `"sysv4", `; version(Hurd) ret ~= `"hurd", `; version(Android) ret ~= `"android", `; version(Cygwin) ret ~= `"cygwin", `; version(MinGW) ret ~= `"mingw", `; return ret; } string determineArchitecture() { string ret; version(X86) ret ~= `"x86", `; version(X86_64) ret ~= `"x86_64", `; version(ARM) ret ~= `"arm", `; version(ARM_Thumb) ret ~= `"arm_thumb", `; version(ARM_SoftFloat) ret ~= `"arm_softfloat", `; version(ARM_HardFloat) ret ~= `"arm_hardfloat", `; version(ARM64) ret ~= `"arm64", `; version(PPC) ret ~= `"ppc", `; version(PPC_SoftFP) ret ~= `"ppc_softfp", `; version(PPC_HardFP) ret ~= `"ppc_hardfp", `; version(PPC64) ret ~= `"ppc64", `; version(IA64) ret ~= `"ia64", `; version(MIPS) ret ~= `"mips", `; version(MIPS32) ret ~= `"mips32", `; version(MIPS64) ret ~= `"mips64", `; version(MIPS_O32) ret ~= `"mips_o32", `; version(MIPS_N32) ret ~= `"mips_n32", `; version(MIPS_O64) ret ~= `"mips_o64", `; version(MIPS_N64) ret ~= `"mips_n64", `; version(MIPS_EABI) ret ~= `"mips_eabi", `; version(MIPS_NoFloat) ret ~= `"mips_nofloat", `; version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `; version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `; version(SPARC) ret ~= `"sparc", `; version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `; version(SPARC_SoftFP) ret ~= `"sparc_softfp", `; version(SPARC_HardFP) ret ~= `"sparc_hardfp", `; version(SPARC64) ret ~= `"sparc64", `; version(S390) ret ~= `"s390", `; version(S390X) ret ~= `"s390x", `; version(HPPA) ret ~= `"hppa", `; version(HPPA64) ret ~= `"hppa64", `; version(SH) ret ~= `"sh", `; version(SH64) ret ~= `"sh64", `; version(Alpha) ret ~= `"alpha", `; version(Alpha_SoftFP) ret ~= `"alpha_softfp", `; version(Alpha_HardFP) ret ~= `"alpha_hardfp", `; return ret; } string determineCompiler() { version(DigitalMars) return "dmd"; else version(GNU) return "gdc"; else version(LDC) return "ldc"; else version(SDC) return "sdc"; else return null; } }); fil.close(); return path; } BuildPlatform readPlatformProbe(string output) { import std.string; // work around possible additional output of the compiler auto idx1 = output.indexOf("{"); auto idx2 = output.lastIndexOf("}"); enforce(idx1 >= 0 && idx1 < idx2, "Unexpected platform information output - does not contain a JSON object."); output = output[idx1 .. idx2+1]; import dub.internal.vibecompat.data.json; auto json = parseJsonString(output); BuildPlatform build_platform; build_platform.platform = json.platform.get!(Json[]).map!(e => e.get!string()).array(); build_platform.architecture = json.architecture.get!(Json[]).map!(e => e.get!string()).array(); build_platform.compiler = json.compiler.get!string; build_platform.frontendVersion = json.frontendVersion.get!int; return build_platform; } private { Compiler[] s_compilers; }