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 BuildOptions[] flags; 110 string alternative; 111 } 112 static immutable SpecialOption[] s_specialOptions = [ 113 {[BuildOptions.debugMode], "Call DUB with --build=debug"}, 114 {[BuildOptions.releaseMode], "Call DUB with --build=release"}, 115 {[BuildOptions.coverage], "Call DUB with --build=cov or --build=unittest-cov"}, 116 {[BuildOptions.debugInfo], "Call DUB with --build=debug"}, 117 {[BuildOptions.inline], "Call DUB with --build=release"}, 118 {[BuildOptions.noBoundsCheck], "Call DUB with --build=release-nobounds"}, 119 {[BuildOptions.optimize], "Call DUB with --build=release"}, 120 {[BuildOptions.profile], "Call DUB with --build=profile"}, 121 {[BuildOptions.unittests], "Call DUB with --build=unittest"}, 122 {[BuildOptions.warnings, BuildOptions.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, 123 {[BuildOptions.ignoreDeprecations, BuildOptions.deprecationWarnings, BuildOptions.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, 124 {[BuildOptions.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(BuildOptions.warningsAsErrors); 173 if (settings.requirements & BuildRequirements.allowWarnings) { settings.options &= ~BuildOptions.warningsAsErrors; settings.options |= BuildOptions.warnings; } 174 if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings); 175 if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; } 176 if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; } 177 if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= ~BuildOptions.inline; 178 if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize; 179 if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsCheck; 180 if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.releaseMode; 181 if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.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 string[] pkgconfig_libs; 207 foreach (lib; settings.libs) 208 if (execute([pkgconfig_bin, "--exists", "lib"~lib]).status == 0) 209 pkgconfig_libs ~= lib; 210 211 logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.map!(l => "lib"~l).array.join(", ")); 212 213 if (pkgconfig_libs.length) { 214 auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs.map!(l => "lib"~l)().array()); 215 enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output)); 216 foreach (f; libflags.output.split()) { 217 if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 218 else settings.addLFlags(f); 219 } 220 settings.libs = settings.libs.filter!(l => !pkgconfig_libs.canFind(l)).array; 221 } 222 if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", ")); 223 } catch (Exception e) { 224 logDiagnostic("pkg-config failed: %s", e.msg); 225 logDiagnostic("Falling back to direct -l... flags."); 226 } 227 } 228 } 229 230 231 interface Compiler { 232 @property string name() const; 233 234 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); 235 236 /// Replaces high level fields with low level fields and converts 237 /// dmd flags to compiler-specific flags 238 void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all) const; 239 240 /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. 241 void extractBuildOptions(ref BuildSettings settings) const; 242 243 /// Adds the appropriate flag to set a target path 244 void setTarget(ref BuildSettings settings, in BuildPlatform platform, string targetPath = null) const; 245 246 /// Invokes the compiler using the given flags 247 void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback); 248 249 /// Invokes the underlying linker directly 250 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback); 251 252 protected final void invokeTool(string[] args, void delegate(int, string) output_callback) 253 { 254 import std.string; 255 256 int status; 257 if (output_callback) { 258 auto result = executeShell(escapeShellCommand(args)); 259 output_callback(result.status, result.output); 260 status = result.status; 261 } else { 262 auto compiler_pid = spawnShell(escapeShellCommand(args)); 263 status = compiler_pid.wait(); 264 } 265 266 version (Posix) if (status == -9) { 267 throw new Exception(format("%s failed with exit code %s. This may indicate that the process has run out of memory.", 268 args[0], status)); 269 } 270 enforce(status == 0, format("%s failed with exit code %s.", args[0], status)); 271 } 272 } 273 274 275 /// Represents a platform a package can be build upon. 276 struct BuildPlatform { 277 /// e.g. ["posix", "windows"] 278 string[] platform; 279 /// e.g. ["x86", "x86_64"] 280 string[] architecture; 281 /// Canonical compiler name e.g. "dmd" 282 string compiler; 283 /// Compiler binary name e.g. "ldmd2" 284 string compilerBinary; 285 /// Compiled frontend version (e.g. 2065) 286 int frontendVersion; 287 288 enum any = BuildPlatform(null, null, null, null, -1); 289 290 /// Build platforms can be specified via a string specification. 291 /// 292 /// Specifications are build upon the following scheme, where each component 293 /// is optional (indicated by []), but the order is obligatory. 294 /// "[-platform][-architecture][-compiler]" 295 /// 296 /// So the following strings are valid specifications: 297 /// "-windows-x86-dmd" 298 /// "-dmd" 299 /// "-arm" 300 /// "-arm-dmd" 301 /// "-windows-dmd" 302 /// 303 /// Params: 304 /// specification = The specification being matched. It must be the empty string or start with a dash. 305 /// 306 /// Returns: 307 /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches) 308 /// 309 bool matchesSpecification(const(char)[] specification) 310 const { 311 if (specification.empty) return true; 312 if (this == any) return true; 313 314 auto splitted=specification.splitter('-'); 315 assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); 316 splitted.popFront(); // Drop leading empty match. 317 enforce(!splitted.empty, "Platform specification if present, must not be empty!"); 318 if (platform.canFind(splitted.front)) { 319 splitted.popFront(); 320 if(splitted.empty) 321 return true; 322 } 323 if (architecture.canFind(splitted.front)) { 324 splitted.popFront(); 325 if(splitted.empty) 326 return true; 327 } 328 if (compiler == splitted.front) { 329 splitted.popFront(); 330 enforce(splitted.empty, "No valid specification! The compiler has to be the last element!"); 331 return true; 332 } 333 return false; 334 } 335 unittest { 336 auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 337 assert(platform.matchesSpecification("-posix")); 338 assert(platform.matchesSpecification("-linux")); 339 assert(platform.matchesSpecification("-linux-dmd")); 340 assert(platform.matchesSpecification("-linux-x86_64-dmd")); 341 assert(platform.matchesSpecification("-x86_64")); 342 assert(!platform.matchesSpecification("-windows")); 343 assert(!platform.matchesSpecification("-ldc")); 344 assert(!platform.matchesSpecification("-windows-dmd")); 345 } 346 } 347 348 349 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 350 { 351 assert(settings.targetName.length > 0, "No target name set."); 352 final switch (settings.targetType) { 353 case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); 354 case TargetType.none: return null; 355 case TargetType.sourceLibrary: return null; 356 case TargetType.executable: 357 if( platform.platform.canFind("windows") ) 358 return settings.targetName ~ ".exe"; 359 else return settings.targetName; 360 case TargetType.library: 361 case TargetType.staticLibrary: 362 if (platform.platform.canFind("windows") && platform.compiler == "dmd") 363 return settings.targetName ~ ".lib"; 364 else return "lib" ~ settings.targetName ~ ".a"; 365 case TargetType.dynamicLibrary: 366 if( platform.platform.canFind("windows") ) 367 return settings.targetName ~ ".dll"; 368 else return "lib" ~ settings.targetName ~ ".so"; 369 case TargetType.object: 370 if (platform.platform.canFind("windows")) 371 return settings.targetName ~ ".obj"; 372 else return settings.targetName ~ ".o"; 373 } 374 } 375 376 377 bool isLinkerFile(string f) 378 { 379 import std.path; 380 switch (extension(f)) { 381 default: 382 return false; 383 version (Windows) { 384 case ".lib", ".obj", ".res", ".def": 385 return true; 386 } else { 387 case ".a", ".o", ".so", ".dylib": 388 return true; 389 } 390 } 391 } 392 393 /// Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...) 394 Path generatePlatformProbeFile() 395 { 396 import dub.internal.vibecompat.core.file; 397 import dub.internal.vibecompat.data.json; 398 import dub.internal.utils; 399 400 auto path = getTempFile("dub_platform_probe", ".d"); 401 402 auto fil = openFile(path, FileMode.CreateTrunc); 403 scope (failure) { 404 fil.close(); 405 } 406 407 fil.write(q{ 408 module dub_platform_probe; 409 410 template toString(int v) { enum toString = v.stringof; } 411 412 pragma(msg, `{`); 413 pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`); 414 pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); 415 pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`); 416 pragma(msg, ` "platform": [`); 417 pragma(msg, ` ` ~ determinePlatform()); 418 pragma(msg, ` ],`); 419 pragma(msg, ` "architecture": [`); 420 pragma(msg, ` ` ~ determineArchitecture()); 421 pragma(msg, ` ],`); 422 pragma(msg, `}`); 423 424 string determinePlatform() 425 { 426 string ret; 427 version(Windows) ret ~= `"windows", `; 428 version(linux) ret ~= `"linux", `; 429 version(Posix) ret ~= `"posix", `; 430 version(OSX) ret ~= `"osx", `; 431 version(FreeBSD) ret ~= `"freebsd", `; 432 version(OpenBSD) ret ~= `"openbsd", `; 433 version(NetBSD) ret ~= `"netbsd", `; 434 version(DragonFlyBSD) ret ~= `"dragonflybsd", `; 435 version(BSD) ret ~= `"bsd", `; 436 version(Solaris) ret ~= `"solaris", `; 437 version(AIX) ret ~= `"aix", `; 438 version(Haiku) ret ~= `"haiku", `; 439 version(SkyOS) ret ~= `"skyos", `; 440 version(SysV3) ret ~= `"sysv3", `; 441 version(SysV4) ret ~= `"sysv4", `; 442 version(Hurd) ret ~= `"hurd", `; 443 version(Android) ret ~= `"android", `; 444 version(Cygwin) ret ~= `"cygwin", `; 445 version(MinGW) ret ~= `"mingw", `; 446 return ret; 447 } 448 449 string determineArchitecture() 450 { 451 string ret; 452 version(X86) ret ~= `"x86", `; 453 version(X86_64) ret ~= `"x86_64", `; 454 version(ARM) ret ~= `"arm", `; 455 version(ARM_Thumb) ret ~= `"arm_thumb", `; 456 version(ARM_SoftFloat) ret ~= `"arm_softfloat", `; 457 version(ARM_HardFloat) ret ~= `"arm_hardfloat", `; 458 version(ARM64) ret ~= `"arm64", `; 459 version(PPC) ret ~= `"ppc", `; 460 version(PPC_SoftFP) ret ~= `"ppc_softfp", `; 461 version(PPC_HardFP) ret ~= `"ppc_hardfp", `; 462 version(PPC64) ret ~= `"ppc64", `; 463 version(IA64) ret ~= `"ia64", `; 464 version(MIPS) ret ~= `"mips", `; 465 version(MIPS32) ret ~= `"mips32", `; 466 version(MIPS64) ret ~= `"mips64", `; 467 version(MIPS_O32) ret ~= `"mips_o32", `; 468 version(MIPS_N32) ret ~= `"mips_n32", `; 469 version(MIPS_O64) ret ~= `"mips_o64", `; 470 version(MIPS_N64) ret ~= `"mips_n64", `; 471 version(MIPS_EABI) ret ~= `"mips_eabi", `; 472 version(MIPS_NoFloat) ret ~= `"mips_nofloat", `; 473 version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `; 474 version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `; 475 version(SPARC) ret ~= `"sparc", `; 476 version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `; 477 version(SPARC_SoftFP) ret ~= `"sparc_softfp", `; 478 version(SPARC_HardFP) ret ~= `"sparc_hardfp", `; 479 version(SPARC64) ret ~= `"sparc64", `; 480 version(S390) ret ~= `"s390", `; 481 version(S390X) ret ~= `"s390x", `; 482 version(HPPA) ret ~= `"hppa", `; 483 version(HPPA64) ret ~= `"hppa64", `; 484 version(SH) ret ~= `"sh", `; 485 version(SH64) ret ~= `"sh64", `; 486 version(Alpha) ret ~= `"alpha", `; 487 version(Alpha_SoftFP) ret ~= `"alpha_softfp", `; 488 version(Alpha_HardFP) ret ~= `"alpha_hardfp", `; 489 return ret; 490 } 491 492 string determineCompiler() 493 { 494 version(DigitalMars) return "dmd"; 495 else version(GNU) return "gdc"; 496 else version(LDC) return "ldc"; 497 else version(SDC) return "sdc"; 498 else return null; 499 } 500 }); 501 502 fil.close(); 503 504 return path; 505 } 506 507 BuildPlatform readPlatformProbe(string output) 508 { 509 import std.string; 510 511 // work around possible additional output of the compiler 512 auto idx1 = output.indexOf("{"); 513 auto idx2 = output.lastIndexOf("}"); 514 enforce(idx1 >= 0 && idx1 < idx2, 515 "Unexpected platform information output - does not contain a JSON object."); 516 output = output[idx1 .. idx2+1]; 517 518 import dub.internal.vibecompat.data.json; 519 auto json = parseJsonString(output); 520 521 BuildPlatform build_platform; 522 build_platform.platform = json.platform.get!(Json[]).map!(e => e.get!string()).array(); 523 build_platform.architecture = json.architecture.get!(Json[]).map!(e => e.get!string()).array(); 524 build_platform.compiler = json.compiler.get!string; 525 build_platform.frontendVersion = json.frontendVersion.get!int; 526 return build_platform; 527 } 528 529 private { 530 Compiler[] s_compilers; 531 }