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, compilerCheck, platformCheck; 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 /** 262 Turn a DMD-like version (e.g. 2.082.1) into a SemVer-like version (e.g. 2.82.1). 263 The function accepts a dependency operator prefix and some text postfix. 264 Prefix and postfix are returned verbatim. 265 Params: 266 ver = version string, possibly with a dependency operator prefix and some 267 test postfix. 268 Returns: 269 A Semver compliant string 270 */ 271 package(dub) string dmdLikeVersionToSemverLike(string ver) 272 { 273 import std.algorithm : countUntil, joiner, map, skipOver, splitter; 274 import std.array : join, split; 275 import std.ascii : isDigit; 276 import std.conv : text; 277 import std.exception : enforce; 278 import std.functional : not; 279 import std.range : padRight; 280 281 const start = ver.countUntil!isDigit; 282 enforce(start != -1, "Invalid semver: "~ver); 283 const prefix = ver[0 .. start]; 284 ver = ver[start .. $]; 285 286 const end = ver.countUntil!(c => !c.isDigit && c != '.'); 287 const postfix = end == -1 ? null : ver[end .. $]; 288 auto verStr = ver[0 .. $-postfix.length]; 289 290 auto comps = verStr 291 .splitter(".") 292 .map!((a) { if (a.length > 1) a.skipOver("0"); return a;}) 293 .padRight("0", 3); 294 295 return text(prefix, comps.joiner("."), postfix); 296 } 297 298 /// 299 unittest { 300 assert(dmdLikeVersionToSemverLike("2.082.1") == "2.82.1"); 301 assert(dmdLikeVersionToSemverLike("2.082.0") == "2.82.0"); 302 assert(dmdLikeVersionToSemverLike("2.082") == "2.82.0"); 303 assert(dmdLikeVersionToSemverLike("~>2.082") == "~>2.82.0"); 304 assert(dmdLikeVersionToSemverLike("~>2.082-beta1") == "~>2.82.0-beta1"); 305 assert(dmdLikeVersionToSemverLike("2.4.6") == "2.4.6"); 306 assert(dmdLikeVersionToSemverLike("2.4.6-alpha12") == "2.4.6-alpha12"); 307 } 308 309 private enum probeBeginMark = "__dub_probe_begin__"; 310 private enum probeEndMark = "__dub_probe_end__"; 311 312 /** 313 Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...) 314 315 See_Also: `readPlatformProbe` 316 */ 317 NativePath generatePlatformProbeFile() 318 { 319 import dub.internal.vibecompat.core.file; 320 import dub.internal.vibecompat.data.json; 321 import dub.internal.utils; 322 import std.string : format; 323 324 // try to not use phobos in the probe to avoid long import times 325 enum probe = q{ 326 module dub_platform_probe; 327 328 template toString(int v) { enum toString = v.stringof; } 329 string stringArray(string[] ary) { 330 string res; 331 foreach (i, e; ary) { 332 if (i) 333 res ~= ", "; 334 res ~= '"' ~ e ~ '"'; 335 } 336 return res; 337 } 338 339 pragma(msg, `%1$s` 340 ~ '\n' ~ `{` 341 ~ '\n' ~ ` "compiler": "`~ determineCompiler() ~ `",` 342 ~ '\n' ~ ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,` 343 ~ '\n' ~ ` "compilerVendor": "` ~ __VENDOR__ ~ `",` 344 ~ '\n' ~ ` "platform": [` 345 ~ '\n' ~ ` ` ~ determinePlatform().stringArray 346 ~ '\n' ~ ` ],` 347 ~ '\n' ~ ` "architecture": [` 348 ~ '\n' ~ ` ` ~ determineArchitecture().stringArray 349 ~ '\n' ~ ` ],` 350 ~ '\n' ~ `}` 351 ~ '\n' ~ `%2$s`); 352 353 string[] determinePlatform() { %3$s } 354 string[] determineArchitecture() { %4$s } 355 string determineCompiler() { %5$s } 356 357 }.format(probeBeginMark, probeEndMark, platformCheck, archCheck, compilerCheck); 358 359 auto path = getTempFile("dub_platform_probe", ".d"); 360 writeFile(path, probe); 361 362 return path; 363 } 364 365 /** 366 Processes the JSON output generated by compiling the platform probe file. 367 368 See_Also: `generatePlatformProbeFile`. 369 */ 370 BuildPlatform readPlatformJsonProbe(string output) 371 { 372 import std.algorithm : map; 373 import std.array : array; 374 import std.exception : enforce; 375 import std.string; 376 377 // work around possible additional output of the compiler 378 auto idx1 = output.indexOf(probeBeginMark); 379 auto idx2 = output.lastIndexOf(probeEndMark); 380 enforce(idx1 >= 0 && idx1 < idx2, 381 "Unexpected platform information output - does not contain a JSON object."); 382 output = output[idx1+probeBeginMark.length .. idx2]; 383 384 import dub.internal.vibecompat.data.json; 385 auto json = parseJsonString(output); 386 387 BuildPlatform build_platform; 388 build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array(); 389 build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array(); 390 build_platform.compiler = json["compiler"].get!string; 391 build_platform.frontendVersion = json["frontendVersion"].get!int; 392 return build_platform; 393 }