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 Alters the build options to comply with the specified build requirements. 111 */ 112 void enforceBuildRequirements(ref BuildSettings settings) 113 { 114 settings.addOptions(BuildOptions.warningsAsErrors); 115 if (settings.requirements & BuildRequirements.allowWarnings) { settings.options &= ~BuildOptions.warningsAsErrors; settings.options |= BuildOptions.warnings; } 116 if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings); 117 if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; } 118 if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; } 119 if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= BuildOptions.inline; 120 if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize; 121 if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsCheck; 122 if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.releaseMode; 123 if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.property; 124 } 125 126 127 /** 128 Replaces each referenced import library by the appropriate linker flags. 129 130 This function tries to invoke "pkg-config" if possible and falls back to 131 direct flag translation if that fails. 132 */ 133 void resolveLibs(ref BuildSettings settings) 134 { 135 if (settings.libs.length == 0) return; 136 137 if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { 138 logDiagnostic("Ignoring all import libraries for static library build."); 139 settings.libs = null; 140 version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; 141 } 142 143 try { 144 logDiagnostic("Trying to use pkg-config to resolve library flags for %s.", settings.libs); 145 auto libflags = execute(["pkg-config", "--libs"] ~ settings.libs.map!(l => "lib"~l)().array()); 146 enforce(libflags.status == 0, "pkg-config exited with error code "~to!string(libflags.status)); 147 foreach (f; libflags.output.split()) { 148 if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 149 else settings.addLFlags(f); 150 } 151 settings.libs = null; 152 } catch (Exception e) { 153 logDiagnostic("pkg-config failed: %s", e.msg); 154 logDiagnostic("Falling back to direct -lxyz flags."); 155 } 156 } 157 158 159 interface Compiler { 160 @property string name() const; 161 162 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); 163 164 /// Replaces high level fields with low level fields and converts 165 /// dmd flags to compiler-specific flags 166 void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all); 167 168 /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. 169 void extractBuildOptions(ref BuildSettings settings); 170 171 /// Adds the appropriate flag to set a target path 172 void setTarget(ref BuildSettings settings, in BuildPlatform platform); 173 174 /// Invokes the compiler using the given flags 175 void invoke(in BuildSettings settings, in BuildPlatform platform); 176 177 /// Invokes the underlying linker directly 178 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects); 179 } 180 181 /// BuildPlatform specific settings, like needed libraries or additional 182 /// include paths. 183 struct BuildSettings { 184 TargetType targetType; 185 string targetPath; 186 string targetName; 187 string workingDirectory; 188 string mainSourceFile; 189 string[] dflags; 190 string[] lflags; 191 string[] libs; 192 string[] sourceFiles; 193 string[] copyFiles; 194 string[] versions; 195 string[] debugVersions; 196 string[] importPaths; 197 string[] stringImportPaths; 198 string[] importFiles; 199 string[] stringImportFiles; 200 string[] preGenerateCommands; 201 string[] postGenerateCommands; 202 string[] preBuildCommands; 203 string[] postBuildCommands; 204 BuildRequirements requirements; 205 BuildOptions options; 206 207 BuildSettings dup() 208 const { 209 BuildSettings ret; 210 foreach (m; __traits(allMembers, BuildSettings)) { 211 static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m).dup))) 212 __traits(getMember, ret, m) = __traits(getMember, this, m).dup; 213 else static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m)))) 214 __traits(getMember, ret, m) = __traits(getMember, this, m); 215 } 216 assert(ret.targetType == targetType); 217 assert(ret.targetName == targetName); 218 assert(ret.importPaths == importPaths); 219 return ret; 220 } 221 222 void add(in BuildSettings bs) 223 { 224 addDFlags(bs.dflags); 225 addLFlags(bs.lflags); 226 addLibs(bs.libs); 227 addSourceFiles(bs.sourceFiles); 228 addCopyFiles(bs.copyFiles); 229 addVersions(bs.versions); 230 addDebugVersions(bs.debugVersions); 231 addImportPaths(bs.importPaths); 232 addStringImportPaths(bs.stringImportPaths); 233 addImportFiles(bs.importFiles); 234 addStringImportFiles(bs.stringImportFiles); 235 addPreGenerateCommands(bs.preGenerateCommands); 236 addPostGenerateCommands(bs.postGenerateCommands); 237 addPreBuildCommands(bs.preBuildCommands); 238 addPostBuildCommands(bs.postBuildCommands); 239 } 240 241 void addDFlags(in string[] value...) { dflags ~= value; } 242 void removeDFlags(in string[] value...) { remove(dflags, value); } 243 void addLFlags(in string[] value...) { lflags ~= value; } 244 void addLibs(in string[] value...) { add(libs, value); } 245 void addSourceFiles(in string[] value...) { add(sourceFiles, value); } 246 void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); } 247 void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); } 248 void addCopyFiles(in string[] value...) { add(copyFiles, value); } 249 void addVersions(in string[] value...) { add(versions, value); } 250 void addDebugVersions(in string[] value...) { add(debugVersions, value); } 251 void addImportPaths(in string[] value...) { add(importPaths, value); } 252 void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); } 253 void prependStringImportPaths(in string[] value...) { prepend(stringImportPaths, value); } 254 void addImportFiles(in string[] value...) { add(importFiles, value); } 255 void removeImportFiles(in string[] value...) { removePaths(importFiles, value); } 256 void addStringImportFiles(in string[] value...) { add(stringImportFiles, value); } 257 void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); } 258 void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); } 259 void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); } 260 void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); } 261 void addRequirements(in BuildRequirements[] value...) { foreach (v; value) this.requirements |= v; } 262 void addOptions(in BuildOptions[] value...) { foreach (v; value) this.options |= v; } 263 void removeOptions(in BuildOptions[] value...) { foreach (v; value) this.options &= ~v; } 264 265 // Adds vals to arr without adding duplicates. 266 private void add(ref string[] arr, in string[] vals, bool no_duplicates = true) 267 { 268 if (!no_duplicates) { 269 arr ~= vals; 270 return; 271 } 272 273 foreach (v; vals) { 274 bool found = false; 275 foreach (i; 0 .. arr.length) 276 if (arr[i] == v) { 277 found = true; 278 break; 279 } 280 if (!found) arr ~= v; 281 } 282 } 283 284 private void prepend(ref string[] arr, in string[] vals, bool no_duplicates = true) 285 { 286 if (!no_duplicates) { 287 arr = vals ~ arr; 288 return; 289 } 290 291 foreach_reverse (v; vals) { 292 bool found = false; 293 foreach (i; 0 .. arr.length) 294 if (arr[i] == v) { 295 found = true; 296 break; 297 } 298 if (!found) arr = v ~ arr; 299 } 300 } 301 302 private void removePaths(ref string[] arr, in string[] vals) 303 { 304 bool matches(string s) 305 { 306 foreach (p; vals) 307 if (Path(s) == Path(p) || globMatch(s, p)) 308 return true; 309 return false; 310 } 311 arr = arr.filter!(s => !matches(s))().array(); 312 } 313 314 private void remove(ref string[] arr, in string[] vals) 315 { 316 bool matches(string s) 317 { 318 foreach (p; vals) 319 if (s == p) 320 return true; 321 return false; 322 } 323 arr = arr.filter!(s => !matches(s))().array(); 324 } 325 } 326 327 /// Represents a platform a package can be build upon. 328 struct BuildPlatform { 329 /// e.g. ["posix", "windows"] 330 string[] platform; 331 /// e.g. ["x86", "x86_64"] 332 string[] architecture; 333 /// Canonical compiler name e.g. "dmd" 334 string compiler; 335 /// Compiler binary name e.g. "ldmd2" 336 string compilerBinary; 337 338 /// Build platforms can be specified via a string specification. 339 /// 340 /// Specifications are build upon the following scheme, where each component 341 /// is optional (indicated by []), but the order is obligatory. 342 /// "[-platform][-architecture][-compiler]" 343 /// 344 /// So the following strings are valid specifications: 345 /// "-windows-x86-dmd" 346 /// "-dmd" 347 /// "-arm" 348 /// "-arm-dmd" 349 /// "-windows-dmd" 350 /// 351 /// Params: 352 /// specification = The specification being matched. It must be the empty string or start with a dash. 353 /// 354 /// Returns: 355 /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches) 356 /// 357 bool matchesSpecification(const(char)[] specification) const { 358 if (specification.empty) 359 return true; 360 auto splitted=specification.splitter('-'); 361 assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); 362 splitted.popFront(); // Drop leading empty match. 363 enforce(!splitted.empty, "Platform specification if present, must not be empty!"); 364 if (platform.canFind(splitted.front)) { 365 splitted.popFront(); 366 if(splitted.empty) 367 return true; 368 } 369 if (architecture.canFind(splitted.front)) { 370 splitted.popFront(); 371 if(splitted.empty) 372 return true; 373 } 374 if (compiler == splitted.front) { 375 splitted.popFront(); 376 enforce(splitted.empty, "No valid specification! The compiler has to be the last element!"); 377 return true; 378 } 379 return false; 380 } 381 unittest { 382 auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 383 assert(platform.matchesSpecification("-posix")); 384 assert(platform.matchesSpecification("-linux")); 385 assert(platform.matchesSpecification("-linux-dmd")); 386 assert(platform.matchesSpecification("-linux-x86_64-dmd")); 387 assert(platform.matchesSpecification("-x86_64")); 388 assert(!platform.matchesSpecification("-windows")); 389 assert(!platform.matchesSpecification("-ldc")); 390 assert(!platform.matchesSpecification("-windows-dmd")); 391 } 392 } 393 394 enum BuildSetting { 395 dflags = 1<<0, 396 lflags = 1<<1, 397 libs = 1<<2, 398 sourceFiles = 1<<3, 399 copyFiles = 1<<4, 400 versions = 1<<5, 401 debugVersions = 1<<6, 402 importPaths = 1<<7, 403 stringImportPaths = 1<<8, 404 options = 1<<9, 405 none = 0, 406 commandLine = dflags|copyFiles, 407 commandLineSeparate = commandLine|lflags, 408 all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options, 409 noOptions = all & ~options 410 } 411 412 enum TargetType { 413 autodetect, 414 none, 415 executable, 416 library, 417 sourceLibrary, 418 dynamicLibrary, 419 staticLibrary 420 } 421 422 enum BuildRequirements { 423 none = 0, /// No special requirements 424 allowWarnings = 1<<0, /// Warnings do not abort compilation 425 silenceWarnings = 1<<1, /// Don't show warnings 426 disallowDeprecations = 1<<2, /// Using deprecated features aborts compilation 427 silenceDeprecations = 1<<3, /// Don't show deprecation warnings 428 disallowInlining = 1<<4, /// Avoid function inlining, even in release builds 429 disallowOptimization = 1<<5, /// Avoid optimizations, even in release builds 430 requireBoundsCheck = 1<<6, /// Always perform bounds checks 431 requireContracts = 1<<7, /// Leave assertions and contracts enabled in release builds 432 relaxProperties = 1<<8, /// DEPRECATED: Do not enforce strict property handling (-property) 433 noDefaultFlags = 1<<9, /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes 434 } 435 436 enum BuildOptions { 437 none = 0, /// Use compiler defaults 438 debugMode = 1<<0, /// Compile in debug mode (enables contracts, -debug) 439 releaseMode = 1<<1, /// Compile in release mode (disables assertions and bounds checks, -release) 440 coverage = 1<<2, /// Enable code coverage analysis (-cov) 441 debugInfo = 1<<3, /// Enable symbolic debug information (-g) 442 debugInfoC = 1<<4, /// Enable symbolic debug information in C compatible form (-gc) 443 alwaysStackFrame = 1<<5, /// Always generate a stack frame (-gs) 444 stackStomping = 1<<6, /// Perform stack stomping (-gx) 445 inline = 1<<7, /// Perform function inlining (-inline) 446 noBoundsCheck = 1<<8, /// Disable all bounds checking (-noboundscheck) 447 optimize = 1<<9, /// Enable optimizations (-O) 448 profile = 1<<10, /// Emit profiling code (-profile) 449 unittests = 1<<11, /// Compile unit tests (-unittest) 450 verbose = 1<<12, /// Verbose compiler output (-v) 451 ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore) 452 syntaxOnly = 1<<14, /// Don't generate object files (-o-) 453 warnings = 1<<15, /// Enable warnings (-wi) 454 warningsAsErrors = 1<<16, /// Treat warnings as errors (-w) 455 ignoreDeprecations = 1<<17, /// Do not warn about using deprecated features (-d) 456 deprecationWarnings = 1<<18, /// Warn about using deprecated features (-dw) 457 deprecationErrors = 1<<19, /// Stop compilation upon usage of deprecated features (-de) 458 property = 1<<20, /// DEPRECATED: Enforce property syntax (-property) 459 } 460 461 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 462 { 463 assert(settings.targetName.length > 0, "No target name set."); 464 final switch (settings.targetType) { 465 case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); 466 case TargetType.none: return null; 467 case TargetType.sourceLibrary: return null; 468 case TargetType.executable: 469 if( platform.platform.canFind("windows") ) 470 return settings.targetName ~ ".exe"; 471 else return settings.targetName; 472 case TargetType.library: 473 case TargetType.staticLibrary: 474 if (platform.platform.canFind("windows") && platform.compiler == "dmd") 475 return settings.targetName ~ ".lib"; 476 else return "lib" ~ settings.targetName ~ ".a"; 477 case TargetType.dynamicLibrary: 478 if( platform.platform.canFind("windows") ) 479 return settings.targetName ~ ".dll"; 480 else return "lib" ~ settings.targetName ~ ".so"; 481 } 482 } 483 484 485 bool isLinkerFile(string f) 486 { 487 import std.path; 488 switch (extension(f)) { 489 default: 490 return false; 491 version (Windows) { 492 case ".lib", ".obj", ".res": 493 return true; 494 } else { 495 case ".a", ".o", ".so", ".dylib": 496 return true; 497 } 498 } 499 } 500 501 private { 502 Compiler[] s_compilers; 503 }