1 /** 2 Utility functionality for compiler class implementations. 3 4 Copyright: © 2013-2016 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.utils; 9 10 import dub.compilers.buildsettings; 11 import dub.platform : BuildPlatform, archCheck, compilerCheckPragmas, platformCheck, pragmaGen; 12 import dub.internal.vibecompat.inet.path; 13 import dub.internal.logging; 14 15 import std.algorithm : canFind, endsWith, filter; 16 17 /** 18 Alters the build options to comply with the specified build requirements. 19 20 And enabled options that do not comply will get disabled. 21 */ 22 void enforceBuildRequirements(ref BuildSettings settings) 23 { 24 settings.addOptions(BuildOption.warningsAsErrors); 25 if (settings.requirements & BuildRequirement.allowWarnings) { settings.options &= ~BuildOption.warningsAsErrors; settings.options |= BuildOption.warnings; } 26 if (settings.requirements & BuildRequirement.silenceWarnings) settings.options &= ~(BuildOption.warningsAsErrors|BuildOption.warnings); 27 if (settings.requirements & BuildRequirement.disallowDeprecations) { settings.options &= ~(BuildOption.ignoreDeprecations|BuildOption.deprecationWarnings); settings.options |= BuildOption.deprecationErrors; } 28 if (settings.requirements & BuildRequirement.silenceDeprecations) { settings.options &= ~(BuildOption.deprecationErrors|BuildOption.deprecationWarnings); settings.options |= BuildOption.ignoreDeprecations; } 29 if (settings.requirements & BuildRequirement.disallowInlining) settings.options &= ~BuildOption.inline; 30 if (settings.requirements & BuildRequirement.disallowOptimization) settings.options &= ~BuildOption.optimize; 31 if (settings.requirements & BuildRequirement.requireBoundsCheck) settings.options &= ~BuildOption.noBoundsCheck; 32 if (settings.requirements & BuildRequirement.requireContracts) settings.options &= ~BuildOption.releaseMode; 33 if (settings.requirements & BuildRequirement.relaxProperties) settings.options &= ~BuildOption.property; 34 } 35 36 37 /** 38 Determines if a specific file name has the extension of a linker file. 39 40 Linker files include static/dynamic libraries, resource files, object files 41 and DLL definition files. 42 */ 43 bool isLinkerFile(const scope ref BuildPlatform platform, string f) 44 { 45 import std.path; 46 switch (extension(f)) { 47 default: 48 return false; 49 case ".lib", ".obj", ".res", ".def": 50 return platform.isWindows(); 51 case ".a", ".o", ".so", ".dylib": 52 return !platform.isWindows(); 53 } 54 } 55 56 unittest { 57 BuildPlatform p; 58 59 p.platform = ["windows"]; 60 assert(isLinkerFile(p, "test.obj")); 61 assert(isLinkerFile(p, "test.lib")); 62 assert(isLinkerFile(p, "test.res")); 63 assert(!isLinkerFile(p, "test.o")); 64 assert(!isLinkerFile(p, "test.d")); 65 66 p.platform = ["something else"]; 67 assert(isLinkerFile(p, "test.o")); 68 assert(isLinkerFile(p, "test.a")); 69 assert(isLinkerFile(p, "test.so")); 70 assert(isLinkerFile(p, "test.dylib")); 71 assert(!isLinkerFile(p, "test.obj")); 72 assert(!isLinkerFile(p, "test.d")); 73 } 74 75 76 /** 77 Adds a default DT_SONAME (ELF) / 'install name' (Mach-O) when linking a dynamic library. 78 This makes dependees reference their dynamic-lib deps by filename only (DT_NEEDED etc.) 79 instead of by the path used in the dependee linker cmdline, and enables loading the 80 deps from the dependee's output directory - either by setting the LD_LIBRARY_PATH 81 environment variable, or baking an rpath into the executable. 82 */ 83 package void addDynamicLibName(ref BuildSettings settings, in BuildPlatform platform, string fileName) 84 { 85 if (!platform.isWindows()) { 86 // *pre*pend to allow the user to override it 87 if (platform.platform.canFind("darwin")) 88 settings.prependLFlags("-install_name", "@rpath/" ~ fileName); 89 else 90 settings.prependLFlags("-soname", fileName); 91 } 92 } 93 94 95 /** 96 Replaces each referenced import library by the appropriate linker flags. 97 98 This function tries to invoke "pkg-config" if possible and falls back to 99 direct flag translation if that fails. 100 */ 101 void resolveLibs(ref BuildSettings settings, const scope ref BuildPlatform platform) 102 { 103 import std.string : format; 104 import std.array : array; 105 106 if (settings.libs.length == 0) return; 107 108 if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { 109 logDiagnostic("Ignoring all import libraries for static library build."); 110 settings.libs = null; 111 if (platform.isWindows()) 112 settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; 113 } 114 115 version (Posix) { 116 import std.algorithm : any, map, partition, startsWith; 117 import std.array : array, join, split; 118 import std.exception : enforce; 119 import std.process : execute; 120 121 try { 122 enum pkgconfig_bin = "pkg-config"; 123 124 bool exists(string lib) { 125 return execute([pkgconfig_bin, "--exists", lib]).status == 0; 126 } 127 128 auto pkgconfig_libs = settings.libs.partition!(l => !exists(l)); 129 pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length] 130 .partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array; 131 settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length]; 132 133 if (pkgconfig_libs.length) { 134 logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", ")); 135 auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs); 136 enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output)); 137 foreach (f; libflags.output.split()) { 138 if (f.startsWith("-L-L")) { 139 settings.addLFlags(f[2 .. $]); 140 } else if (f.startsWith("-defaultlib")) { 141 settings.addDFlags(f); 142 } else if (f.startsWith("-L-defaultlib")) { 143 settings.addDFlags(f[2 .. $]); 144 } else if (f.startsWith("-pthread")) { 145 settings.addLFlags("-lpthread"); 146 } else if (f.startsWith("-L-l")) { 147 settings.addLFlags(f[2 .. $].split(",")); 148 } else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 149 else settings.addLFlags(f); 150 } 151 152 // Also extract C preprocessor flags from pkg-config --cflags 153 auto cflags = execute([pkgconfig_bin, "--cflags"] ~ pkgconfig_libs); 154 if (cflags.status == 0) { 155 logDiagnostic("Using pkg-config to resolve C preprocessor flags for %s.", pkgconfig_libs.join(", ")); 156 applyPkgConfigCFlags(settings, cflags.output); 157 } 158 } 159 if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", ")); 160 } catch (Exception e) { 161 logWarn("'pkg-config' does not seem to be installed or functional as it failed with: %s", e.msg); 162 logWarn("'dub' will automatically fall back to directly using '-l' flags, " ~ 163 "but this is error prone. Please install 'pkg-config', or use " ~ 164 "'lflags' directly."); 165 } 166 } 167 } 168 169 170 /** 171 Parses pkg-config --cflags output and applies C preprocessor flags to build settings. 172 173 All C preprocessor flags (-I, -D, -U, -include, -isystem, -idirafter) are passed 174 to the D compiler via -P prefix (for DMD/LDC) so they get forwarded to the C preprocessor. 175 This is called from resolveLibs() which runs after cImportPaths are already processed, 176 so we add directly to dflags with the -P prefix. 177 */ 178 package void applyPkgConfigCFlags(ref BuildSettings settings, string cflagsOutput) 179 { 180 import std.algorithm : splitter, startsWith; 181 182 // Note: We split by whitespace for consistency with --libs parsing above. 183 // This won't handle shell-quoted paths with spaces, but neither does --libs. 184 foreach (f; cflagsOutput.splitter()) { 185 if (f.startsWith("-I") || f.startsWith("-D") || f.startsWith("-U") || 186 f.startsWith("-include") || f.startsWith("-isystem") || 187 f.startsWith("-idirafter")) { 188 // Pass all C preprocessor flags via -P (for DMD/LDC) 189 settings.addDFlags("-P" ~ f); 190 } 191 } 192 } 193 194 unittest { 195 BuildSettings settings; 196 197 // Test -I flag extraction (passed via -P prefix) 198 applyPkgConfigCFlags(settings, "-I/usr/include/foo -I/usr/include/bar"); 199 assert(settings.dflags == ["-P-I/usr/include/foo", "-P-I/usr/include/bar"]); 200 201 // Test preprocessor define flags 202 settings = BuildSettings.init; 203 applyPkgConfigCFlags(settings, "-DFOO=1 -DBAR -UBAZ"); 204 assert(settings.dflags == ["-P-DFOO=1", "-P-DBAR", "-P-UBAZ"]); 205 206 // Test mixed flags 207 settings = BuildSettings.init; 208 applyPkgConfigCFlags(settings, "-I/usr/include -DVERSION=2 -isystem/usr/local/include"); 209 assert(settings.dflags == ["-P-I/usr/include", "-P-DVERSION=2", "-P-isystem/usr/local/include"]); 210 211 // Test -idirafter flag (attached path) 212 settings = BuildSettings.init; 213 applyPkgConfigCFlags(settings, "-idirafter/fallback/include"); 214 assert(settings.dflags == ["-P-idirafter/fallback/include"]); 215 216 // Note: -include with space-separated argument (e.g., "-include config.h") is not 217 // fully supported - the flag is passed but the argument may be lost if space-separated. 218 // Most pkg-config files use attached arguments (e.g., -I/path, -DFOO). 219 220 // Test empty input 221 settings = BuildSettings.init; 222 applyPkgConfigCFlags(settings, ""); 223 assert(settings.dflags.length == 0); 224 225 // Test unknown flags are ignored (e.g., -pthread which is for linker) 226 settings = BuildSettings.init; 227 applyPkgConfigCFlags(settings, "-I/inc -pthread -Wall"); 228 assert(settings.dflags == ["-P-I/inc"]); 229 } 230 231 232 /** Searches the given list of compiler flags for ones that have a generic 233 equivalent. 234 235 Certain compiler flags should, instead of using compiler-specific syntax, 236 be specified as build options (`BuildOption`) or built requirements 237 (`BuildRequirements`). This function will output warning messages to 238 assist the user in making the best choice. 239 */ 240 void warnOnSpecialCompilerFlags(string[] compiler_flags, Flags!BuildOption options, string package_name, string config_name) 241 { 242 import std.algorithm : any, endsWith, startsWith; 243 import std.range : empty; 244 245 struct SpecialFlag { 246 string[] flags; 247 string alternative; 248 } 249 static immutable SpecialFlag[] s_specialFlags = [ 250 {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"}, 251 {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, 252 {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, 253 {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, 254 {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, 255 {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, 256 {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, 257 {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, 258 {["-unittest", "-funittest"], "Call dub with --build=unittest"}, 259 {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, 260 {["-D"], "Call dub with --build=docs or --build=ddox"}, 261 {["-X"], "Call dub with --build=ddox"}, 262 {["-cov"], "Call dub with --build=cov or --build=unittest-cov"}, 263 {["-cov=ctfe"], "Call dub with --build=cov-ctfe or --build=unittest-cov-ctfe"}, 264 {["-profile"], "Call dub with --build=profile"}, 265 {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, 266 {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, 267 {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, 268 {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, 269 {["-m32", "-m64", "-m32mscoff"], `Use --arch=x86/--arch=x86_64/--arch=x86_mscoff to specify the target architecture, e.g. 'dub build --arch=x86_64'`} 270 ]; 271 272 struct SpecialOption { 273 BuildOption[] flags; 274 string alternative; 275 } 276 static immutable SpecialOption[] s_specialOptions = [ 277 {[BuildOption.debugMode], "Call DUB with --build=debug"}, 278 {[BuildOption.releaseMode], "Call DUB with --build=release"}, 279 {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"}, 280 {[BuildOption.coverageCTFE], "Call DUB with --build=cov-ctfe or --build=unittest-cov-ctfe"}, 281 {[BuildOption.debugInfo], "Call DUB with --build=debug"}, 282 {[BuildOption.inline], "Call DUB with --build=release"}, 283 {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"}, 284 {[BuildOption.optimize], "Call DUB with --build=release"}, 285 {[BuildOption.profile], "Call DUB with --build=profile"}, 286 {[BuildOption.unittests], "Call DUB with --build=unittest"}, 287 {[BuildOption.syntaxOnly], "Call DUB with --build=syntax"}, 288 {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, 289 {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, 290 {[BuildOption.property], "This flag is deprecated and has no effect"} 291 ]; 292 293 bool got_preamble = false; 294 void outputPreamble() 295 { 296 if (got_preamble) return; 297 got_preamble = true; 298 logWarn(""); 299 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 300 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 301 logWarn(""); 302 logWarn("The following compiler flags have been specified in the package description"); 303 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 304 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 305 logWarn("to the compiler, or use one of the suggestions below:"); 306 logWarn(""); 307 } 308 309 foreach (f; compiler_flags) { 310 foreach (sf; s_specialFlags) { 311 if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 312 outputPreamble(); 313 logWarn("%s: %s", f, sf.alternative); 314 break; 315 } 316 } 317 } 318 319 foreach (sf; s_specialOptions) { 320 foreach (f; sf.flags) { 321 if (options & f) { 322 outputPreamble(); 323 logWarn("%s: %s", f, sf.alternative); 324 break; 325 } 326 } 327 } 328 329 if (got_preamble) logWarn(""); 330 } 331 332 private enum probeBeginMark = "__dub_probe_begin__"; 333 private enum probeEndMark = "__dub_probe_end__"; 334 335 /** 336 Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...) 337 338 See_Also: `readPlatformProbe` 339 */ 340 NativePath generatePlatformProbeFile() 341 { 342 import dub.internal.vibecompat.core.file; 343 import dub.internal.utils; 344 import std.string : format; 345 346 enum moduleInfo = q{ 347 module object; 348 alias string = const(char)[]; 349 }; 350 351 // avoid druntime so that this compiles without a compiler's builtin object.d 352 enum probe = q{ 353 %1$s 354 pragma(msg, `%2$s`); 355 pragma(msg, `\n`); 356 pragma(msg, `compiler`); 357 %6$s 358 pragma(msg, `\n`); 359 pragma(msg, `frontendVersion "`); 360 pragma(msg, __VERSION__.stringof); 361 pragma(msg, `"\n`); 362 pragma(msg, `compilerVendor "`); 363 pragma(msg, __VENDOR__); 364 pragma(msg, `"\n`); 365 pragma(msg, `platform`); 366 %4$s 367 pragma(msg, `\n`); 368 pragma(msg, `architecture `); 369 %5$s 370 pragma(msg, `\n`); 371 pragma(msg, `%3$s`); 372 }.format(moduleInfo, probeBeginMark, probeEndMark, pragmaGen(platformCheck), pragmaGen(archCheck), compilerCheckPragmas); 373 374 auto path = getTempFile("dub_platform_probe", ".d"); 375 writeFile(path, probe); 376 return path; 377 } 378 379 380 /** 381 Processes the SDL output generated by compiling the platform probe file. 382 383 See_Also: `generatePlatformProbeFile`. 384 */ 385 BuildPlatform readPlatformSDLProbe(string output) 386 { 387 import std.algorithm : map, max, splitter, joiner, count, filter; 388 import std.array : array; 389 import std.exception : enforce; 390 import std.range : front; 391 import std.ascii : newline; 392 import std.string; 393 import dub.internal.sdlang.parser; 394 import dub.internal.sdlang.ast; 395 import std.conv; 396 397 // work around possible additional output of the compiler 398 auto idx1 = output.indexOf(probeBeginMark ~ newline ~ "\\n"); 399 auto idx2 = output[max(0, idx1) .. $].indexOf(probeEndMark) + idx1; 400 enforce(idx1 >= 0 && idx1 < idx2, 401 "Unexpected platform information output - does not contain a JSON object."); 402 output = output[idx1 + probeBeginMark.length .. idx2].replace(newline, "").replace("\\n", "\n"); 403 404 output = output.splitter("\n").filter!((e) => e.length > 0) 405 .map!((e) { 406 if (e.count("\"") == 0) 407 { 408 return e ~ ` ""`; 409 } 410 return e; 411 }) 412 .joiner("\n").array().to!string; 413 414 BuildPlatform build_platform; 415 Tag sdl = parseSource(output); 416 417 foreach (n; sdl.all.tags) 418 { 419 switch (n.name) 420 { 421 default: 422 break; 423 case "platform": 424 build_platform.platform = n.values.map!(e => e.toString()).array(); 425 break; 426 case "architecture": 427 build_platform.architecture = n.values.map!(e => e.toString()).array(); 428 break; 429 case "compiler": 430 build_platform.compiler = n.values.front.toString(); 431 break; 432 case "frontendVersion": 433 build_platform.frontendVersion = n.values.front.toString() 434 .filter!((e) => e >= '0' && e <= '9').array().to!string 435 .to!int; 436 break; 437 } 438 } 439 return build_platform; 440 }