1 /******************************************************************************* 2 3 Contains struct definition for settings.json files 4 5 User settings are file that allow to configure dub default behavior. 6 7 *******************************************************************************/ 8 9 module dub.data.settings; 10 11 import dub.internal.configy.Attributes; 12 import dub.internal.vibecompat.inet.path; 13 14 /// Determines which of the default package suppliers are queried for packages. 15 public enum SkipPackageSuppliers { 16 none, /// Uses all configured package suppliers. 17 standard, /// Does not use the default package suppliers (`defaultPackageSuppliers`). 18 configured, /// Does not use default suppliers or suppliers configured in DUB's configuration file 19 all, /// Uses only manually specified package suppliers. 20 } 21 22 /** 23 * User-provided settings (configuration) 24 * 25 * All fields in this struct should be optional. 26 * Fields that are *not* optional should be mandatory from the POV 27 * of the application, not the POV of file parsing. 28 * For example, git's `core.author` and `core.email` are required to commit, 29 * but the error happens on the commit, not when the gitconfig is parsed. 30 * 31 * We have multiple configuration locations, and two kinds of fields: 32 * additive and non-additive. Additive fields are fields which are the union 33 * of all configuration files (e.g. `registryURLs`). Non-additive fields 34 * will ignore values set in lower priorities configuration, although parsing 35 * must still succeed. Additive fields are marked as `@Optional`, 36 * non-additive are marked as `SetInfo`. 37 */ 38 package(dub) struct Settings { 39 @Optional string[] registryUrls; 40 @Optional NativePath[] customCachePaths; 41 42 SetInfo!(SkipPackageSuppliers) skipRegistry; 43 SetInfo!(string) defaultCompiler; 44 SetInfo!(string) defaultArchitecture; 45 SetInfo!(bool) defaultLowMemory; 46 47 SetInfo!(string[string]) defaultEnvironments; 48 SetInfo!(string[string]) defaultBuildEnvironments; 49 SetInfo!(string[string]) defaultRunEnvironments; 50 SetInfo!(string[string]) defaultPreGenerateEnvironments; 51 SetInfo!(string[string]) defaultPostGenerateEnvironments; 52 SetInfo!(string[string]) defaultPreBuildEnvironments; 53 SetInfo!(string[string]) defaultPostBuildEnvironments; 54 SetInfo!(string[string]) defaultPreRunEnvironments; 55 SetInfo!(string[string]) defaultPostRunEnvironments; 56 SetInfo!(string) dubHome; 57 58 /// Merge a lower priority config (`this`) with a `higher` priority config 59 public Settings merge(Settings higher) 60 return @safe pure nothrow 61 { 62 import std.traits : hasUDA; 63 Settings result; 64 65 static foreach (idx, _; Settings.tupleof) { 66 static if (hasUDA!(Settings.tupleof[idx], Optional)) 67 result.tupleof[idx] = higher.tupleof[idx] ~ this.tupleof[idx]; 68 else static if (IsSetInfo!(typeof(this.tupleof[idx]))) { 69 if (higher.tupleof[idx].set) 70 result.tupleof[idx] = higher.tupleof[idx]; 71 else 72 result.tupleof[idx] = this.tupleof[idx]; 73 } else 74 static assert(false, 75 "Expect `@Optional` or `SetInfo` on: `" ~ 76 __traits(identifier, this.tupleof[idx]) ~ 77 "` of type : `" ~ 78 typeof(this.tupleof[idx]).stringof ~ "`"); 79 } 80 81 return result; 82 } 83 84 /// Workaround multiple `E` declaration in `static foreach` when inline 85 private template IsSetInfo(T) { enum bool IsSetInfo = is(T : SetInfo!E, E); } 86 } 87 88 unittest { 89 import dub.internal.configy.Read; 90 91 const str1 = `{ 92 "registryUrls": [ "http://foo.bar\/optional\/escape" ], 93 "customCachePaths": [ "foo/bar", "foo/foo" ], 94 95 "skipRegistry": "all", 96 "defaultCompiler": "dmd", 97 "defaultArchitecture": "fooarch", 98 "defaultLowMemory": false, 99 100 "defaultEnvironments": { 101 "VAR2": "settings.VAR2", 102 "VAR3": "settings.VAR3", 103 "VAR4": "settings.VAR4" 104 } 105 }`; 106 107 const str2 = `{ 108 "registryUrls": [ "http://bar.foo" ], 109 "customCachePaths": [ "bar/foo", "bar/bar" ], 110 111 "skipRegistry": "none", 112 "defaultCompiler": "ldc", 113 "defaultArchitecture": "bararch", 114 "defaultLowMemory": true, 115 116 "defaultEnvironments": { 117 "VAR": "Hi", 118 } 119 }`; 120 121 auto c1 = parseConfigString!Settings(str1, "/dev/null"); 122 assert(c1.registryUrls == [ "http://foo.bar/optional/escape" ]); 123 assert(c1.customCachePaths == [ NativePath("foo/bar"), NativePath("foo/foo") ]); 124 assert(c1.skipRegistry == SkipPackageSuppliers.all); 125 assert(c1.defaultCompiler == "dmd"); 126 assert(c1.defaultArchitecture == "fooarch"); 127 assert(c1.defaultLowMemory == false); 128 assert(c1.defaultEnvironments.length == 3); 129 assert(c1.defaultEnvironments["VAR2"] == "settings.VAR2"); 130 assert(c1.defaultEnvironments["VAR3"] == "settings.VAR3"); 131 assert(c1.defaultEnvironments["VAR4"] == "settings.VAR4"); 132 133 auto c2 = parseConfigString!Settings(str2, "/dev/null"); 134 assert(c2.registryUrls == [ "http://bar.foo" ]); 135 assert(c2.customCachePaths == [ NativePath("bar/foo"), NativePath("bar/bar") ]); 136 assert(c2.skipRegistry == SkipPackageSuppliers.none); 137 assert(c2.defaultCompiler == "ldc"); 138 assert(c2.defaultArchitecture == "bararch"); 139 assert(c2.defaultLowMemory == true); 140 assert(c2.defaultEnvironments.length == 1); 141 assert(c2.defaultEnvironments["VAR"] == "Hi"); 142 143 auto m1 = c2.merge(c1); 144 // c1 takes priority, so its registryUrls is first 145 assert(m1.registryUrls == [ "http://foo.bar/optional/escape", "http://bar.foo" ]); 146 // Same with CCP 147 assert(m1.customCachePaths == [ 148 NativePath("foo/bar"), NativePath("foo/foo"), 149 NativePath("bar/foo"), NativePath("bar/bar"), 150 ]); 151 152 // c1 fields only 153 assert(m1.skipRegistry == c1.skipRegistry); 154 assert(m1.defaultCompiler == c1.defaultCompiler); 155 assert(m1.defaultArchitecture == c1.defaultArchitecture); 156 assert(m1.defaultLowMemory == c1.defaultLowMemory); 157 assert(m1.defaultEnvironments == c1.defaultEnvironments); 158 159 auto m2 = c1.merge(c2); 160 assert(m2.registryUrls == [ "http://bar.foo", "http://foo.bar/optional/escape" ]); 161 assert(m2.customCachePaths == [ 162 NativePath("bar/foo"), NativePath("bar/bar"), 163 NativePath("foo/bar"), NativePath("foo/foo"), 164 ]); 165 assert(m2.skipRegistry == c2.skipRegistry); 166 assert(m2.defaultCompiler == c2.defaultCompiler); 167 assert(m2.defaultArchitecture == c2.defaultArchitecture); 168 assert(m2.defaultLowMemory == c2.defaultLowMemory); 169 assert(m2.defaultEnvironments == c2.defaultEnvironments); 170 171 auto m3 = Settings.init.merge(c1); 172 assert(m3 == c1); 173 }