1 /** 2 Compiler settings and abstraction. 3 4 Copyright: © 2013-2014 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 public import dub.compilers.buildsettings; 11 12 import dub.compilers.dmd; 13 import dub.compilers.gdc; 14 import dub.compilers.ldc; 15 import dub.internal.vibecompat.core.file; 16 import dub.internal.vibecompat.core.log; 17 import dub.internal.vibecompat.data.json; 18 import dub.internal.vibecompat.inet.path; 19 20 import std.algorithm; 21 import std.array; 22 import std.conv; 23 import std.exception; 24 import std.process; 25 26 27 static this() 28 { 29 registerCompiler(new DmdCompiler); 30 registerCompiler(new GdcCompiler); 31 registerCompiler(new LdcCompiler); 32 } 33 34 Compiler getCompiler(string name) 35 { 36 foreach (c; s_compilers) 37 if (c.name == name) 38 return c; 39 40 // try to match names like gdmd or gdc-2.61 41 if (name.canFind("dmd")) return getCompiler("dmd"); 42 if (name.canFind("gdc")) return getCompiler("gdc"); 43 if (name.canFind("ldc")) return getCompiler("ldc"); 44 45 throw new Exception("Unknown compiler: "~name); 46 } 47 48 string defaultCompiler() 49 { 50 static string name; 51 if (!name.length) name = findCompiler(); 52 return name; 53 } 54 55 private string findCompiler() 56 { 57 import std.process : env=environment; 58 import dub.version_ : initialCompilerBinary; 59 version (Windows) enum sep = ";", exe = ".exe"; 60 version (Posix) enum sep = ":", exe = ""; 61 62 auto def = Path(initialCompilerBinary); 63 if (def.absolute && existsFile(def)) 64 return initialCompilerBinary; 65 66 auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; 67 if (!def.absolute) 68 compilers = initialCompilerBinary ~ compilers; 69 70 auto paths = env.get("PATH", "").splitter(sep).map!Path; 71 auto res = compilers.find!(bin => paths.canFind!(p => existsFile(p ~ (bin~exe)))); 72 return res.empty ? initialCompilerBinary : res.front; 73 } 74 75 void registerCompiler(Compiler c) 76 { 77 s_compilers ~= c; 78 } 79 80 void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name) 81 { 82 struct SpecialFlag { 83 string[] flags; 84 string alternative; 85 } 86 static immutable SpecialFlag[] s_specialFlags = [ 87 {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"}, 88 {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, 89 {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, 90 {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, 91 {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, 92 {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, 93 {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, 94 {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, 95 {["-unittest", "-funittest"], "Call dub with --build=unittest"}, 96 {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, 97 {["-D"], "Call dub with --build=docs or --build=ddox"}, 98 {["-X"], "Call dub with --build=ddox"}, 99 {["-cov"], "Call dub with --build=cov or --build=unittest-cov"}, 100 {["-profile"], "Call dub with --build=profile"}, 101 {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, 102 {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, 103 {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, 104 {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, 105 {["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`} 106 ]; 107 108 struct SpecialOption { 109 BuildOption[] flags; 110 string alternative; 111 } 112 static immutable SpecialOption[] s_specialOptions = [ 113 {[BuildOption.debugMode], "Call DUB with --build=debug"}, 114 {[BuildOption.releaseMode], "Call DUB with --build=release"}, 115 {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"}, 116 {[BuildOption.debugInfo], "Call DUB with --build=debug"}, 117 {[BuildOption.inline], "Call DUB with --build=release"}, 118 {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"}, 119 {[BuildOption.optimize], "Call DUB with --build=release"}, 120 {[BuildOption.profile], "Call DUB with --build=profile"}, 121 {[BuildOption.unittests], "Call DUB with --build=unittest"}, 122 {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, 123 {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, 124 {[BuildOption.property], "This flag is deprecated and has no effect"} 125 ]; 126 127 bool got_preamble = false; 128 void outputPreamble() 129 { 130 if (got_preamble) return; 131 got_preamble = true; 132 logWarn(""); 133 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 134 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 135 logWarn(""); 136 logWarn("The following compiler flags have been specified in the package description"); 137 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 138 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 139 logWarn("to the compiler, or use one of the suggestions below:"); 140 logWarn(""); 141 } 142 143 foreach (f; compiler_flags) { 144 foreach (sf; s_specialFlags) { 145 if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 146 outputPreamble(); 147 logWarn("%s: %s", f, sf.alternative); 148 break; 149 } 150 } 151 } 152 153 foreach (sf; s_specialOptions) { 154 foreach (f; sf.flags) { 155 if (options & f) { 156 outputPreamble(); 157 logWarn("%s: %s", f, sf.alternative); 158 break; 159 } 160 } 161 } 162 163 if (got_preamble) logWarn(""); 164 } 165 166 167 /** 168 Alters the build options to comply with the specified build requirements. 169 */ 170 void enforceBuildRequirements(ref BuildSettings settings) 171 { 172 settings.addOptions(BuildOption.warningsAsErrors); 173 if (settings.requirements & BuildRequirement.allowWarnings) { settings.options &= ~BuildOption.warningsAsErrors; settings.options |= BuildOption.warnings; } 174 if (settings.requirements & BuildRequirement.silenceWarnings) settings.options &= ~(BuildOption.warningsAsErrors|BuildOption.warnings); 175 if (settings.requirements & BuildRequirement.disallowDeprecations) { settings.options &= ~(BuildOption.ignoreDeprecations|BuildOption.deprecationWarnings); settings.options |= BuildOption.deprecationErrors; } 176 if (settings.requirements & BuildRequirement.silenceDeprecations) { settings.options &= ~(BuildOption.deprecationErrors|BuildOption.deprecationWarnings); settings.options |= BuildOption.ignoreDeprecations; } 177 if (settings.requirements & BuildRequirement.disallowInlining) settings.options &= ~BuildOption.inline; 178 if (settings.requirements & BuildRequirement.disallowOptimization) settings.options &= ~BuildOption.optimize; 179 if (settings.requirements & BuildRequirement.requireBoundsCheck) settings.options &= ~BuildOption.noBoundsCheck; 180 if (settings.requirements & BuildRequirement.requireContracts) settings.options &= ~BuildOption.releaseMode; 181 if (settings.requirements & BuildRequirement.relaxProperties) settings.options &= ~BuildOption.property; 182 } 183 184 185 /** 186 Replaces each referenced import library by the appropriate linker flags. 187 188 This function tries to invoke "pkg-config" if possible and falls back to 189 direct flag translation if that fails. 190 */ 191 void resolveLibs(ref BuildSettings settings) 192 { 193 import std..string : format; 194 195 if (settings.libs.length == 0) return; 196 197 if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { 198 logDiagnostic("Ignoring all import libraries for static library build."); 199 settings.libs = null; 200 version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; 201 } 202 203 version (Posix) { 204 try { 205 enum pkgconfig_bin = "pkg-config"; 206 207 bool exists(string lib) { 208 return execute([pkgconfig_bin, "--exists", lib]).status == 0; 209 } 210 211 auto pkgconfig_libs = settings.libs.partition!(l => !exists(l)); 212 pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length] 213 .partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array; 214 settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length]; 215 216 if (pkgconfig_libs.length) { 217 logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", ")); 218 auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs); 219 enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output)); 220 foreach (f; libflags.output.split()) { 221 if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 222 else settings.addLFlags(f); 223 } 224 } 225 if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", ")); 226 } catch (Exception e) { 227 logDiagnostic("pkg-config failed: %s", e.msg); 228 logDiagnostic("Falling back to direct -l... flags."); 229 } 230 } 231 } 232 233 234 interface Compiler { 235 @property string name() const; 236 237 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); 238 239 /// Replaces high level fields with low level fields and converts 240 /// dmd flags to compiler-specific flags 241 void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const; 242 243 /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. 244 void extractBuildOptions(ref BuildSettings settings) const; 245 246 /// Adds the appropriate flag to set a target path 247 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const; 248 249 /// Invokes the compiler using the given flags 250 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback); 251 252 /// Invokes the underlying linker directly 253 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback); 254 255 protected final void invokeTool(string[] args, void delegate(int, string) output_callback) 256 { 257 import std..string; 258 259 int status; 260 if (output_callback) { 261 auto result = executeShell(escapeShellCommand(args)); 262 output_callback(result.status, result.output); 263 status = result.status; 264 } else { 265 auto compiler_pid = spawnShell(escapeShellCommand(args)); 266 status = compiler_pid.wait(); 267 } 268 269 version (Posix) if (status == -9) { 270 throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory.", 271 args[0], status)); 272 } 273 enforce(status == 0, format("%s failed with exit code %s.", args[0], status)); 274 } 275 } 276 277 278 /// Represents a platform a package can be build upon. 279 struct BuildPlatform { 280 /// e.g. ["posix", "windows"] 281 string[] platform; 282 /// e.g. ["x86", "x86_64"] 283 string[] architecture; 284 /// Canonical compiler name e.g. "dmd" 285 string compiler; 286 /// Compiler binary name e.g. "ldmd2" 287 string compilerBinary; 288 /// Compiled frontend version (e.g. 2065) 289 int frontendVersion; 290 291 enum any = BuildPlatform(null, null, null, null, -1); 292 293 /// Build platforms can be specified via a string specification. 294 /// 295 /// Specifications are build upon the following scheme, where each component 296 /// is optional (indicated by []), but the order is obligatory. 297 /// "[-platform][-architecture][-compiler]" 298 /// 299 /// So the following strings are valid specifications: 300 /// "-windows-x86-dmd" 301 /// "-dmd" 302 /// "-arm" 303 /// "-arm-dmd" 304 /// "-windows-dmd" 305 /// 306 /// Params: 307 /// specification = The specification being matched. It must be the empty string or start with a dash. 308 /// 309 /// Returns: 310 /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches) 311 /// 312 bool matchesSpecification(const(char)[] specification) 313 const { 314 if (specification.empty) return true; 315 if (this == any) return true; 316 317 auto splitted=specification.splitter('-'); 318 assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); 319 splitted.popFront(); // Drop leading empty match. 320 enforce(!splitted.empty, "Platform specification if present, must not be empty!"); 321 if (platform.canFind(splitted.front)) { 322 splitted.popFront(); 323 if(splitted.empty) 324 return true; 325 } 326 if (architecture.canFind(splitted.front)) { 327 splitted.popFront(); 328 if(splitted.empty) 329 return true; 330 } 331 if (compiler == splitted.front) { 332 splitted.popFront(); 333 enforce(splitted.empty, "No valid specification! The compiler has to be the last element!"); 334 return true; 335 } 336 return false; 337 } 338 unittest { 339 auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 340 assert(platform.matchesSpecification("-posix")); 341 assert(platform.matchesSpecification("-linux")); 342 assert(platform.matchesSpecification("-linux-dmd")); 343 assert(platform.matchesSpecification("-linux-x86_64-dmd")); 344 assert(platform.matchesSpecification("-x86_64")); 345 assert(!platform.matchesSpecification("-windows")); 346 assert(!platform.matchesSpecification("-ldc")); 347 assert(!platform.matchesSpecification("-windows-dmd")); 348 } 349 } 350 351 352 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 353 { 354 assert(settings.targetName.length > 0, "No target name set."); 355 final switch (settings.targetType) { 356 case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); 357 case TargetType.none: return null; 358 case TargetType.sourceLibrary: return null; 359 case TargetType.executable: 360 if( platform.platform.canFind("windows") ) 361 return settings.targetName ~ ".exe"; 362 else return settings.targetName; 363 case TargetType.library: 364 case TargetType.staticLibrary: 365 if (platform.platform.canFind("windows") && platform.compiler == "dmd") 366 return settings.targetName ~ ".lib"; 367 else return "lib" ~ settings.targetName ~ ".a"; 368 case TargetType.dynamicLibrary: 369 if( platform.platform.canFind("windows") ) 370 return settings.targetName ~ ".dll"; 371 else return "lib" ~ settings.targetName ~ ".so"; 372 case TargetType.object: 373 if (platform.platform.canFind("windows")) 374 return settings.targetName ~ ".obj"; 375 else return settings.targetName ~ ".o"; 376 } 377 } 378 379 380 bool isLinkerFile(string f) 381 { 382 import std.path; 383 switch (extension(f)) { 384 default: 385 return false; 386 version (Windows) { 387 case ".lib", ".obj", ".res", ".def": 388 return true; 389 } else { 390 case ".a", ".o", ".so", ".dylib": 391 return true; 392 } 393 } 394 } 395 396 /// Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...) 397 Path generatePlatformProbeFile() 398 { 399 import dub.internal.vibecompat.core.file; 400 import dub.internal.vibecompat.data.json; 401 import dub.internal.utils; 402 403 auto path = getTempFile("dub_platform_probe", ".d"); 404 405 auto fil = openFile(path, FileMode.CreateTrunc); 406 scope (failure) { 407 fil.close(); 408 } 409 410 fil.write(q{ 411 module dub_platform_probe; 412 413 template toString(int v) { enum toString = v.stringof; } 414 415 pragma(msg, `{`); 416 pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`); 417 pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); 418 pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`); 419 pragma(msg, ` "platform": [`); 420 pragma(msg, ` ` ~ determinePlatform()); 421 pragma(msg, ` ],`); 422 pragma(msg, ` "architecture": [`); 423 pragma(msg, ` ` ~ determineArchitecture()); 424 pragma(msg, ` ],`); 425 pragma(msg, `}`); 426 427 string determinePlatform() 428 { 429 string ret; 430 version(Windows) ret ~= `"windows", `; 431 version(linux) ret ~= `"linux", `; 432 version(Posix) ret ~= `"posix", `; 433 version(OSX) ret ~= `"osx", `; 434 version(FreeBSD) ret ~= `"freebsd", `; 435 version(OpenBSD) ret ~= `"openbsd", `; 436 version(NetBSD) ret ~= `"netbsd", `; 437 version(DragonFlyBSD) ret ~= `"dragonflybsd", `; 438 version(BSD) ret ~= `"bsd", `; 439 version(Solaris) ret ~= `"solaris", `; 440 version(AIX) ret ~= `"aix", `; 441 version(Haiku) ret ~= `"haiku", `; 442 version(SkyOS) ret ~= `"skyos", `; 443 version(SysV3) ret ~= `"sysv3", `; 444 version(SysV4) ret ~= `"sysv4", `; 445 version(Hurd) ret ~= `"hurd", `; 446 version(Android) ret ~= `"android", `; 447 version(Cygwin) ret ~= `"cygwin", `; 448 version(MinGW) ret ~= `"mingw", `; 449 return ret; 450 } 451 452 string determineArchitecture() 453 { 454 string ret; 455 version(X86) ret ~= `"x86", `; 456 version(X86_64) ret ~= `"x86_64", `; 457 version(ARM) ret ~= `"arm", `; 458 version(ARM_Thumb) ret ~= `"arm_thumb", `; 459 version(ARM_SoftFloat) ret ~= `"arm_softfloat", `; 460 version(ARM_HardFloat) ret ~= `"arm_hardfloat", `; 461 version(ARM64) ret ~= `"arm64", `; 462 version(PPC) ret ~= `"ppc", `; 463 version(PPC_SoftFP) ret ~= `"ppc_softfp", `; 464 version(PPC_HardFP) ret ~= `"ppc_hardfp", `; 465 version(PPC64) ret ~= `"ppc64", `; 466 version(IA64) ret ~= `"ia64", `; 467 version(MIPS) ret ~= `"mips", `; 468 version(MIPS32) ret ~= `"mips32", `; 469 version(MIPS64) ret ~= `"mips64", `; 470 version(MIPS_O32) ret ~= `"mips_o32", `; 471 version(MIPS_N32) ret ~= `"mips_n32", `; 472 version(MIPS_O64) ret ~= `"mips_o64", `; 473 version(MIPS_N64) ret ~= `"mips_n64", `; 474 version(MIPS_EABI) ret ~= `"mips_eabi", `; 475 version(MIPS_NoFloat) ret ~= `"mips_nofloat", `; 476 version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `; 477 version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `; 478 version(SPARC) ret ~= `"sparc", `; 479 version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `; 480 version(SPARC_SoftFP) ret ~= `"sparc_softfp", `; 481 version(SPARC_HardFP) ret ~= `"sparc_hardfp", `; 482 version(SPARC64) ret ~= `"sparc64", `; 483 version(S390) ret ~= `"s390", `; 484 version(S390X) ret ~= `"s390x", `; 485 version(HPPA) ret ~= `"hppa", `; 486 version(HPPA64) ret ~= `"hppa64", `; 487 version(SH) ret ~= `"sh", `; 488 version(SH64) ret ~= `"sh64", `; 489 version(Alpha) ret ~= `"alpha", `; 490 version(Alpha_SoftFP) ret ~= `"alpha_softfp", `; 491 version(Alpha_HardFP) ret ~= `"alpha_hardfp", `; 492 return ret; 493 } 494 495 string determineCompiler() 496 { 497 version(DigitalMars) return "dmd"; 498 else version(GNU) return "gdc"; 499 else version(LDC) return "ldc"; 500 else version(SDC) return "sdc"; 501 else return null; 502 } 503 }); 504 505 fil.close(); 506 507 return path; 508 } 509 510 BuildPlatform readPlatformProbe(string output) 511 { 512 import std..string; 513 514 // work around possible additional output of the compiler 515 auto idx1 = output.indexOf("{"); 516 auto idx2 = output.lastIndexOf("}"); 517 enforce(idx1 >= 0 && idx1 < idx2, 518 "Unexpected platform information output - does not contain a JSON object."); 519 output = output[idx1 .. idx2+1]; 520 521 import dub.internal.vibecompat.data.json; 522 auto json = parseJsonString(output); 523 524 BuildPlatform build_platform; 525 build_platform.platform = json.platform.get!(Json[]).map!(e => e.get!string()).array(); 526 build_platform.architecture = json.architecture.get!(Json[]).map!(e => e.get!string()).array(); 527 build_platform.compiler = json.compiler.get!string; 528 build_platform.frontendVersion = json.frontendVersion.get!int; 529 return build_platform; 530 } 531 532 private { 533 Compiler[] s_compilers; 534 }