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 if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", ")); 153 } catch (Exception e) { 154 logDiagnostic("pkg-config failed: %s", e.msg); 155 logDiagnostic("Falling back to direct -l... flags."); 156 } 157 } 158 } 159 160 161 /** Searches the given list of compiler flags for ones that have a generic 162 equivalent. 163 164 Certain compiler flags should, instead of using compiler-specific syntax, 165 be specified as build options (`BuildOption`) or built requirements 166 (`BuildRequirements`). This function will output warning messages to 167 assist the user in making the best choice. 168 */ 169 void warnOnSpecialCompilerFlags(string[] compiler_flags, Flags!BuildOption options, string package_name, string config_name) 170 { 171 import std.algorithm : any, endsWith, startsWith; 172 import std.range : empty; 173 174 struct SpecialFlag { 175 string[] flags; 176 string alternative; 177 } 178 static immutable SpecialFlag[] s_specialFlags = [ 179 {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"}, 180 {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, 181 {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, 182 {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, 183 {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, 184 {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, 185 {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, 186 {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, 187 {["-unittest", "-funittest"], "Call dub with --build=unittest"}, 188 {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, 189 {["-D"], "Call dub with --build=docs or --build=ddox"}, 190 {["-X"], "Call dub with --build=ddox"}, 191 {["-cov"], "Call dub with --build=cov or --build=unittest-cov"}, 192 {["-cov=ctfe"], "Call dub with --build=cov-ctfe or --build=unittest-cov-ctfe"}, 193 {["-profile"], "Call dub with --build=profile"}, 194 {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, 195 {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, 196 {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, 197 {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, 198 {["-m32", "-m64", "-m32mscoff"], `Use --arch=x86/--arch=x86_64/--arch=x86_mscoff to specify the target architecture, e.g. 'dub build --arch=x86_64'`} 199 ]; 200 201 struct SpecialOption { 202 BuildOption[] flags; 203 string alternative; 204 } 205 static immutable SpecialOption[] s_specialOptions = [ 206 {[BuildOption.debugMode], "Call DUB with --build=debug"}, 207 {[BuildOption.releaseMode], "Call DUB with --build=release"}, 208 {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"}, 209 {[BuildOption.coverageCTFE], "Call DUB with --build=cov-ctfe or --build=unittest-cov-ctfe"}, 210 {[BuildOption.debugInfo], "Call DUB with --build=debug"}, 211 {[BuildOption.inline], "Call DUB with --build=release"}, 212 {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"}, 213 {[BuildOption.optimize], "Call DUB with --build=release"}, 214 {[BuildOption.profile], "Call DUB with --build=profile"}, 215 {[BuildOption.unittests], "Call DUB with --build=unittest"}, 216 {[BuildOption.syntaxOnly], "Call DUB with --build=syntax"}, 217 {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, 218 {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, 219 {[BuildOption.property], "This flag is deprecated and has no effect"} 220 ]; 221 222 bool got_preamble = false; 223 void outputPreamble() 224 { 225 if (got_preamble) return; 226 got_preamble = true; 227 logWarn(""); 228 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 229 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 230 logWarn(""); 231 logWarn("The following compiler flags have been specified in the package description"); 232 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 233 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 234 logWarn("to the compiler, or use one of the suggestions below:"); 235 logWarn(""); 236 } 237 238 foreach (f; compiler_flags) { 239 foreach (sf; s_specialFlags) { 240 if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 241 outputPreamble(); 242 logWarn("%s: %s", f, sf.alternative); 243 break; 244 } 245 } 246 } 247 248 foreach (sf; s_specialOptions) { 249 foreach (f; sf.flags) { 250 if (options & f) { 251 outputPreamble(); 252 logWarn("%s: %s", f, sf.alternative); 253 break; 254 } 255 } 256 } 257 258 if (got_preamble) logWarn(""); 259 } 260 261 private enum probeBeginMark = "__dub_probe_begin__"; 262 private enum probeEndMark = "__dub_probe_end__"; 263 264 /** 265 Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...) 266 267 See_Also: `readPlatformProbe` 268 */ 269 NativePath generatePlatformProbeFile() 270 { 271 import dub.internal.vibecompat.core.file; 272 import dub.internal.utils; 273 import std.string : format; 274 275 enum moduleInfo = q{ 276 module object; 277 alias string = const(char)[]; 278 }; 279 280 // avoid druntime so that this compiles without a compiler's builtin object.d 281 enum probe = q{ 282 %1$s 283 pragma(msg, `%2$s`); 284 pragma(msg, `\n`); 285 pragma(msg, `compiler`); 286 %6$s 287 pragma(msg, `\n`); 288 pragma(msg, `frontendVersion "`); 289 pragma(msg, __VERSION__.stringof); 290 pragma(msg, `"\n`); 291 pragma(msg, `compilerVendor "`); 292 pragma(msg, __VENDOR__); 293 pragma(msg, `"\n`); 294 pragma(msg, `platform`); 295 %4$s 296 pragma(msg, `\n`); 297 pragma(msg, `architecture `); 298 %5$s 299 pragma(msg, `\n`); 300 pragma(msg, `%3$s`); 301 }.format(moduleInfo, probeBeginMark, probeEndMark, pragmaGen(platformCheck), pragmaGen(archCheck), compilerCheckPragmas); 302 303 auto path = getTempFile("dub_platform_probe", ".d"); 304 writeFile(path, probe); 305 return path; 306 } 307 308 309 /** 310 Processes the SDL output generated by compiling the platform probe file. 311 312 See_Also: `generatePlatformProbeFile`. 313 */ 314 BuildPlatform readPlatformSDLProbe(string output) 315 { 316 import std.algorithm : map, max, splitter, joiner, count, filter; 317 import std.array : array; 318 import std.exception : enforce; 319 import std.range : front; 320 import std.ascii : newline; 321 import std.string; 322 import dub.internal.sdlang.parser; 323 import dub.internal.sdlang.ast; 324 import std.conv; 325 326 // work around possible additional output of the compiler 327 auto idx1 = output.indexOf(probeBeginMark ~ newline ~ "\\n"); 328 auto idx2 = output[max(0, idx1) .. $].indexOf(probeEndMark) + idx1; 329 enforce(idx1 >= 0 && idx1 < idx2, 330 "Unexpected platform information output - does not contain a JSON object."); 331 output = output[idx1 + probeBeginMark.length .. idx2].replace(newline, "").replace("\\n", "\n"); 332 333 output = output.splitter("\n").filter!((e) => e.length > 0) 334 .map!((e) { 335 if (e.count("\"") == 0) 336 { 337 return e ~ ` ""`; 338 } 339 return e; 340 }) 341 .joiner("\n").array().to!string; 342 343 BuildPlatform build_platform; 344 Tag sdl = parseSource(output); 345 346 foreach (n; sdl.all.tags) 347 { 348 switch (n.name) 349 { 350 default: 351 break; 352 case "platform": 353 build_platform.platform = n.values.map!(e => e.toString()).array(); 354 break; 355 case "architecture": 356 build_platform.architecture = n.values.map!(e => e.toString()).array(); 357 break; 358 case "compiler": 359 build_platform.compiler = n.values.front.toString(); 360 break; 361 case "frontendVersion": 362 build_platform.frontendVersion = n.values.front.toString() 363 .filter!((e) => e >= '0' && e <= '9').array().to!string 364 .to!int; 365 break; 366 } 367 } 368 return build_platform; 369 }