/** 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 std.algorithm; import std.array; import std.exception; import vibecompat.data.json; import vibecompat.inet.path; 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) { import vibecompat.core.log; struct SpecialFlag { string[] flags; string alternative; } static immutable SpecialFlag[] s_specialFlags = [ {["-c", "-o-", "-w", "-property"], "Managed by DUB, do not specify in package.json"}, {["-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(""); logWarn("Warning"); logWarn("======="); logWarn(""); logWarn("The following compiler flags have been specified in %s's", package_name); logWarn("package description file. They are handled by DUB and direct use in packages is"); logWarn("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.startsWith(sff))()) { outputPreamble(); logWarn("%s: %s", f, sf.alternative); break; } } } if (got_preamble) logWarn(""); } 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); /// Adds the appropriate flag to set a target path void setTarget(ref BuildSettings settings, in BuildPlatform platform); /// Invokes the underlying linker directly void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects); } /// BuildPlatform specific settings, like needed libraries or additional /// include paths. struct BuildSettings { TargetType targetType; string targetPath; string targetName; string[] dflags; string[] lflags; string[] libs; string[] sourceFiles; string[] copyFiles; string[] versions; string[] importPaths; string[] stringImportPaths; string[] preGenerateCommands; string[] postGenerateCommands; string[] preBuildCommands; string[] postBuildCommands; void addDFlags(in string[] value...) { add(dflags, value); } void addLFlags(in string[] value...) { add(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 addImportPaths(in string[] value...) { add(importPaths, value); } void addStringImportPaths(in string[] value...) { add(stringImportPaths, 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); } // 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) ) 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; /// e.g. "dmd" string compiler; /// 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, importPaths = 1<<6, stringImportPaths = 1<<7, none = 0, commandLine = dflags|copyFiles, commandLineSeparate = commandLine|lflags, all = dflags|lflags|libs|sourceFiles|copyFiles|versions|importPaths|stringImportPaths } enum TargetType { autodetect, executable, library, sourceLibrary, dynamicLibrary, staticLibrary } 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); 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") ) 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; }