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.core.log; 13 import dub.internal.vibecompat.inet.path; 14 import std.algorithm : canFind, endsWith, filter; 15 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.platform.canFind("windows"); 51 case ".a", ".o", ".so", ".dylib": 52 return !platform.platform.canFind("windows"); 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 Replaces each referenced import library by the appropriate linker flags. 78 79 This function tries to invoke "pkg-config" if possible and falls back to 80 direct flag translation if that fails. 81 */ 82 void resolveLibs(ref BuildSettings settings, const scope ref BuildPlatform platform) 83 { 84 import std..string : format; 85 import std.array : array; 86 87 if (settings.libs.length == 0) return; 88 89 if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { 90 logDiagnostic("Ignoring all import libraries for static library build."); 91 settings.libs = null; 92 if (platform.platform.canFind("windows")) 93 settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; 94 } 95 96 version (Posix) { 97 import std.algorithm : any, map, partition, startsWith; 98 import std.array : array, join, split; 99 import std.exception : enforce; 100 import std.process : execute; 101 102 try { 103 enum pkgconfig_bin = "pkg-config"; 104 105 bool exists(string lib) { 106 return execute([pkgconfig_bin, "--exists", lib]).status == 0; 107 } 108 109 auto pkgconfig_libs = settings.libs.partition!(l => !exists(l)); 110 pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length] 111 .partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array; 112 settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length]; 113 114 if (pkgconfig_libs.length) { 115 logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", ")); 116 auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs); 117 enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output)); 118 foreach (f; libflags.output.split()) { 119 if (f.startsWith("-L-L")) { 120 settings.addLFlags(f[2 .. $]); 121 } else if (f.startsWith("-defaultlib")) { 122 settings.addDFlags(f); 123 } else if (f.startsWith("-L-defaultlib")) { 124 settings.addDFlags(f[2 .. $]); 125 } else if (f.startsWith("-pthread")) { 126 settings.addLFlags("-lpthread"); 127 } else if (f.startsWith("-L-l")) { 128 settings.addLFlags(f[2 .. $].split(",")); 129 } else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 130 else settings.addLFlags(f); 131 } 132 } 133 if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", ")); 134 } catch (Exception e) { 135 logDiagnostic("pkg-config failed: %s", e.msg); 136 logDiagnostic("Falling back to direct -l... flags."); 137 } 138 } 139 } 140 141 142 /** Searches the given list of compiler flags for ones that have a generic 143 equivalent. 144 145 Certain compiler flags should, instead of using compiler-specific syntax, 146 be specified as build options (`BuildOptions`) or built requirements 147 (`BuildRequirements`). This function will output warning messages to 148 assist the user in making the best choice. 149 */ 150 void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name) 151 { 152 import std.algorithm : any, endsWith, startsWith; 153 import std.range : empty; 154 155 struct SpecialFlag { 156 string[] flags; 157 string alternative; 158 } 159 static immutable SpecialFlag[] s_specialFlags = [ 160 {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"}, 161 {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, 162 {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, 163 {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, 164 {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, 165 {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, 166 {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, 167 {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, 168 {["-unittest", "-funittest"], "Call dub with --build=unittest"}, 169 {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, 170 {["-D"], "Call dub with --build=docs or --build=ddox"}, 171 {["-X"], "Call dub with --build=ddox"}, 172 {["-cov"], "Call dub with --build=cov or --build=unittest-cov"}, 173 {["-profile"], "Call dub with --build=profile"}, 174 {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, 175 {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, 176 {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, 177 {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, 178 {["-m32", "-m64", "-m32mscoff"], `Use --arch=x86/--arch=x86_64/--arch=x86_mscoff to specify the target architecture, e.g. 'dub build --arch=x86_64'`} 179 ]; 180 181 struct SpecialOption { 182 BuildOption[] flags; 183 string alternative; 184 } 185 static immutable SpecialOption[] s_specialOptions = [ 186 {[BuildOption.debugMode], "Call DUB with --build=debug"}, 187 {[BuildOption.releaseMode], "Call DUB with --build=release"}, 188 {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"}, 189 {[BuildOption.debugInfo], "Call DUB with --build=debug"}, 190 {[BuildOption.inline], "Call DUB with --build=release"}, 191 {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"}, 192 {[BuildOption.optimize], "Call DUB with --build=release"}, 193 {[BuildOption.profile], "Call DUB with --build=profile"}, 194 {[BuildOption.unittests], "Call DUB with --build=unittest"}, 195 {[BuildOption.syntaxOnly], "Call DUB with --build=syntax"}, 196 {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, 197 {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, 198 {[BuildOption.property], "This flag is deprecated and has no effect"} 199 ]; 200 201 bool got_preamble = false; 202 void outputPreamble() 203 { 204 if (got_preamble) return; 205 got_preamble = true; 206 logWarn(""); 207 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 208 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 209 logWarn(""); 210 logWarn("The following compiler flags have been specified in the package description"); 211 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 212 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 213 logWarn("to the compiler, or use one of the suggestions below:"); 214 logWarn(""); 215 } 216 217 foreach (f; compiler_flags) { 218 foreach (sf; s_specialFlags) { 219 if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 220 outputPreamble(); 221 logWarn("%s: %s", f, sf.alternative); 222 break; 223 } 224 } 225 } 226 227 foreach (sf; s_specialOptions) { 228 foreach (f; sf.flags) { 229 if (options & f) { 230 outputPreamble(); 231 logWarn("%s: %s", f, sf.alternative); 232 break; 233 } 234 } 235 } 236 237 if (got_preamble) logWarn(""); 238 } 239 240 /** 241 Turn a DMD-like version (e.g. 2.082.1) into a SemVer-like version (e.g. 2.82.1). 242 The function accepts a dependency operator prefix and some text postfix. 243 Prefix and postfix are returned verbatim. 244 Params: 245 ver = version string, possibly with a dependency operator prefix and some 246 test postfix. 247 Returns: 248 A Semver compliant string 249 */ 250 package(dub) string dmdLikeVersionToSemverLike(string ver) 251 { 252 import std.algorithm : countUntil, joiner, map, skipOver, splitter; 253 import std.array : join, split; 254 import std.ascii : isDigit; 255 import std.conv : text; 256 import std.exception : enforce; 257 import std.functional : not; 258 import std.range : padRight; 259 260 const start = ver.countUntil!isDigit; 261 enforce(start != -1, "Invalid semver: "~ver); 262 const prefix = ver[0 .. start]; 263 ver = ver[start .. $]; 264 265 const end = ver.countUntil!(c => !c.isDigit && c != '.'); 266 const postfix = end == -1 ? null : ver[end .. $]; 267 auto verStr = ver[0 .. $-postfix.length]; 268 269 auto comps = verStr 270 .splitter(".") 271 .map!((a) { if (a.length > 1) a.skipOver("0"); return a;}) 272 .padRight("0", 3); 273 274 return text(prefix, comps.joiner("."), postfix); 275 } 276 277 /// 278 unittest { 279 assert(dmdLikeVersionToSemverLike("2.082.1") == "2.82.1"); 280 assert(dmdLikeVersionToSemverLike("2.082.0") == "2.82.0"); 281 assert(dmdLikeVersionToSemverLike("2.082") == "2.82.0"); 282 assert(dmdLikeVersionToSemverLike("~>2.082") == "~>2.82.0"); 283 assert(dmdLikeVersionToSemverLike("~>2.082-beta1") == "~>2.82.0-beta1"); 284 assert(dmdLikeVersionToSemverLike("2.4.6") == "2.4.6"); 285 assert(dmdLikeVersionToSemverLike("2.4.6-alpha12") == "2.4.6-alpha12"); 286 } 287 288 private enum probeBeginMark = "__dub_probe_begin__"; 289 private enum probeEndMark = "__dub_probe_end__"; 290 291 /** 292 Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...) 293 294 See_Also: `readPlatformProbe` 295 */ 296 NativePath generatePlatformProbeFile() 297 { 298 import dub.internal.vibecompat.core.file; 299 import dub.internal.vibecompat.data.json; 300 import dub.internal.utils; 301 import std..string : format; 302 303 // try to not use phobos in the probe to avoid long import times 304 enum probe = q{ 305 module dub_platform_probe; 306 307 template toString(int v) { enum toString = v.stringof; } 308 string stringArray(string[] ary) { 309 string res; 310 foreach (i, e; ary) { 311 if (i) 312 res ~= ", "; 313 res ~= '"' ~ e ~ '"'; 314 } 315 return res; 316 } 317 318 pragma(msg, `%1$s` 319 ~ '\n' ~ `{` 320 ~ '\n' ~ ` "compiler": "`~ determineCompiler() ~ `",` 321 ~ '\n' ~ ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,` 322 ~ '\n' ~ ` "compilerVendor": "` ~ __VENDOR__ ~ `",` 323 ~ '\n' ~ ` "platform": [` 324 ~ '\n' ~ ` ` ~ determinePlatform().stringArray 325 ~ '\n' ~ ` ],` 326 ~ '\n' ~ ` "architecture": [` 327 ~ '\n' ~ ` ` ~ determineArchitecture().stringArray 328 ~ '\n' ~ ` ],` 329 ~ '\n' ~ `}` 330 ~ '\n' ~ `%2$s`); 331 332 string[] determinePlatform() { %3$s } 333 string[] determineArchitecture() { %4$s } 334 string determineCompiler() { %5$s } 335 336 }.format(probeBeginMark, probeEndMark, platformCheck, archCheck, compilerCheck); 337 338 auto path = getTempFile("dub_platform_probe", ".d"); 339 auto fil = openFile(path, FileMode.createTrunc); 340 fil.write(probe); 341 342 return path; 343 } 344 345 /** 346 Processes the JSON output generated by compiling the platform probe file. 347 348 See_Also: `generatePlatformProbeFile`. 349 */ 350 BuildPlatform readPlatformJsonProbe(string output) 351 { 352 import std.algorithm : map; 353 import std.array : array; 354 import std.exception : enforce; 355 import std..string; 356 357 // work around possible additional output of the compiler 358 auto idx1 = output.indexOf(probeBeginMark); 359 auto idx2 = output.lastIndexOf(probeEndMark); 360 enforce(idx1 >= 0 && idx1 < idx2, 361 "Unexpected platform information output - does not contain a JSON object."); 362 output = output[idx1+probeBeginMark.length .. idx2]; 363 364 import dub.internal.vibecompat.data.json; 365 auto json = parseJsonString(output); 366 367 BuildPlatform build_platform; 368 build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array(); 369 build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array(); 370 build_platform.compiler = json["compiler"].get!string; 371 build_platform.frontendVersion = json["frontendVersion"].get!int; 372 return build_platform; 373 }