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; 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(string f) 44 { 45 import std.path; 46 switch (extension(f)) { 47 default: 48 return false; 49 version (Windows) { 50 case ".lib", ".obj", ".res", ".def": 51 return true; 52 } else { 53 case ".a", ".o", ".so", ".dylib": 54 return true; 55 } 56 } 57 } 58 59 unittest { 60 version (Windows) { 61 assert(isLinkerFile("test.obj")); 62 assert(isLinkerFile("test.lib")); 63 assert(isLinkerFile("test.res")); 64 assert(!isLinkerFile("test.o")); 65 assert(!isLinkerFile("test.d")); 66 } else { 67 assert(isLinkerFile("test.o")); 68 assert(isLinkerFile("test.a")); 69 assert(isLinkerFile("test.so")); 70 assert(isLinkerFile("test.dylib")); 71 assert(!isLinkerFile("test.obj")); 72 assert(!isLinkerFile("test.d")); 73 } 74 } 75 76 77 /** 78 Replaces each referenced import library by the appropriate linker flags. 79 80 This function tries to invoke "pkg-config" if possible and falls back to 81 direct flag translation if that fails. 82 */ 83 void resolveLibs(ref BuildSettings settings) 84 { 85 import std.string : format; 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 version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; 93 } 94 95 version (Posix) { 96 import std.algorithm : any, map, partition, startsWith; 97 import std.array : array, join, split; 98 import std.exception : enforce; 99 import std.process : execute; 100 101 try { 102 enum pkgconfig_bin = "pkg-config"; 103 104 bool exists(string lib) { 105 return execute([pkgconfig_bin, "--exists", lib]).status == 0; 106 } 107 108 auto pkgconfig_libs = settings.libs.partition!(l => !exists(l)); 109 pkgconfig_libs ~= settings.libs[0 .. $ - pkgconfig_libs.length] 110 .partition!(l => !exists("lib"~l)).map!(l => "lib"~l).array; 111 settings.libs = settings.libs[0 .. $ - pkgconfig_libs.length]; 112 113 if (pkgconfig_libs.length) { 114 logDiagnostic("Using pkg-config to resolve library flags for %s.", pkgconfig_libs.join(", ")); 115 auto libflags = execute([pkgconfig_bin, "--libs"] ~ pkgconfig_libs); 116 enforce(libflags.status == 0, format("pkg-config exited with error code %s: %s", libflags.status, libflags.output)); 117 foreach (f; libflags.output.split()) { 118 if (f.startsWith("-L-L")) { 119 settings.addLFlags(f[2 .. $]); 120 } else if (f.startsWith("-defaultlib")) { 121 settings.addDFlags(f); 122 } else if (f.startsWith("-L-defaultlib")) { 123 settings.addDFlags(f[2 .. $]); 124 } else if (f.startsWith("-pthread")) { 125 settings.addLFlags("-lpthread"); 126 } else if (f.startsWith("-L-l")) { 127 settings.addLFlags(f[2 .. $].split(",")); 128 } else if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 129 else settings.addLFlags(f); 130 } 131 } 132 if (settings.libs.length) logDiagnostic("Using direct -l... flags for %s.", settings.libs.array.join(", ")); 133 } catch (Exception e) { 134 logDiagnostic("pkg-config failed: %s", e.msg); 135 logDiagnostic("Falling back to direct -l... flags."); 136 } 137 } 138 } 139 140 141 /** Searches the given list of compiler flags for ones that have a generic 142 equivalent. 143 144 Certain compiler flags should, instead of using compiler-specfic syntax, 145 be specified as build options (`BuildOptions`) or built requirements 146 (`BuildRequirements`). This function will output warning messages to 147 assist the user in making the best choice. 148 */ 149 void warnOnSpecialCompilerFlags(string[] compiler_flags, BuildOptions options, string package_name, string config_name) 150 { 151 import std.algorithm : any, endsWith, startsWith; 152 import std.range : empty; 153 154 struct SpecialFlag { 155 string[] flags; 156 string alternative; 157 } 158 static immutable SpecialFlag[] s_specialFlags = [ 159 {["-c", "-o-"], "Automatically issued by DUB, do not specify in dub.json"}, 160 {["-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, 161 {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, 162 {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, 163 {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, 164 {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, 165 {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, 166 {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, 167 {["-unittest", "-funittest"], "Call dub with --build=unittest"}, 168 {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, 169 {["-D"], "Call dub with --build=docs or --build=ddox"}, 170 {["-X"], "Call dub with --build=ddox"}, 171 {["-cov"], "Call dub with --build=cov or --build=unittest-cov"}, 172 {["-profile"], "Call dub with --build=profile"}, 173 {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, 174 {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, 175 {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, 176 {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, 177 {["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`} 178 ]; 179 180 struct SpecialOption { 181 BuildOption[] flags; 182 string alternative; 183 } 184 static immutable SpecialOption[] s_specialOptions = [ 185 {[BuildOption.debugMode], "Call DUB with --build=debug"}, 186 {[BuildOption.releaseMode], "Call DUB with --build=release"}, 187 {[BuildOption.coverage], "Call DUB with --build=cov or --build=unittest-cov"}, 188 {[BuildOption.debugInfo], "Call DUB with --build=debug"}, 189 {[BuildOption.inline], "Call DUB with --build=release"}, 190 {[BuildOption.noBoundsCheck], "Call DUB with --build=release-nobounds"}, 191 {[BuildOption.optimize], "Call DUB with --build=release"}, 192 {[BuildOption.profile], "Call DUB with --build=profile"}, 193 {[BuildOption.unittests], "Call DUB with --build=unittest"}, 194 {[BuildOption.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, 195 {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, 196 {[BuildOption.property], "This flag is deprecated and has no effect"} 197 ]; 198 199 bool got_preamble = false; 200 void outputPreamble() 201 { 202 if (got_preamble) return; 203 got_preamble = true; 204 logWarn(""); 205 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 206 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 207 logWarn(""); 208 logWarn("The following compiler flags have been specified in the package description"); 209 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 210 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 211 logWarn("to the compiler, or use one of the suggestions below:"); 212 logWarn(""); 213 } 214 215 foreach (f; compiler_flags) { 216 foreach (sf; s_specialFlags) { 217 if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 218 outputPreamble(); 219 logWarn("%s: %s", f, sf.alternative); 220 break; 221 } 222 } 223 } 224 225 foreach (sf; s_specialOptions) { 226 foreach (f; sf.flags) { 227 if (options & f) { 228 outputPreamble(); 229 logWarn("%s: %s", f, sf.alternative); 230 break; 231 } 232 } 233 } 234 235 if (got_preamble) logWarn(""); 236 } 237 238 239 /** 240 Generate a file that will give, at compile time, informations about the compiler (architecture, frontend version...) 241 242 See_Also: `readPlatformProbe` 243 */ 244 Path generatePlatformProbeFile() 245 { 246 import dub.internal.vibecompat.core.file; 247 import dub.internal.vibecompat.data.json; 248 import dub.internal.utils; 249 250 auto path = getTempFile("dub_platform_probe", ".d"); 251 252 auto fil = openFile(path, FileMode.createTrunc); 253 scope (failure) { 254 fil.close(); 255 } 256 257 // NOTE: This must be kept in sync with the dub.platform module 258 fil.write(q{ 259 module dub_platform_probe; 260 261 template toString(int v) { enum toString = v.stringof; } 262 263 pragma(msg, `{`); 264 pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`); 265 pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); 266 pragma(msg, ` "compilerVendor": "` ~ __VENDOR__ ~ `",`); 267 pragma(msg, ` "platform": [`); 268 pragma(msg, ` ` ~ determinePlatform()); 269 pragma(msg, ` ],`); 270 pragma(msg, ` "architecture": [`); 271 pragma(msg, ` ` ~ determineArchitecture()); 272 pragma(msg, ` ],`); 273 pragma(msg, `}`); 274 275 string determinePlatform() 276 { 277 string ret; 278 version(Windows) ret ~= `"windows", `; 279 version(linux) ret ~= `"linux", `; 280 version(Posix) ret ~= `"posix", `; 281 version(OSX) ret ~= `"osx", `; 282 version(FreeBSD) ret ~= `"freebsd", `; 283 version(OpenBSD) ret ~= `"openbsd", `; 284 version(NetBSD) ret ~= `"netbsd", `; 285 version(DragonFlyBSD) ret ~= `"dragonflybsd", `; 286 version(BSD) ret ~= `"bsd", `; 287 version(Solaris) ret ~= `"solaris", `; 288 version(AIX) ret ~= `"aix", `; 289 version(Haiku) ret ~= `"haiku", `; 290 version(SkyOS) ret ~= `"skyos", `; 291 version(SysV3) ret ~= `"sysv3", `; 292 version(SysV4) ret ~= `"sysv4", `; 293 version(Hurd) ret ~= `"hurd", `; 294 version(Android) ret ~= `"android", `; 295 version(Cygwin) ret ~= `"cygwin", `; 296 version(MinGW) ret ~= `"mingw", `; 297 return ret; 298 } 299 300 string determineArchitecture() 301 { 302 string ret; 303 version(X86) ret ~= `"x86", `; 304 version(X86_64) ret ~= `"x86_64", `; 305 version(ARM) ret ~= `"arm", `; 306 version(ARM_Thumb) ret ~= `"arm_thumb", `; 307 version(ARM_SoftFloat) ret ~= `"arm_softfloat", `; 308 version(ARM_HardFloat) ret ~= `"arm_hardfloat", `; 309 version(ARM64) ret ~= `"arm64", `; 310 version(PPC) ret ~= `"ppc", `; 311 version(PPC_SoftFP) ret ~= `"ppc_softfp", `; 312 version(PPC_HardFP) ret ~= `"ppc_hardfp", `; 313 version(PPC64) ret ~= `"ppc64", `; 314 version(IA64) ret ~= `"ia64", `; 315 version(MIPS) ret ~= `"mips", `; 316 version(MIPS32) ret ~= `"mips32", `; 317 version(MIPS64) ret ~= `"mips64", `; 318 version(MIPS_O32) ret ~= `"mips_o32", `; 319 version(MIPS_N32) ret ~= `"mips_n32", `; 320 version(MIPS_O64) ret ~= `"mips_o64", `; 321 version(MIPS_N64) ret ~= `"mips_n64", `; 322 version(MIPS_EABI) ret ~= `"mips_eabi", `; 323 version(MIPS_NoFloat) ret ~= `"mips_nofloat", `; 324 version(MIPS_SoftFloat) ret ~= `"mips_softfloat", `; 325 version(MIPS_HardFloat) ret ~= `"mips_hardfloat", `; 326 version(SPARC) ret ~= `"sparc", `; 327 version(SPARC_V8Plus) ret ~= `"sparc_v8plus", `; 328 version(SPARC_SoftFP) ret ~= `"sparc_softfp", `; 329 version(SPARC_HardFP) ret ~= `"sparc_hardfp", `; 330 version(SPARC64) ret ~= `"sparc64", `; 331 version(S390) ret ~= `"s390", `; 332 version(S390X) ret ~= `"s390x", `; 333 version(HPPA) ret ~= `"hppa", `; 334 version(HPPA64) ret ~= `"hppa64", `; 335 version(SH) ret ~= `"sh", `; 336 version(SH64) ret ~= `"sh64", `; 337 version(Alpha) ret ~= `"alpha", `; 338 version(Alpha_SoftFP) ret ~= `"alpha_softfp", `; 339 version(Alpha_HardFP) ret ~= `"alpha_hardfp", `; 340 return ret; 341 } 342 343 string determineCompiler() 344 { 345 version(DigitalMars) return "dmd"; 346 else version(GNU) return "gdc"; 347 else version(LDC) return "ldc"; 348 else version(SDC) return "sdc"; 349 else return null; 350 } 351 }); 352 353 fil.close(); 354 355 return path; 356 } 357 358 /** 359 Processes the output generated by compiling the platform probe file. 360 361 See_Also: `generatePlatformProbeFile`. 362 */ 363 BuildPlatform readPlatformProbe(string output) 364 { 365 import std.algorithm : map; 366 import std.array : array; 367 import std.exception : enforce; 368 import std.string; 369 370 // work around possible additional output of the compiler 371 auto idx1 = output.indexOf("{"); 372 auto idx2 = output.lastIndexOf("}"); 373 enforce(idx1 >= 0 && idx1 < idx2, 374 "Unexpected platform information output - does not contain a JSON object."); 375 output = output[idx1 .. idx2+1]; 376 377 import dub.internal.vibecompat.data.json; 378 auto json = parseJsonString(output); 379 380 BuildPlatform build_platform; 381 build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array(); 382 build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array(); 383 build_platform.compiler = json["compiler"].get!string; 384 build_platform.frontendVersion = json["frontendVersion"].get!int; 385 return build_platform; 386 }