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(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 import std.array : array; 87 88 if (settings.libs.length == 0) return; 89 90 if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { 91 logDiagnostic("Ignoring all import libraries for static library build."); 92 settings.libs = null; 93 version(Windows) 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.warnings, BuildOption.warningsAsErrors], "Use \"buildRequirements\" to control the warning level"}, 196 {[BuildOption.ignoreDeprecations, BuildOption.deprecationWarnings, BuildOption.deprecationErrors], "Use \"buildRequirements\" to control the deprecation warning level"}, 197 {[BuildOption.property], "This flag is deprecated and has no effect"} 198 ]; 199 200 bool got_preamble = false; 201 void outputPreamble() 202 { 203 if (got_preamble) return; 204 got_preamble = true; 205 logWarn(""); 206 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 207 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 208 logWarn(""); 209 logWarn("The following compiler flags have been specified in the package description"); 210 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 211 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 212 logWarn("to the compiler, or use one of the suggestions below:"); 213 logWarn(""); 214 } 215 216 foreach (f; compiler_flags) { 217 foreach (sf; s_specialFlags) { 218 if (sf.flags.any!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 219 outputPreamble(); 220 logWarn("%s: %s", f, sf.alternative); 221 break; 222 } 223 } 224 } 225 226 foreach (sf; s_specialOptions) { 227 foreach (f; sf.flags) { 228 if (options & f) { 229 outputPreamble(); 230 logWarn("%s: %s", f, sf.alternative); 231 break; 232 } 233 } 234 } 235 236 if (got_preamble) logWarn(""); 237 } 238 239 240 /** 241 Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...) 242 243 See_Also: `readPlatformProbe` 244 */ 245 NativePath generatePlatformProbeFile() 246 { 247 import dub.internal.vibecompat.core.file; 248 import dub.internal.vibecompat.data.json; 249 import dub.internal.utils; 250 251 // try to not use phobos in the probe to avoid long import times 252 enum probe = q{ 253 module dub_platform_probe; 254 255 template toString(int v) { enum toString = v.stringof; } 256 string join(string[] ary, string sep) { 257 string res = ary[0]; 258 foreach (e; ary[1 .. $]) 259 res ~= sep ~ e; 260 return res; 261 } 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().join(`", "`) ~ '"'); 269 pragma(msg, ` ],`); 270 pragma(msg, ` "architecture": [`); 271 pragma(msg, ` "` ~ determineArchitecture().join(`", "`) ~ '"'); 272 pragma(msg, ` ],`); 273 pragma(msg, `}`); 274 275 string[] determinePlatform() } ~ '{' ~ platformCheck ~ '}' ~ q{ 276 string[] determineArchitecture() } ~ '{' ~ archCheck ~ '}' ~ q{ 277 string determineCompiler() } ~ '{' ~ compilerCheck ~ '}'; 278 279 auto path = getTempFile("dub_platform_probe", ".d"); 280 auto fil = openFile(path, FileMode.createTrunc); 281 fil.write(probe); 282 283 return path; 284 } 285 286 /** 287 Processes the output generated by compiling the platform probe file. 288 289 See_Also: `generatePlatformProbeFile`. 290 */ 291 BuildPlatform readPlatformProbe(string output) 292 { 293 import std.algorithm : map; 294 import std.array : array; 295 import std.exception : enforce; 296 import std.string; 297 298 // work around possible additional output of the compiler 299 auto idx1 = output.indexOf("{"); 300 auto idx2 = output.lastIndexOf("}"); 301 enforce(idx1 >= 0 && idx1 < idx2, 302 "Unexpected platform information output - does not contain a JSON object."); 303 output = output[idx1 .. idx2+1]; 304 305 import dub.internal.vibecompat.data.json; 306 auto json = parseJsonString(output); 307 308 BuildPlatform build_platform; 309 build_platform.platform = json["platform"].get!(Json[]).map!(e => e.get!string()).array(); 310 build_platform.architecture = json["architecture"].get!(Json[]).map!(e => e.get!string()).array(); 311 build_platform.compiler = json["compiler"].get!string; 312 build_platform.frontendVersion = json["frontendVersion"].get!int; 313 return build_platform; 314 }