1 /** 2 Compiler settings and abstraction. 3 4 Copyright: © 2013 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dub.compilers.compiler; 9 10 import dub.compilers.dmd; 11 import dub.compilers.gdc; 12 import dub.compilers.ldc; 13 import dub.internal.vibecompat.core.log; 14 import dub.internal.vibecompat.data.json; 15 import dub.internal.vibecompat.inet.path; 16 17 import std.algorithm; 18 import std.array; 19 import std.conv; 20 import std.exception; 21 import std.process; 22 import std.path : globMatch; 23 24 25 static this() 26 { 27 registerCompiler(new DmdCompiler); 28 registerCompiler(new GdcCompiler); 29 registerCompiler(new LdcCompiler); 30 } 31 32 33 Compiler getCompiler(string name) 34 { 35 foreach (c; s_compilers) 36 if (c.name == name) 37 return c; 38 39 // try to match names like gdmd or gdc-2.61 40 if (name.canFind("dmd")) return getCompiler("dmd"); 41 if (name.canFind("gdc")) return getCompiler("gdc"); 42 if (name.canFind("ldc")) return getCompiler("ldc"); 43 44 throw new Exception("Unknown compiler: "~name); 45 } 46 47 void registerCompiler(Compiler c) 48 { 49 s_compilers ~= c; 50 } 51 52 void warnOnSpecialCompilerFlags(string[] compiler_flags, string package_name, string config_name) 53 { 54 struct SpecialFlag { 55 string[] flags; 56 string alternative; 57 } 58 static immutable SpecialFlag[] s_specialFlags = [ 59 {["-c", "-o-"], "Automatically issued by DUB, do not specify in package.json"}, 60 {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, 61 {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, 62 {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, 63 {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, 64 {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, 65 {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, 66 {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, 67 {["-unittest", "-funittest"], "Call dub with --build=unittest"}, 68 {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, 69 {["-D"], "Call dub with --build=docs or --build=ddox"}, 70 {["-X"], "Call dub with --build=ddox"}, 71 {["-cov"], "Call dub with --build=cov or --build=unittest-cox"}, 72 {["-profile"], "Call dub with --build=profile"}, 73 {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, 74 {["-debug=", `Use "debugVersions" to specify version constants in a compiler independent way`]}, 75 {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, 76 {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, 77 ]; 78 79 bool got_preamble = false; 80 void outputPreamble() 81 { 82 if (got_preamble) return; 83 got_preamble = true; 84 logWarn(""); 85 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 86 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 87 logWarn(""); 88 logWarn("The following compiler flags have been specified in the package description"); 89 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 90 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 91 logWarn("to the compiler, or use one of the suggestions below:"); 92 logWarn(""); 93 } 94 95 foreach (f; compiler_flags) { 96 foreach (sf; s_specialFlags) { 97 if (sf.flags.canFind!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 98 outputPreamble(); 99 logWarn("%s: %s", f, sf.alternative); 100 break; 101 } 102 } 103 } 104 105 if (got_preamble) logWarn(""); 106 } 107 108 109 /** 110 Replaces each referenced import library by the appropriate linker flags. 111 112 This function tries to invoke "pkg-config" if possible and falls back to 113 direct flag translation if that fails. 114 */ 115 void resolveLibs(ref BuildSettings settings) 116 { 117 if (settings.libs.length == 0) return; 118 119 if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { 120 logDiagnostic("Ignoring all import libraries for static library build."); 121 settings.libs = null; 122 version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; 123 } 124 125 try { 126 logDiagnostic("Trying to use pkg-config to resolve library flags for %s.", settings.libs); 127 auto libflags = execute(["pkg-config", "--libs"] ~ settings.libs.map!(l => "lib"~l)().array()); 128 enforce(libflags.status == 0, "pkg-config exited with error code "~to!string(libflags.status)); 129 foreach (f; libflags.output.split()) { 130 if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 131 else settings.addLFlags(f); 132 } 133 settings.libs = null; 134 } catch (Exception e) { 135 logDiagnostic("pkg-config failed: %s", e.msg); 136 logDiagnostic("Falling back to direct -lxyz flags."); 137 } 138 } 139 140 141 interface Compiler { 142 @property string name() const; 143 144 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); 145 146 /// Replaces high level fields with low level fields and converts 147 /// dmd flags to compiler-specific flags 148 void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all); 149 150 /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. 151 void extractBuildOptions(ref BuildSettings settings); 152 153 /// Adds the appropriate flag to set a target path 154 void setTarget(ref BuildSettings settings, in BuildPlatform platform); 155 156 /// Invokes the compiler using the given flags 157 void invoke(in BuildSettings settings, in BuildPlatform platform); 158 159 /// Invokes the underlying linker directly 160 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects); 161 162 final protected void enforceBuildRequirements(ref BuildSettings settings) 163 { 164 settings.addOptions(BuildOptions.warningsAsErrors); 165 if (settings.requirements & BuildRequirements.allowWarnings) { settings.options &= ~BuildOptions.warningsAsErrors; settings.options |= BuildOptions.warnings; } 166 if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings); 167 if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; } 168 if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; } 169 if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= BuildOptions.inline; 170 if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize; 171 if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsChecks; 172 if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.releaseMode; 173 if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.property; 174 } 175 } 176 177 178 /// BuildPlatform specific settings, like needed libraries or additional 179 /// include paths. 180 struct BuildSettings { 181 TargetType targetType; 182 string targetPath; 183 string targetName; 184 string workingDirectory; 185 string mainSourceFile; 186 string[] dflags; 187 string[] lflags; 188 string[] libs; 189 string[] sourceFiles; 190 string[] copyFiles; 191 string[] versions; 192 string[] debugVersions; 193 string[] importPaths; 194 string[] stringImportPaths; 195 string[] importFiles; 196 string[] stringImportFiles; 197 string[] preGenerateCommands; 198 string[] postGenerateCommands; 199 string[] preBuildCommands; 200 string[] postBuildCommands; 201 BuildRequirements requirements; 202 BuildOptions options; 203 204 void addDFlags(in string[] value...) { dflags ~= value; } 205 void removeDFlags(in string[] value...) { remove(dflags, value); } 206 void addLFlags(in string[] value...) { lflags ~= value; } 207 void addLibs(in string[] value...) { add(libs, value); } 208 void addSourceFiles(in string[] value...) { add(sourceFiles, value); } 209 void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); } 210 void addCopyFiles(in string[] value...) { add(copyFiles, value); } 211 void addVersions(in string[] value...) { add(versions, value); } 212 void addDebugVersions(in string[] value...) { add(debugVersions, value); } 213 void addImportPaths(in string[] value...) { add(importPaths, value); } 214 void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); } 215 void addImportFiles(in string[] value...) { add(importFiles, value); } 216 void removeImportFiles(in string[] value...) { removePaths(importFiles, value); } 217 void addStringImportFiles(in string[] value...) { add(stringImportFiles, value); } 218 void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); } 219 void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); } 220 void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); } 221 void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); } 222 void addRequirements(in BuildRequirements[] value...) { foreach (v; value) this.requirements |= v; } 223 void addOptions(in BuildOptions[] value...) { foreach (v; value) this.options |= v; } 224 void removeOptions(in BuildOptions[] value...) { foreach (v; value) this.options &= ~v; } 225 226 // Adds vals to arr without adding duplicates. 227 private void add(ref string[] arr, in string[] vals, bool no_duplicates = true) 228 { 229 if (!no_duplicates) { 230 arr ~= vals; 231 return; 232 } 233 234 foreach (v; vals) { 235 bool found = false; 236 foreach (i; 0 .. arr.length) 237 if (arr[i] == v) { 238 found = true; 239 break; 240 } 241 if (!found) arr ~= v; 242 } 243 } 244 245 private void removePaths(ref string[] arr, in string[] vals) 246 { 247 bool matches(string s) 248 { 249 foreach (p; vals) 250 if (Path(s) == Path(p) || globMatch(s, p)) 251 return true; 252 return false; 253 } 254 arr = arr.filter!(s => !matches(s))().array(); 255 } 256 257 private void remove(ref string[] arr, in string[] vals) 258 { 259 bool matches(string s) 260 { 261 foreach (p; vals) 262 if (s == p) 263 return true; 264 return false; 265 } 266 arr = arr.filter!(s => !matches(s))().array(); 267 } 268 } 269 270 /// Represents a platform a package can be build upon. 271 struct BuildPlatform { 272 /// e.g. ["posix", "windows"] 273 string[] platform; 274 /// e.g. ["x86", "x86_64"] 275 string[] architecture; 276 /// Canonical compiler name e.g. "dmd" 277 string compiler; 278 /// Compiler binary name e.g. "ldmd2" 279 string compilerBinary; 280 281 /// Build platforms can be specified via a string specification. 282 /// 283 /// Specifications are build upon the following scheme, where each component 284 /// is optional (indicated by []), but the order is obligatory. 285 /// "[-platform][-architecture][-compiler]" 286 /// 287 /// So the following strings are valid specifications: 288 /// "-windows-x86-dmd" 289 /// "-dmd" 290 /// "-arm" 291 /// "-arm-dmd" 292 /// "-windows-dmd" 293 /// 294 /// Params: 295 /// specification = The specification being matched. It must be the empty string or start with a dash. 296 /// 297 /// Returns: 298 /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches) 299 /// 300 bool matchesSpecification(const(char)[] specification) const { 301 if (specification.empty) 302 return true; 303 auto splitted=specification.splitter('-'); 304 assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); 305 splitted.popFront(); // Drop leading empty match. 306 enforce(!splitted.empty, "Platform specification if present, must not be empty!"); 307 if (platform.canFind(splitted.front)) { 308 splitted.popFront(); 309 if(splitted.empty) 310 return true; 311 } 312 if (architecture.canFind(splitted.front)) { 313 splitted.popFront(); 314 if(splitted.empty) 315 return true; 316 } 317 if (compiler == splitted.front) { 318 splitted.popFront(); 319 enforce(splitted.empty, "No valid specification! The compiler has to be the last element!"); 320 return true; 321 } 322 return false; 323 } 324 unittest { 325 auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 326 assert(platform.matchesSpecification("-posix")); 327 assert(platform.matchesSpecification("-linux")); 328 assert(platform.matchesSpecification("-linux-dmd")); 329 assert(platform.matchesSpecification("-linux-x86_64-dmd")); 330 assert(platform.matchesSpecification("-x86_64")); 331 assert(!platform.matchesSpecification("-windows")); 332 assert(!platform.matchesSpecification("-ldc")); 333 assert(!platform.matchesSpecification("-windows-dmd")); 334 } 335 } 336 337 enum BuildSetting { 338 dflags = 1<<0, 339 lflags = 1<<1, 340 libs = 1<<2, 341 sourceFiles = 1<<3, 342 copyFiles = 1<<4, 343 versions = 1<<5, 344 debugVersions = 1<<6, 345 importPaths = 1<<7, 346 stringImportPaths = 1<<8, 347 options = 1<<9, 348 none = 0, 349 commandLine = dflags|copyFiles, 350 commandLineSeparate = commandLine|lflags, 351 all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options, 352 noOptions = all & ~options 353 } 354 355 enum TargetType { 356 autodetect, 357 none, 358 executable, 359 library, 360 sourceLibrary, 361 dynamicLibrary, 362 staticLibrary 363 } 364 365 enum BuildRequirements { 366 none = 0, /// No special requirements 367 allowWarnings = 1<<0, /// Warnings do not abort compilation 368 silenceWarnings = 1<<1, /// Don't show warnings 369 disallowDeprecations = 1<<2, /// Using deprecated features aborts compilation 370 silenceDeprecations = 1<<3, /// Don't show deprecation warnings 371 disallowInlining = 1<<4, /// Avoid function inlining, even in release builds 372 disallowOptimization = 1<<5, /// Avoid optimizations, even in release builds 373 requireBoundsCheck = 1<<6, /// Always perform bounds checks 374 requireContracts = 1<<7, /// Leave assertions and contracts enabled in release builds 375 relaxProperties = 1<<8, /// DEPRECATED: Do not enforce strict property handling (-property) 376 noDefaultFlags = 1<<9, /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes 377 } 378 379 enum BuildOptions { 380 none = 0, /// Use compiler defaults 381 debugMode = 1<<0, /// Compile in debug mode (enables contracts, -debug) 382 releaseMode = 1<<1, /// Compile in release mode (disables assertions and bounds checks, -release) 383 coverage = 1<<2, /// Enable code coverage analysis (-cov) 384 debugInfo = 1<<3, /// Enable symbolic debug information (-g) 385 debugInfoC = 1<<4, /// Enable symbolic debug information in C compatible form (-gc) 386 alwaysStackFrame = 1<<5, /// Always generate a stack frame (-gs) 387 stackStomping = 1<<6, /// Perform stack stomping (-gx) 388 inline = 1<<7, /// Perform function inlining (-inline) 389 noBoundsChecks = 1<<8, /// Disable all bounds checking (-noboundscheck) 390 optimize = 1<<9, /// Enable optimizations (-O) 391 profile = 1<<10, /// Emit profiling code (-profile) 392 unittests = 1<<11, /// Compile unit tests (-unittest) 393 verbose = 1<<12, /// Verbose compiler output (-v) 394 ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore) 395 syntaxOnly = 1<<14, /// Don't generate object files (-o-) 396 warnings = 1<<15, /// Enable warnings (-wi) 397 warningsAsErrors = 1<<16, /// Treat warnings as errors (-w) 398 ignoreDeprecations = 1<<17, /// Do not warn about using deprecated features (-d) 399 deprecationWarnings = 1<<18, /// Warn about using deprecated features (-dw) 400 deprecationErrors = 1<<19, /// Stop compilation upon usage of deprecated features (-de) 401 property = 1<<20, /// DEPRECATED: Enforce property syntax (-property) 402 } 403 404 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 405 { 406 assert(settings.targetName.length > 0, "No target name set."); 407 final switch (settings.targetType) { 408 case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); 409 case TargetType.none: return null; 410 case TargetType.sourceLibrary: return null; 411 case TargetType.executable: 412 if( platform.platform.canFind("windows") ) 413 return settings.targetName ~ ".exe"; 414 else return settings.targetName; 415 case TargetType.library: 416 case TargetType.staticLibrary: 417 if (platform.platform.canFind("windows") && platform.compiler == "dmd") 418 return settings.targetName ~ ".lib"; 419 else return "lib" ~ settings.targetName ~ ".a"; 420 case TargetType.dynamicLibrary: 421 if( platform.platform.canFind("windows") ) 422 return settings.targetName ~ ".dll"; 423 else return "lib" ~ settings.targetName ~ ".so"; 424 } 425 } 426 427 428 429 private { 430 Compiler[] s_compilers; 431 }