/** Compiler settings and abstraction. Copyright: © 2013 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; import dub.compilers.dmd; import dub.compilers.gdc; import dub.compilers.ldc; 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; import std.path : globMatch; static this() { registerCompiler(new DmdCompiler); registerCompiler(new GdcCompiler); registerCompiler(new LdcCompiler); } 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); } void registerCompiler(Compiler c) { s_compilers ~= c; } void warnOnSpecialCompilerFlags(string[] compiler_flags, 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 package.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-cox"}, {["-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`}, ]; 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.canFind!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { outputPreamble(); logWarn("%s: %s", f, sf.alternative); break; } } } if (got_preamble) logWarn(""); } /** 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) { 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; } try { logDiagnostic("Trying to use pkg-config to resolve library flags for %s.", settings.libs); auto libflags = execute(["pkg-config", "--libs"] ~ settings.libs.map!(l => "lib"~l)().array()); enforce(libflags.status == 0, "pkg-config exited with error code "~to!string(libflags.status)); foreach (f; libflags.output.split()) { if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); else settings.addLFlags(f); } settings.libs = null; } catch (Exception e) { logDiagnostic("pkg-config failed: %s", e.msg); logDiagnostic("Falling back to direct -lxyz 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); /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. void extractBuildOptions(ref BuildSettings settings); /// Adds the appropriate flag to set a target path void setTarget(ref BuildSettings settings, in BuildPlatform platform); /// Invokes the compiler using the given flags void invoke(in BuildSettings settings, in BuildPlatform platform); /// Invokes the underlying linker directly void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects); final protected void enforceBuildRequirements(ref BuildSettings settings) { settings.addOptions(BuildOptions.warningsAsErrors); if (settings.requirements & BuildRequirements.allowWarnings) { settings.options &= ~BuildOptions.warningsAsErrors; settings.options |= BuildOptions.warnings; } if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings); if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; } if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; } if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= BuildOptions.inline; if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize; if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsChecks; if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.release; if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.property; } } /// BuildPlatform specific settings, like needed libraries or additional /// include paths. struct BuildSettings { TargetType targetType; string targetPath; string targetName; string workingDirectory; string[] dflags; string[] lflags; string[] libs; string[] sourceFiles; string[] copyFiles; string[] versions; string[] debugVersions; string[] importPaths; string[] stringImportPaths; string[] importFiles; string[] stringImportFiles; string[] preGenerateCommands; string[] postGenerateCommands; string[] preBuildCommands; string[] postBuildCommands; BuildRequirements requirements; BuildOptions options; void addDFlags(in string[] value...) { dflags ~= value; } void removeDFlags(in string[] value...) { remove(dflags, value); } void addLFlags(in string[] value...) { lflags ~= value; } void addLibs(in string[] value...) { add(libs, value); } void addSourceFiles(in string[] value...) { add(sourceFiles, value); } void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); } void addCopyFiles(in string[] value...) { add(copyFiles, value); } void addVersions(in string[] value...) { add(versions, value); } void addDebugVersions(in string[] value...) { add(debugVersions, value); } void addImportPaths(in string[] value...) { add(importPaths, value); } void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); } void addImportFiles(in string[] value...) { add(importFiles, value); } void removeImportFiles(in string[] value...) { removePaths(importFiles, value); } void addStringImportFiles(in string[] value...) { add(stringImportFiles, value); } void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); } void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); } void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); } void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); } void addRequirements(in BuildRequirements[] value...) { foreach (v; value) this.requirements |= v; } void addOptions(in BuildOptions[] value...) { foreach (v; value) this.options |= v; } void removeOptions(in BuildOptions[] value...) { foreach (v; value) this.options &= ~v; } // Adds vals to arr without adding duplicates. private void add(ref string[] arr, in string[] vals, bool no_duplicates = true) { if (!no_duplicates) { arr ~= vals; return; } foreach (v; vals) { bool found = false; foreach (i; 0 .. arr.length) if (arr[i] == v) { found = true; break; } if (!found) arr ~= v; } } private void removePaths(ref string[] arr, in string[] vals) { bool matches(string s) { foreach (p; vals) if (Path(s) == Path(p) || globMatch(s, p)) return true; return false; } arr = arr.filter!(s => !matches(s))().array(); } private void remove(ref string[] arr, in string[] vals) { bool matches(string s) { foreach (p; vals) if (s == p) return true; return false; } arr = arr.filter!(s => !matches(s))().array(); } } /// Represents a platform a package can be build upon. struct BuildPlatform { /// e.g. ["posix", "windows"] string[] platform; /// e.g. ["x86", "x86_64"] string[] architecture; /// Canonical compiler name e.g. "dmd" string compiler; /// Compiler binary name e.g. "ldmd2" string compilerBinary; /// Build platforms can be specified via a string specification. /// /// Specifications are build upon the following scheme, where each component /// is optional (indicated by []), but the order is obligatory. /// "[-platform][-architecture][-compiler]" /// /// So the following strings are valid specifications: /// "-windows-x86-dmd" /// "-dmd" /// "-arm" /// "-arm-dmd" /// "-windows-dmd" /// /// Params: /// specification = The specification being matched. It must be the empty string or start with a dash. /// /// Returns: /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches) /// bool matchesSpecification(const(char)[] specification) const { if (specification.empty) return true; auto splitted=specification.splitter('-'); assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); splitted.popFront(); // Drop leading empty match. enforce(!splitted.empty, "Platform specification if present, must not be empty!"); if (platform.canFind(splitted.front)) { splitted.popFront(); if(splitted.empty) return true; } if (architecture.canFind(splitted.front)) { splitted.popFront(); if(splitted.empty) return true; } if (compiler == splitted.front) { splitted.popFront(); enforce(splitted.empty, "No valid specification! The compiler has to be the last element!"); return true; } return false; } unittest { auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); assert(platform.matchesSpecification("-posix")); assert(platform.matchesSpecification("-linux")); assert(platform.matchesSpecification("-linux-dmd")); assert(platform.matchesSpecification("-linux-x86_64-dmd")); assert(platform.matchesSpecification("-x86_64")); assert(!platform.matchesSpecification("-windows")); assert(!platform.matchesSpecification("-ldc")); assert(!platform.matchesSpecification("-windows-dmd")); } } enum BuildSetting { dflags = 1<<0, lflags = 1<<1, libs = 1<<2, sourceFiles = 1<<3, copyFiles = 1<<4, versions = 1<<5, debugVersions = 1<<6, importPaths = 1<<7, stringImportPaths = 1<<8, options = 1<<9, none = 0, commandLine = dflags|copyFiles, commandLineSeparate = commandLine|lflags, all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options, noOptions = all & ~options } enum TargetType { autodetect, none, executable, library, sourceLibrary, dynamicLibrary, staticLibrary } enum BuildRequirements { none = 0, /// No special requirements allowWarnings = 1<<0, /// Warnings do not abort compilation silenceWarnings = 1<<1, /// Don't show warnings disallowDeprecations = 1<<2, /// Using deprecated features aborts compilation silenceDeprecations = 1<<3, /// Don't show deprecation warnings disallowInlining = 1<<4, /// Avoid function inlining, even in release builds disallowOptimization = 1<<5, /// Avoid optimizations, even in release builds requireBoundsCheck = 1<<6, /// Always perform bounds checks requireContracts = 1<<7, /// Leave assertions and contracts enabled in release builds relaxProperties = 1<<8, /// DEPRECATED: Do not enforce strict property handling (-property) noDefaultFlags = 1<<9, /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes } enum BuildOptions { none = 0, /// Use compiler defaults debug_ = 1<<0, /// Compile in debug mode (enables contracts, -debug) release = 1<<1, /// Compile in release mode (disables assertions and bounds checks, -release) coverage = 1<<2, /// Enable code coverage analysis (-cov) debugInfo = 1<<3, /// Enable symbolic debug information (-g) debugInfoC = 1<<4, /// Enable symbolic debug information in C compatible form (-gc) alwaysStackFrame = 1<<5, /// Always generate a stack frame (-gs) stackStomping = 1<<6, /// Perform stack stomping (-gx) inline = 1<<7, /// Perform function inlining (-inline) noBoundsChecks = 1<<8, /// Disable all bounds checking (-noboundscheck) optimize = 1<<9, /// Enable optimizations (-O) profile = 1<<10, /// Emit profiling code (-profile) unittests = 1<<11, /// Compile unit tests (-unittest) verbose = 1<<12, /// Verbose compiler output (-v) ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore) syntaxOnly = 1<<14, /// Don't generate object files (-o-) warnings = 1<<15, /// Enable warnings (-wi) warningsAsErrors = 1<<16, /// Treat warnings as errors (-w) ignoreDeprecations = 1<<17, /// Do not warn about using deprecated features (-d) deprecationWarnings = 1<<18, /// Warn about using deprecated features (-dw) deprecationErrors = 1<<19, /// Stop compilation upon usage of deprecated features (-de) property = 1<<20, /// DEPRECATED: Enforce property syntax (-property) } 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"; } } private { Compiler[] s_compilers; }