1 /** 2 Compiler settings and abstraction. 3 4 Copyright: © 2013 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.compiler; 9 10 import dub.compilers.dmd; 11 import dub.compilers.gdc; 12 import dub.compilers.ldc; 13 import dub.internal.std.process; 14 import dub.internal.vibecompat.core.log; 15 import dub.internal.vibecompat.data.json; 16 import dub.internal.vibecompat.inet.path; 17 18 import std.algorithm; 19 import std.array; 20 import std.conv; 21 import std.exception; 22 23 24 static this() 25 { 26 registerCompiler(new DmdCompiler); 27 registerCompiler(new GdcCompiler); 28 registerCompiler(new LdcCompiler); 29 } 30 31 32 Compiler getCompiler(string name) 33 { 34 foreach (c; s_compilers) 35 if (c.name == name) 36 return c; 37 38 // try to match names like gdmd or gdc-2.61 39 if (name.canFind("dmd")) return getCompiler("dmd"); 40 if (name.canFind("gdc")) return getCompiler("gdc"); 41 if (name.canFind("ldc")) return getCompiler("ldc"); 42 43 throw new Exception("Unknown compiler: "~name); 44 } 45 46 void registerCompiler(Compiler c) 47 { 48 s_compilers ~= c; 49 } 50 51 void warnOnSpecialCompilerFlags(string[] compiler_flags, string package_name, string config_name) 52 { 53 struct SpecialFlag { 54 string[] flags; 55 string alternative; 56 } 57 static immutable SpecialFlag[] s_specialFlags = [ 58 {["-c", "-o-"], "Automatically issued by DUB, do not specify in package.json"}, 59 {[ "-w", "-Wall", "-Werr"], `Use "buildRequirements" to control warning behavior`}, 60 {["-property", "-fproperty"], "Using this flag may break building of dependencies and it will probably be removed from DMD in the future"}, 61 {["-wi"], `Use the "buildRequirements" field to control warning behavior`}, 62 {["-d", "-de", "-dw"], `Use the "buildRequirements" field to control deprecation behavior`}, 63 {["-of"], `Use "targetPath" and "targetName" to customize the output file`}, 64 {["-debug", "-fdebug", "-g"], "Call dub with --build=debug"}, 65 {["-release", "-frelease", "-O", "-inline"], "Call dub with --build=release"}, 66 {["-unittest", "-funittest"], "Call dub with --build=unittest"}, 67 {["-lib"], `Use {"targetType": "staticLibrary"} or let dub manage this`}, 68 {["-D"], "Call dub with --build=docs or --build=ddox"}, 69 {["-X"], "Call dub with --build=ddox"}, 70 {["-cov"], "Call dub with --build=cov or --build=unittest-cox"}, 71 {["-profile"], "Call dub with --build=profile"}, 72 {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, 73 {["-debug=", `Use "debugVersions" to specify version constants in a compiler independent way`]}, 74 {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, 75 {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, 76 ]; 77 78 bool got_preamble = false; 79 void outputPreamble() 80 { 81 if (got_preamble) return; 82 got_preamble = true; 83 logWarn(""); 84 if (config_name.empty) logWarn("## Warning for package %s ##", package_name); 85 else logWarn("## Warning for package %s, configuration %s ##", package_name, config_name); 86 logWarn(""); 87 logWarn("The following compiler flags have been specified in the package description"); 88 logWarn("file. They are handled by DUB and direct use in packages is discouraged."); 89 logWarn("Alternatively, you can set the DFLAGS environment variable to pass custom flags"); 90 logWarn("to the compiler, or use one of the suggestions below:"); 91 logWarn(""); 92 } 93 94 foreach (f; compiler_flags) { 95 foreach (sf; s_specialFlags) { 96 if (sf.flags.canFind!(sff => f == sff || (sff.endsWith("=") && f.startsWith(sff)))) { 97 outputPreamble(); 98 logWarn("%s: %s", f, sf.alternative); 99 break; 100 } 101 } 102 } 103 104 if (got_preamble) logWarn(""); 105 } 106 107 108 /** 109 Replaces each referenced import library by the appropriate linker flags. 110 111 This function tries to invoke "pkg-config" if possible and falls back to 112 direct flag translation if that fails. 113 */ 114 void resolveLibs(ref BuildSettings settings) 115 { 116 if (settings.libs.length == 0) return; 117 118 if (settings.targetType == TargetType.library || settings.targetType == TargetType.staticLibrary) { 119 logDiagnostic("Ignoring all import libraries for static library build."); 120 settings.libs = null; 121 version(Windows) settings.sourceFiles = settings.sourceFiles.filter!(f => !f.endsWith(".lib")).array; 122 } 123 124 try { 125 logDiagnostic("Trying to use pkg-config to resolve library flags for %s.", settings.libs); 126 auto libflags = execute(["pkg-config", "--libs"] ~ settings.libs.map!(l => "lib"~l)().array()); 127 enforce(libflags.status == 0, "pkg-config exited with error code "~to!string(libflags.status)); 128 foreach (f; libflags.output.split()) { 129 if (f.startsWith("-Wl,")) settings.addLFlags(f[4 .. $].split(",")); 130 else settings.addLFlags(f); 131 } 132 } catch (Exception e) { 133 logDiagnostic("pkg-config failed: %s", e.msg); 134 logDiagnostic("Falling back to direct -lxyz flags."); 135 version(Windows) settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array()); 136 else settings.addLFlags(settings.libs.map!(l => "-l"~l)().array()); 137 } 138 settings.libs = null; 139 } 140 141 142 interface Compiler { 143 @property string name() const; 144 145 BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override = null); 146 147 /// Replaces high level fields with low level fields and converts 148 /// dmd flags to compiler-specific flags 149 void prepareBuildSettings(ref BuildSettings settings, BuildSetting supported_fields = BuildSetting.all); 150 151 /// Removes any dflags that match one of the BuildOptions values and populates the BuildSettings.options field. 152 void extractBuildOptions(ref BuildSettings settings); 153 154 /// Adds the appropriate flag to set a target path 155 void setTarget(ref BuildSettings settings, in BuildPlatform platform); 156 157 /// Invokes the compiler using the given flags 158 void invoke(in BuildSettings settings, in BuildPlatform platform); 159 160 /// Invokes the underlying linker directly 161 void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects); 162 163 final protected void enforceBuildRequirements(ref BuildSettings settings) 164 { 165 settings.addOptions(BuildOptions.warnings); 166 if (settings.requirements & BuildRequirements.allowWarnings) settings.options &= ~BuildOptions.warningsAsErrors; 167 if (settings.requirements & BuildRequirements.silenceWarnings) settings.options &= ~(BuildOptions.warningsAsErrors|BuildOptions.warnings); 168 if (settings.requirements & BuildRequirements.disallowDeprecations) { settings.options &= ~(BuildOptions.ignoreDeprecations|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.deprecationErrors; } 169 if (settings.requirements & BuildRequirements.silenceDeprecations) { settings.options &= ~(BuildOptions.deprecationErrors|BuildOptions.deprecationWarnings); settings.options |= BuildOptions.ignoreDeprecations; } 170 if (settings.requirements & BuildRequirements.disallowInlining) settings.options &= BuildOptions.inline; 171 if (settings.requirements & BuildRequirements.disallowOptimization) settings.options &= ~BuildOptions.optimize; 172 if (settings.requirements & BuildRequirements.requireBoundsCheck) settings.options &= ~BuildOptions.noBoundsChecks; 173 if (settings.requirements & BuildRequirements.requireContracts) settings.options &= ~BuildOptions.release; 174 if (settings.requirements & BuildRequirements.relaxProperties) settings.options &= ~BuildOptions.property; 175 } 176 } 177 178 179 /// BuildPlatform specific settings, like needed libraries or additional 180 /// include paths. 181 struct BuildSettings { 182 TargetType targetType; 183 string targetPath; 184 string targetName; 185 string workingDirectory; 186 string[] dflags; 187 string[] lflags; 188 string[] libs; 189 string[] sourceFiles; 190 string[] copyFiles; 191 string[] versions; 192 string[] debugVersions; 193 string[] importPaths; 194 string[] stringImportPaths; 195 string[] preGenerateCommands; 196 string[] postGenerateCommands; 197 string[] preBuildCommands; 198 string[] postBuildCommands; 199 BuildRequirements requirements; 200 BuildOptions options; 201 202 void addDFlags(in string[] value...) { dflags ~= value; } 203 void removeDFlags(in string[] value...) { remove(dflags, value); } 204 void addLFlags(in string[] value...) { lflags ~= value; } 205 void addLibs(in string[] value...) { add(libs, value); } 206 void addSourceFiles(in string[] value...) { add(sourceFiles, value); } 207 void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); } 208 void addCopyFiles(in string[] value...) { add(copyFiles, value); } 209 void addVersions(in string[] value...) { add(versions, value); } 210 void addDebugVersions(in string[] value...) { add(debugVersions, value); } 211 void addImportPaths(in string[] value...) { add(importPaths, value); } 212 void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); } 213 void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); } 214 void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); } 215 void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); } 216 void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); } 217 void addRequirements(in BuildRequirements[] value...) { foreach (v; value) this.requirements |= v; } 218 void addOptions(in BuildOptions[] value...) { foreach (v; value) this.options |= v; } 219 void removeOptions(in BuildOptions[] value...) { foreach (v; value) this.options &= ~v; } 220 221 // Adds vals to arr without adding duplicates. 222 private void add(ref string[] arr, in string[] vals, bool no_duplicates = true) 223 { 224 if (!no_duplicates) { 225 arr ~= vals; 226 return; 227 } 228 229 foreach (v; vals) { 230 bool found = false; 231 foreach (i; 0 .. arr.length) 232 if (arr[i] == v) { 233 found = true; 234 break; 235 } 236 if (!found) arr ~= v; 237 } 238 } 239 240 private void removePaths(ref string[] arr, in string[] vals) 241 { 242 bool matches(string s) 243 { 244 foreach (p; vals) 245 if (Path(s) == Path(p)) 246 return true; 247 return false; 248 } 249 arr = arr.filter!(s => !matches(s))().array(); 250 } 251 252 private void remove(ref string[] arr, in string[] vals) 253 { 254 bool matches(string s) 255 { 256 foreach (p; vals) 257 if (s == p) 258 return true; 259 return false; 260 } 261 arr = arr.filter!(s => !matches(s))().array(); 262 } 263 } 264 265 /// Represents a platform a package can be build upon. 266 struct BuildPlatform { 267 /// e.g. ["posix", "windows"] 268 string[] platform; 269 /// e.g. ["x86", "x86_64"] 270 string[] architecture; 271 /// Canonical compiler name e.g. "dmd" 272 string compiler; 273 /// Compiler binary name e.g. "ldmd2" 274 string compilerBinary; 275 276 /// Build platforms can be specified via a string specification. 277 /// 278 /// Specifications are build upon the following scheme, where each component 279 /// is optional (indicated by []), but the order is obligatory. 280 /// "[-platform][-architecture][-compiler]" 281 /// 282 /// So the following strings are valid specifications: 283 /// "-windows-x86-dmd" 284 /// "-dmd" 285 /// "-arm" 286 /// "-arm-dmd" 287 /// "-windows-dmd" 288 /// 289 /// Params: 290 /// specification = The specification being matched. It must be the empty string or start with a dash. 291 /// 292 /// Returns: 293 /// true if the given specification matches this BuildPlatform, false otherwise. (The empty string matches) 294 /// 295 bool matchesSpecification(const(char)[] specification) const { 296 if (specification.empty) 297 return true; 298 auto splitted=specification.splitter('-'); 299 assert(!splitted.empty, "No valid platform specification! The leading hyphen is required!"); 300 splitted.popFront(); // Drop leading empty match. 301 enforce(!splitted.empty, "Platform specification if present, must not be empty!"); 302 if (platform.canFind(splitted.front)) { 303 splitted.popFront(); 304 if(splitted.empty) 305 return true; 306 } 307 if (architecture.canFind(splitted.front)) { 308 splitted.popFront(); 309 if(splitted.empty) 310 return true; 311 } 312 if (compiler == splitted.front) { 313 splitted.popFront(); 314 enforce(splitted.empty, "No valid specification! The compiler has to be the last element!"); 315 return true; 316 } 317 return false; 318 } 319 unittest { 320 auto platform=BuildPlatform(["posix", "linux"], ["x86_64"], "dmd"); 321 assert(platform.matchesSpecification("-posix")); 322 assert(platform.matchesSpecification("-linux")); 323 assert(platform.matchesSpecification("-linux-dmd")); 324 assert(platform.matchesSpecification("-linux-x86_64-dmd")); 325 assert(platform.matchesSpecification("-x86_64")); 326 assert(!platform.matchesSpecification("-windows")); 327 assert(!platform.matchesSpecification("-ldc")); 328 assert(!platform.matchesSpecification("-windows-dmd")); 329 } 330 } 331 332 enum BuildSetting { 333 dflags = 1<<0, 334 lflags = 1<<1, 335 libs = 1<<2, 336 sourceFiles = 1<<3, 337 copyFiles = 1<<4, 338 versions = 1<<5, 339 debugVersions = 1<<6, 340 importPaths = 1<<7, 341 stringImportPaths = 1<<8, 342 options = 1<<9, 343 none = 0, 344 commandLine = dflags|copyFiles, 345 commandLineSeparate = commandLine|lflags, 346 all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|stringImportPaths|options, 347 noOptions = all & ~options 348 } 349 350 enum TargetType { 351 autodetect, 352 none, 353 executable, 354 library, 355 sourceLibrary, 356 dynamicLibrary, 357 staticLibrary 358 } 359 360 enum BuildRequirements { 361 none = 0, /// No special requirements 362 allowWarnings = 1<<0, /// Warnings do not abort compilation 363 silenceWarnings = 1<<1, /// Don't show warnings 364 disallowDeprecations = 1<<2, /// Using deprecated features aborts compilation 365 silenceDeprecations = 1<<3, /// Don't show deprecation warnings 366 disallowInlining = 1<<4, /// Avoid function inlining, even in release builds 367 disallowOptimization = 1<<5, /// Avoid optimizations, even in release builds 368 requireBoundsCheck = 1<<6, /// Always perform bounds checks 369 requireContracts = 1<<7, /// Leave assertions and contracts enabled in release builds 370 relaxProperties = 1<<8, /// DEPRECATED: Do not enforce strict property handling (-property) 371 noDefaultFlags = 1<<9, /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes 372 } 373 374 enum BuildOptions { 375 none = 0, /// Use compiler defaults 376 debug_ = 1<<0, /// Compile in debug mode (enables contracts, -debug) 377 release = 1<<1, /// Compile in release mode (disables assertions and bounds checks, -release) 378 coverage = 1<<2, /// Enable code coverage analysis (-cov) 379 debugInfo = 1<<3, /// Enable symbolic debug information (-g) 380 debugInfoC = 1<<4, /// Enable symbolic debug information in C compatible form (-gc) 381 alwaysStackFrame = 1<<5, /// Always generate a stack frame (-gs) 382 stackStomping = 1<<6, /// Perform stack stomping (-gx) 383 inline = 1<<7, /// Perform function inlining (-inline) 384 noBoundsChecks = 1<<8, /// Disable all bounds checking (-noboundscheck) 385 optimize = 1<<9, /// Enable optimizations (-O) 386 profile = 1<<10, /// Emit profiling code (-profile) 387 unittests = 1<<11, /// Compile unit tests (-unittest) 388 verbose = 1<<12, /// Verbose compiler output (-v) 389 ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore) 390 syntaxOnly = 1<<14, /// Don't generate object files (-o-) 391 warnings = 1<<15, /// Enable warnings (-wi) 392 warningsAsErrors = 1<<16, /// Treat warnings as errors (-w) 393 ignoreDeprecations = 1<<17, /// Do not warn about using deprecated features (-d) 394 deprecationWarnings = 1<<18, /// Warn about using deprecated features (-dw) 395 deprecationErrors = 1<<19, /// Stop compilation upon usage of deprecated features (-de) 396 property = 1<<20, /// DEPRECATED: Enforce property syntax (-property) 397 } 398 399 string getTargetFileName(in BuildSettings settings, in BuildPlatform platform) 400 { 401 assert(settings.targetName.length > 0, "No target name set."); 402 final switch (settings.targetType) { 403 case TargetType.autodetect: assert(false, "Configurations must have a concrete target type."); 404 case TargetType.none: return null; 405 case TargetType.sourceLibrary: return null; 406 case TargetType.executable: 407 if( platform.platform.canFind("windows") ) 408 return settings.targetName ~ ".exe"; 409 else return settings.targetName; 410 case TargetType.library: 411 case TargetType.staticLibrary: 412 if( platform.platform.canFind("windows") ) 413 return settings.targetName ~ ".lib"; 414 else return "lib" ~ settings.targetName ~ ".a"; 415 case TargetType.dynamicLibrary: 416 if( platform.platform.canFind("windows") ) 417 return settings.targetName ~ ".dll"; 418 else return "lib" ~ settings.targetName ~ ".so"; 419 } 420 } 421 422 423 424 private { 425 Compiler[] s_compilers; 426 }