Newer
Older
dub_jkp / source / dub / data / settings.d
  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. }