1 /** 2 Build settings definitions. 3 4 Copyright: © 2013-2014 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.buildsettings; 9 10 import dub.internal.vibecompat.inet.path; 11 12 import dub.internal.configy.Attributes; 13 14 import std.array : array; 15 import std.algorithm : filter, any; 16 import std.path : globMatch; 17 import std.typecons : BitFlags; 18 import std.algorithm.iteration : uniq; 19 import std.range : chain; 20 21 /// BuildPlatform specific settings, like needed libraries or additional 22 /// include paths. 23 struct BuildSettings { 24 import dub.internal.vibecompat.data.serialization : byName; 25 26 TargetType targetType; 27 string targetPath; 28 string targetName; 29 string workingDirectory; 30 string mainSourceFile; 31 string[] dflags; 32 string[] lflags; 33 string[] libs; 34 string[] linkerFiles; 35 string[] sourceFiles; 36 string[] injectSourceFiles; 37 string[] copyFiles; 38 string[] extraDependencyFiles; 39 string[] versions; 40 string[] debugVersions; 41 string[] versionFilters; 42 string[] debugVersionFilters; 43 string[] importPaths; 44 string[] cImportPaths; 45 string[] stringImportPaths; 46 string[] importFiles; 47 string[] stringImportFiles; 48 string[] preGenerateCommands; 49 string[] postGenerateCommands; 50 string[] preBuildCommands; 51 string[] postBuildCommands; 52 string[] preRunCommands; 53 string[] postRunCommands; 54 string[string] environments; 55 string[string] buildEnvironments; 56 string[string] runEnvironments; 57 string[string] preGenerateEnvironments; 58 string[string] postGenerateEnvironments; 59 string[string] preBuildEnvironments; 60 string[string] postBuildEnvironments; 61 string[string] preRunEnvironments; 62 string[string] postRunEnvironments; 63 @byName Flags!BuildRequirement requirements; 64 @byName Flags!BuildOption options; 65 66 BuildSettings dup() 67 const { 68 import std.traits: FieldNameTuple; 69 import std.algorithm: map; 70 import std.typecons: tuple; 71 import std.array: assocArray; 72 BuildSettings ret; 73 foreach (m; FieldNameTuple!BuildSettings) { 74 static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m).dup))) 75 __traits(getMember, ret, m) = __traits(getMember, this, m).dup; 76 else static if (is(typeof(add(__traits(getMember, ret, m), __traits(getMember, this, m))))) 77 add(__traits(getMember, ret, m), __traits(getMember, this, m)); 78 else static if (is(typeof(__traits(getMember, ret, m) = __traits(getMember, this, m)))) 79 __traits(getMember, ret, m) = __traits(getMember, this, m); 80 else static assert(0, "Cannot duplicate BuildSettings." ~ m); 81 } 82 assert(ret.targetType == targetType); 83 assert(ret.targetName == targetName); 84 assert(ret.importPaths == importPaths); 85 assert(ret.cImportPaths == cImportPaths); 86 return ret; 87 } 88 89 /** 90 * Merges $(LREF bs) onto `this` BuildSettings instance. This is called for 91 * sourceLibrary dependencies when they are included in the build to be 92 * merged into the root package build settings as well as configuring 93 * targets for different build types such as `release` or `unittest-cov`. 94 */ 95 void add(in BuildSettings bs) 96 { 97 addDFlags(bs.dflags); 98 addLFlags(bs.lflags); 99 addLibs(bs.libs); 100 addLinkerFiles(bs.linkerFiles); 101 addSourceFiles(bs.sourceFiles); 102 addInjectSourceFiles(bs.injectSourceFiles); 103 addCopyFiles(bs.copyFiles); 104 addExtraDependencyFiles(bs.extraDependencyFiles); 105 addVersions(bs.versions); 106 addDebugVersions(bs.debugVersions); 107 addVersionFilters(bs.versionFilters); 108 addDebugVersionFilters(bs.debugVersionFilters); 109 addImportPaths(bs.importPaths); 110 addCImportPaths(bs.cImportPaths); 111 addStringImportPaths(bs.stringImportPaths); 112 addImportFiles(bs.importFiles); 113 addStringImportFiles(bs.stringImportFiles); 114 addPreGenerateCommands(bs.preGenerateCommands); 115 addPostGenerateCommands(bs.postGenerateCommands); 116 addPreBuildCommands(bs.preBuildCommands); 117 addPostBuildCommands(bs.postBuildCommands); 118 addPreRunCommands(bs.preRunCommands); 119 addPostRunCommands(bs.postRunCommands); 120 addEnvironments(bs.environments); 121 addBuildEnvironments(bs.buildEnvironments); 122 addRunEnvironments(bs.runEnvironments); 123 addPreGenerateEnvironments(bs.preGenerateEnvironments); 124 addPostGenerateEnvironments(bs.postGenerateEnvironments); 125 addPreBuildEnvironments(bs.preBuildEnvironments); 126 addPostBuildEnvironments(bs.postBuildEnvironments); 127 addPreRunEnvironments(bs.preRunEnvironments); 128 addPostRunEnvironments(bs.postRunEnvironments); 129 addRequirements(bs.requirements); 130 addOptions(bs.options); 131 } 132 133 void addDFlags(in string[] value...) { dflags = chain(dflags, value.dup).uniq.array; } 134 void prependDFlags(in string[] value...) { prepend(dflags, value); } 135 void removeDFlags(in string[] value...) { remove(dflags, value); } 136 void addLFlags(in string[] value...) { lflags ~= value; } 137 void prependLFlags(in string[] value...) { prepend(lflags, value, false); } 138 void addLibs(in string[] value...) { add(libs, value); } 139 void addLinkerFiles(in string[] value...) { add(linkerFiles, value); } 140 void addSourceFiles(in string[] value...) { add(sourceFiles, value); } 141 void prependSourceFiles(in string[] value...) { prepend(sourceFiles, value); } 142 void removeSourceFiles(in string[] value...) { removePaths(sourceFiles, value); } 143 void addInjectSourceFiles(in string[] value...) { add(injectSourceFiles, value); } 144 void addCopyFiles(in string[] value...) { add(copyFiles, value); } 145 void addExtraDependencyFiles(in string[] value...) { add(extraDependencyFiles, value); } 146 void addVersions(in string[] value...) { add(versions, value); } 147 void addDebugVersions(in string[] value...) { add(debugVersions, value); } 148 void addVersionFilters(in string[] value...) { add(versionFilters, value); } 149 void addDebugVersionFilters(in string[] value...) { add(debugVersionFilters, value); } 150 void addImportPaths(in string[] value...) { add(importPaths, value); } 151 void addCImportPaths(in string[] value...) { add(cImportPaths, value); } 152 void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); } 153 void prependStringImportPaths(in string[] value...) { prepend(stringImportPaths, value); } 154 void addImportFiles(in string[] value...) { add(importFiles, value); } 155 void addStringImportFiles(in string[] value...) { addSI(stringImportFiles, value); } 156 void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); } 157 void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); } 158 void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); } 159 void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); } 160 void addPreRunCommands(in string[] value...) { add(preRunCommands, value, false); } 161 void addPostRunCommands(in string[] value...) { add(postRunCommands, value, false); } 162 void addEnvironments(in string[string] value) { add(environments, value); } 163 void updateEnvironments(in string[string] value) { update(environments, value); } 164 void addBuildEnvironments(in string[string] value) { add(buildEnvironments, value); } 165 void updateBuildEnvironments(in string[string] value) { update(buildEnvironments, value); } 166 void addRunEnvironments(in string[string] value) { add(runEnvironments, value); } 167 void updateRunEnvironments(in string[string] value) { update(runEnvironments, value); } 168 void addPreGenerateEnvironments(in string[string] value) { add(preGenerateEnvironments, value); } 169 void updatePreGenerateEnvironments(in string[string] value) { update(preGenerateEnvironments, value); } 170 void addPostGenerateEnvironments(in string[string] value) { add(postGenerateEnvironments, value); } 171 void updatePostGenerateEnvironments(in string[string] value) { update(postGenerateEnvironments, value); } 172 void addPreBuildEnvironments(in string[string] value) { add(preBuildEnvironments, value); } 173 void updatePreBuildEnvironments(in string[string] value) { update(preBuildEnvironments, value); } 174 void addPostBuildEnvironments(in string[string] value) { add(postBuildEnvironments, value); } 175 void updatePostBuildEnvironments(in string[string] value) { update(postBuildEnvironments, value); } 176 void addPreRunEnvironments(in string[string] value) { add(preRunEnvironments, value); } 177 void updatePreRunEnvironments(in string[string] value) { update(preRunEnvironments, value); } 178 void addPostRunEnvironments(in string[string] value) { add(postRunEnvironments, value); } 179 void updatePostRunEnvironments(in string[string] value) { update(postRunEnvironments, value); } 180 void addRequirements(in BuildRequirement[] value...) { foreach (v; value) this.requirements |= v; } 181 void addRequirements(in Flags!BuildRequirement value) { this.requirements |= value; } 182 void addOptions(in BuildOption[] value...) { foreach (v; value) this.options |= v; } 183 void addOptions(in Flags!BuildOption value) { this.options |= value; } 184 void removeOptions(in BuildOption[] value...) { foreach (v; value) this.options &= ~v; } 185 void removeOptions(in Flags!BuildOption value) { this.options &= ~value; } 186 187 private: 188 static auto filterDuplicates(T)(ref string[] arr, in T vals, bool noDuplicates = true) 189 { 190 return noDuplicates 191 ? vals.filter!(filtered => !arr.any!(item => item == filtered)).array 192 : vals; 193 } 194 195 // Append `vals` to `arr` without adding duplicates. 196 static void add(ref string[] arr, in string[] vals, bool noDuplicates = true) 197 { 198 // vals might contain duplicates, add each val individually 199 foreach (val; vals) 200 arr ~= filterDuplicates(arr, [val], noDuplicates); 201 } 202 // Append `vals` to `aa` 203 static void add(ref string[string] aa, in string[string] vals) 204 { 205 // vals might contain duplicated keys, add each val individually 206 foreach (key, val; vals) 207 if (key !in aa) 208 aa[key] = val; 209 } 210 // Update `vals` to `aa` 211 static void update(ref string[string] aa, in string[string] vals) 212 { 213 // If there are duplicate keys, they will be ignored and overwritten. 214 foreach (key, val; vals) 215 aa[key] = val; 216 } 217 218 unittest 219 { 220 auto ary = ["-dip1000", "-vgc"]; 221 BuildSettings.add(ary, ["-dip1000", "-vgc"]); 222 assert(ary == ["-dip1000", "-vgc"]); 223 BuildSettings.add(ary, ["-dip1001", "-vgc"], false); 224 assert(ary == ["-dip1000", "-vgc", "-dip1001", "-vgc"]); 225 BuildSettings.add(ary, ["-dupflag", "-notdupflag", "-dupflag"]); 226 assert(ary == ["-dip1000", "-vgc", "-dip1001", "-vgc", "-dupflag", "-notdupflag"]); 227 } 228 229 // Prepend `arr` by `vals` without adding duplicates. 230 static void prepend(ref string[] arr, in string[] vals, bool noDuplicates = true) 231 { 232 import std.range : retro; 233 // vals might contain duplicates, add each val individually 234 foreach (val; vals.retro) 235 arr = filterDuplicates(arr, [val], noDuplicates) ~ arr; 236 } 237 238 unittest 239 { 240 auto ary = ["-dip1000", "-vgc"]; 241 BuildSettings.prepend(ary, ["-dip1000", "-vgc"]); 242 assert(ary == ["-dip1000", "-vgc"]); 243 BuildSettings.prepend(ary, ["-dip1001", "-vgc"], false); 244 assert(ary == ["-dip1001", "-vgc", "-dip1000", "-vgc"]); 245 BuildSettings.prepend(ary, ["-dupflag", "-notdupflag", "-dupflag"]); 246 assert(ary == ["-notdupflag", "-dupflag", "-dip1001", "-vgc", "-dip1000", "-vgc"]); 247 } 248 249 // add string import files (avoids file name duplicates in addition to path duplicates) 250 static void addSI(ref string[] arr, in string[] vals) 251 { 252 bool[string] existing; 253 foreach (v; arr) existing[NativePath(v).head.name] = true; 254 foreach (v; vals) { 255 auto s = NativePath(v).head.name; 256 if (s !in existing) { 257 existing[s] = true; 258 arr ~= v; 259 } 260 } 261 } 262 263 unittest 264 { 265 auto ary = ["path/foo.txt"]; 266 BuildSettings.addSI(ary, ["path2/foo2.txt"]); 267 assert(ary == ["path/foo.txt", "path2/foo2.txt"]); 268 BuildSettings.addSI(ary, ["path2/foo.txt"]); // no duplicate basenames 269 assert(ary == ["path/foo.txt", "path2/foo2.txt"]); 270 } 271 272 static bool pathMatch(string path, string pattern) 273 { 274 import std.functional : memoize; 275 276 alias nativePath = memoize!((string stringPath) => NativePath(stringPath)); 277 278 return nativePath(path) == nativePath(pattern) || globMatch(path, pattern); 279 } 280 281 static void removeValuesFromArray(alias Match)(ref string[] arr, in string[] vals) 282 { 283 bool matches(string s) 284 { 285 return vals.any!(item => Match(s, item)); 286 } 287 arr = arr.filter!(s => !matches(s)).array; 288 } 289 290 static void removePaths(ref string[] arr, in string[] vals) 291 { 292 removeValuesFromArray!(pathMatch)(arr, vals); 293 } 294 295 unittest 296 { 297 auto ary = ["path1", "root/path1", "root/path2", "root2/path1"]; 298 BuildSettings.removePaths(ary, ["path1"]); 299 assert(ary == ["root/path1", "root/path2", "root2/path1"]); 300 BuildSettings.removePaths(ary, ["*/path1"]); 301 assert(ary == ["root/path2"]); 302 BuildSettings.removePaths(ary, ["foo", "bar", "root/path2"]); 303 assert(ary == []); 304 } 305 306 static void remove(ref string[] arr, in string[] vals) 307 { 308 removeValuesFromArray!((a, b) => a == b)(arr, vals); 309 } 310 311 unittest 312 { 313 import std.string : join; 314 315 auto ary = ["path1", "root/path1", "root/path2", "root2/path1"]; 316 BuildSettings.remove(ary, ["path1"]); 317 assert(ary == ["root/path1", "root/path2", "root2/path1"]); 318 BuildSettings.remove(ary, ["root/path*"]); 319 assert(ary == ["root/path1", "root/path2", "root2/path1"]); 320 BuildSettings.removePaths(ary, ["foo", "root/path2", "bar", "root2/path1"]); 321 assert(ary == ["root/path1"]); 322 BuildSettings.remove(ary, ["root/path1", "foo"]); 323 assert(ary == []); 324 } 325 } 326 327 enum BuildSetting { 328 dflags = 1<<0, 329 lflags = 1<<1, 330 libs = 1<<2, 331 sourceFiles = 1<<3, 332 copyFiles = 1<<4, 333 versions = 1<<5, 334 debugVersions = 1<<6, 335 importPaths = 1<<7, 336 cImportPaths = 1<<8, 337 stringImportPaths = 1<<9, 338 options = 1<<10, 339 none = 0, 340 commandLine = dflags|copyFiles, 341 commandLineSeparate = commandLine|lflags, 342 all = dflags|lflags|libs|sourceFiles|copyFiles|versions|debugVersions|importPaths|cImportPaths|stringImportPaths|options, 343 noOptions = all & ~options 344 } 345 346 enum TargetType { 347 autodetect, 348 none, 349 executable, 350 library, 351 sourceLibrary, 352 dynamicLibrary, 353 staticLibrary, 354 object 355 } 356 357 enum BuildRequirement { 358 none = 0, /// No special requirements 359 allowWarnings = 1<<0, /// Warnings do not abort compilation 360 silenceWarnings = 1<<1, /// Don't show warnings 361 disallowDeprecations = 1<<2, /// Using deprecated features aborts compilation 362 silenceDeprecations = 1<<3, /// Don't show deprecation warnings 363 disallowInlining = 1<<4, /// Avoid function inlining, even in release builds 364 disallowOptimization = 1<<5, /// Avoid optimizations, even in release builds 365 requireBoundsCheck = 1<<6, /// Always perform bounds checks 366 requireContracts = 1<<7, /// Leave assertions and contracts enabled in release builds 367 relaxProperties = 1<<8, /// DEPRECATED: Do not enforce strict property handling (-property) 368 noDefaultFlags = 1<<9, /// Do not issue any of the default build flags (e.g. -debug, -w, -property etc.) - use only for development purposes 369 } 370 371 enum BuildOption { 372 none = 0, /// Use compiler defaults 373 debugMode = 1<<0, /// Compile in debug mode (enables contracts, -debug) 374 releaseMode = 1<<1, /// Compile in release mode (disables assertions and bounds checks, -release) 375 coverage = 1<<2, /// Enable code coverage analysis (-cov) 376 debugInfo = 1<<3, /// Enable symbolic debug information (-g) 377 debugInfoC = 1<<4, /// Enable symbolic debug information in C compatible form (-gc) 378 alwaysStackFrame = 1<<5, /// Always generate a stack frame (-gs) 379 stackStomping = 1<<6, /// Perform stack stomping (-gx) 380 inline = 1<<7, /// Perform function inlining (-inline) 381 noBoundsCheck = 1<<8, /// Disable all bounds checking (-noboundscheck) 382 optimize = 1<<9, /// Enable optimizations (-O) 383 profile = 1<<10, /// Emit profiling code (-profile) 384 unittests = 1<<11, /// Compile unit tests (-unittest) 385 verbose = 1<<12, /// Verbose compiler output (-v) 386 ignoreUnknownPragmas = 1<<13, /// Ignores unknown pragmas during compilation (-ignore) 387 syntaxOnly = 1<<14, /// Don't generate object files (-o-) 388 warnings = 1<<15, /// Enable warnings (-wi) 389 warningsAsErrors = 1<<16, /// Treat warnings as errors (-w) 390 ignoreDeprecations = 1<<17, /// Do not warn about using deprecated features (-d) 391 deprecationWarnings = 1<<18, /// Warn about using deprecated features (-dw) 392 deprecationErrors = 1<<19, /// Stop compilation upon usage of deprecated features (-de) 393 property = 1<<20, /// DEPRECATED: Enforce property syntax (-property) 394 profileGC = 1<<21, /// Profile runtime allocations 395 pic = 1<<22, /// Generate position independent code 396 betterC = 1<<23, /// Compile in betterC mode (-betterC) 397 lowmem = 1<<24, /// Compile in low-memory mode (-lowmem) 398 coverageCTFE = 1<<25, /// Enable code coverage analysis including at compile-time (-cov=ctfe) 399 color = 1<<26, /// Colorize output (-color) 400 401 // for internal usage 402 _docs = 1<<27, // Write ddoc to docs 403 _ddox = 1<<28, // Compile docs.json 404 } 405 406 struct Flags (T) { 407 import dub.internal.vibecompat.data.serialization : ignore; 408 import dub.internal.vibecompat.data.json : Json; 409 410 @ignore BitFlags!T values; 411 412 public this(T opt) @safe pure nothrow @nogc 413 { 414 this.values = opt; 415 } 416 417 public this(BitFlags!T v) @safe pure nothrow @nogc 418 { 419 this.values = v; 420 } 421 422 alias values this; 423 424 public Json toJson() const 425 { 426 import std.conv : to; 427 import std.traits : EnumMembers; 428 429 auto json = Json.emptyArray; 430 431 static foreach (em; EnumMembers!T) { 432 static if (em != 0) { 433 if (values & em) { 434 json ~= em.to!string; 435 } 436 } 437 } 438 439 return json; 440 } 441 442 public static Flags!T fromJson(Json json) 443 { 444 import std.conv : to; 445 import std.exception : enforce; 446 447 BitFlags!T flags; 448 449 enforce(json.type == Json.Type.array, "Should be an array"); 450 foreach (jval; json) { 451 flags |= jval.get!string.to!T; 452 } 453 454 return Flags!T(flags); 455 } 456 457 /** 458 * Reads a list of flags from a JSON/YAML document and converts them 459 * to our internal representation. 460 * 461 * Flags inside of dub code are stored as a `BitFlags`, 462 * but they are specified in the recipe using an array of their name. 463 * This routine handles the conversion from `string[]` to `BitFlags!T`. 464 */ 465 public static Flags!T fromYAML (scope ConfigParser!(Flags!T) p) 466 { 467 import dub.internal.dyaml.node; 468 import std.exception; 469 import std.conv; 470 471 enforce(p.node.nodeID == NodeID.sequence, "Should be a sequence"); 472 typeof(return) res; 473 foreach (str; p.node.sequence) 474 res |= str.as!string.to!T; 475 return res; 476 } 477 } 478 479 unittest 480 { 481 import dub.internal.vibecompat.data.json; 482 483 auto opts = Flags!BuildOption(BuildOption.debugMode | BuildOption.debugInfo | BuildOption.warningsAsErrors); 484 const str = serializeToJsonString(opts); 485 assert(str == `["debugMode","debugInfo","warningsAsErrors"]`); 486 assert(deserializeJson!(typeof(opts))(str) == opts); 487 } 488 489 unittest 490 { 491 import dub.internal.configy.Read; 492 493 static struct Config 494 { 495 Flags!BuildRequirement flags; 496 } 497 498 auto c = parseConfigString!Config(` 499 { 500 "flags": [ "allowWarnings", "noDefaultFlags", "disallowInlining" ] 501 } 502 `, __FILE__); 503 assert(c.flags.allowWarnings); 504 c.flags.allowWarnings = false; 505 assert(c.flags.noDefaultFlags); 506 c.flags.noDefaultFlags = false; 507 assert(c.flags.disallowInlining); 508 c.flags.disallowInlining = false; 509 assert(c.flags == c.flags.init); 510 } 511 512 /** 513 All build options that will be inherited upwards in the dependency graph 514 515 Build options in this category fulfill one of the following properties: 516 $(UL 517 $(LI The option affects the semantics of the generated code) 518 $(LI The option affects if a certain piece of code is valid or not) 519 $(LI The option enabled meta information in dependent projects that are useful for the dependee (e.g. debug information)) 520 ) 521 */ 522 enum Flags!BuildOption inheritedBuildOptions = 523 BuildOption.debugMode | BuildOption.releaseMode 524 | BuildOption.coverage | BuildOption.coverageCTFE | BuildOption.debugInfo | BuildOption.debugInfoC 525 | BuildOption.alwaysStackFrame | BuildOption.stackStomping | BuildOption.inline 526 | BuildOption.noBoundsCheck | BuildOption.profile | BuildOption.ignoreUnknownPragmas 527 | BuildOption.syntaxOnly | BuildOption.warnings | BuildOption.warningsAsErrors 528 | BuildOption.ignoreDeprecations | BuildOption.deprecationWarnings 529 | BuildOption.deprecationErrors | BuildOption.property | BuildOption.profileGC 530 | BuildOption.pic; 531 532 deprecated("Use `Flags!BuildOption` instead") 533 public alias BuildOptions = Flags!BuildOption; 534 535 deprecated("Use `Flags!BuildRequirement` instead") 536 public alias BuildRequirements = Flags!BuildRequirement;