diff --git a/build-files.txt b/build-files.txt index 893d9c8..1944066 100644 --- a/build-files.txt +++ b/build-files.txt @@ -6,6 +6,7 @@ source/dub/compilers/gdc.d source/dub/compilers/ldc.d source/dub/compilers/utils.d +source/dub/data/settings.d source/dub/dependency.d source/dub/dependencyresolver.d source/dub/description.d diff --git a/source/dub/data/settings.d b/source/dub/data/settings.d new file mode 100644 index 0000000..6690a64 --- /dev/null +++ b/source/dub/data/settings.d @@ -0,0 +1,173 @@ +/******************************************************************************* + + Contains struct definition for settings.json files + + User settings are file that allow to configure dub default behavior. + +*******************************************************************************/ + +module dub.data.settings; + +import dub.internal.configy.Attributes; +import dub.internal.vibecompat.inet.path; + +/// Determines which of the default package suppliers are queried for packages. +public enum SkipPackageSuppliers { + none, /// Uses all configured package suppliers. + standard, /// Does not use the default package suppliers (`defaultPackageSuppliers`). + configured, /// Does not use default suppliers or suppliers configured in DUB's configuration file + all, /// Uses only manually specified package suppliers. +} + +/** + * User-provided settings (configuration) + * + * All fields in this struct should be optional. + * Fields that are *not* optional should be mandatory from the POV + * of the application, not the POV of file parsing. + * For example, git's `core.author` and `core.email` are required to commit, + * but the error happens on the commit, not when the gitconfig is parsed. + * + * We have multiple configuration locations, and two kinds of fields: + * additive and non-additive. Additive fields are fields which are the union + * of all configuration files (e.g. `registryURLs`). Non-additive fields + * will ignore values set in lower priorities configuration, although parsing + * must still succeed. Additive fields are marked as `@Optional`, + * non-additive are marked as `SetInfo`. + */ +package(dub) struct UserConfiguration { + @Optional string[] registryUrls; + @Optional NativePath[] customCachePaths; + + SetInfo!(SkipPackageSuppliers) skipRegistry; + SetInfo!(string) defaultCompiler; + SetInfo!(string) defaultArchitecture; + SetInfo!(bool) defaultLowMemory; + + SetInfo!(string[string]) defaultEnvironments; + SetInfo!(string[string]) defaultBuildEnvironments; + SetInfo!(string[string]) defaultRunEnvironments; + SetInfo!(string[string]) defaultPreGenerateEnvironments; + SetInfo!(string[string]) defaultPostGenerateEnvironments; + SetInfo!(string[string]) defaultPreBuildEnvironments; + SetInfo!(string[string]) defaultPostBuildEnvironments; + SetInfo!(string[string]) defaultPreRunEnvironments; + SetInfo!(string[string]) defaultPostRunEnvironments; + SetInfo!(string) dubHome; + + /// Merge a lower priority config (`this`) with a `higher` priority config + public UserConfiguration merge(UserConfiguration higher) + return @safe pure nothrow + { + import std.traits : hasUDA; + UserConfiguration result; + + static foreach (idx, _; UserConfiguration.tupleof) { + static if (hasUDA!(UserConfiguration.tupleof[idx], Optional)) + result.tupleof[idx] = higher.tupleof[idx] ~ this.tupleof[idx]; + else static if (IsSetInfo!(typeof(this.tupleof[idx]))) { + if (higher.tupleof[idx].set) + result.tupleof[idx] = higher.tupleof[idx]; + else + result.tupleof[idx] = this.tupleof[idx]; + } else + static assert(false, + "Expect `@Optional` or `SetInfo` on: `" ~ + __traits(identifier, this.tupleof[idx]) ~ + "` of type : `" ~ + typeof(this.tupleof[idx]).stringof ~ "`"); + } + + return result; + } + + /// Workaround multiple `E` declaration in `static foreach` when inline + private template IsSetInfo(T) { enum bool IsSetInfo = is(T : SetInfo!E, E); } +} + +unittest { + import dub.internal.configy.Read; + + const str1 = `{ + "registryUrls": [ "http://foo.bar\/optional\/escape" ], + "customCachePaths": [ "foo/bar", "foo/foo" ], + + "skipRegistry": "all", + "defaultCompiler": "dmd", + "defaultArchitecture": "fooarch", + "defaultLowMemory": false, + + "defaultEnvironments": { + "VAR2": "settings.VAR2", + "VAR3": "settings.VAR3", + "VAR4": "settings.VAR4" + } +}`; + + const str2 = `{ + "registryUrls": [ "http://bar.foo" ], + "customCachePaths": [ "bar/foo", "bar/bar" ], + + "skipRegistry": "none", + "defaultCompiler": "ldc", + "defaultArchitecture": "bararch", + "defaultLowMemory": true, + + "defaultEnvironments": { + "VAR": "Hi", + } +}`; + + auto c1 = parseConfigString!UserConfiguration(str1, "/dev/null"); + assert(c1.registryUrls == [ "http://foo.bar/optional/escape" ]); + assert(c1.customCachePaths == [ NativePath("foo/bar"), NativePath("foo/foo") ]); + assert(c1.skipRegistry == SkipPackageSuppliers.all); + assert(c1.defaultCompiler == "dmd"); + assert(c1.defaultArchitecture == "fooarch"); + assert(c1.defaultLowMemory == false); + assert(c1.defaultEnvironments.length == 3); + assert(c1.defaultEnvironments["VAR2"] == "settings.VAR2"); + assert(c1.defaultEnvironments["VAR3"] == "settings.VAR3"); + assert(c1.defaultEnvironments["VAR4"] == "settings.VAR4"); + + auto c2 = parseConfigString!UserConfiguration(str2, "/dev/null"); + assert(c2.registryUrls == [ "http://bar.foo" ]); + assert(c2.customCachePaths == [ NativePath("bar/foo"), NativePath("bar/bar") ]); + assert(c2.skipRegistry == SkipPackageSuppliers.none); + assert(c2.defaultCompiler == "ldc"); + assert(c2.defaultArchitecture == "bararch"); + assert(c2.defaultLowMemory == true); + assert(c2.defaultEnvironments.length == 1); + assert(c2.defaultEnvironments["VAR"] == "Hi"); + + auto m1 = c2.merge(c1); + // c1 takes priority, so its registryUrls is first + assert(m1.registryUrls == [ "http://foo.bar/optional/escape", "http://bar.foo" ]); + // Same with CCP + assert(m1.customCachePaths == [ + NativePath("foo/bar"), NativePath("foo/foo"), + NativePath("bar/foo"), NativePath("bar/bar"), + ]); + + // c1 fields only + assert(m1.skipRegistry == c1.skipRegistry); + assert(m1.defaultCompiler == c1.defaultCompiler); + assert(m1.defaultArchitecture == c1.defaultArchitecture); + assert(m1.defaultLowMemory == c1.defaultLowMemory); + assert(m1.defaultEnvironments == c1.defaultEnvironments); + + auto m2 = c1.merge(c2); + assert(m2.registryUrls == [ "http://bar.foo", "http://foo.bar/optional/escape" ]); + assert(m2.customCachePaths == [ + NativePath("bar/foo"), NativePath("bar/bar"), + NativePath("foo/bar"), NativePath("foo/foo"), + ]); + assert(m2.skipRegistry == c2.skipRegistry); + assert(m2.defaultCompiler == c2.defaultCompiler); + assert(m2.defaultArchitecture == c2.defaultArchitecture); + assert(m2.defaultLowMemory == c2.defaultLowMemory); + assert(m2.defaultEnvironments == c2.defaultEnvironments); + + auto m3 = UserConfiguration.init.merge(c1); + assert(m3 == c1); +} diff --git a/source/dub/dub.d b/source/dub/dub.d index 1f86444..501e6f6 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -8,6 +8,7 @@ module dub.dub; import dub.compilers.compiler; +import dub.data.settings : SPS = SkipPackageSuppliers, UserConfiguration; import dub.dependency; import dub.dependencyresolver; import dub.internal.utils; @@ -1481,12 +1482,7 @@ } /// Determines which of the default package suppliers are queried for packages. -enum SkipPackageSuppliers { - none, /// Uses all configured package suppliers. - standard, /// Does not use the default package suppliers (`defaultPackageSuppliers`). - configured, /// Does not use default suppliers or suppliers configured in DUB's configuration file - all /// Uses only manually specified package suppliers. -} +public alias SkipPackageSuppliers = SPS; private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) { protected { @@ -1847,158 +1843,3 @@ return result; } } - -/** - * User-provided configuration - * - * All fields in this struct should be optional. - * Fields that are *not* optional should be mandatory from the POV - * of the application, not the POV of file parsing. - * For example, git's `core.author` and `core.email` are required to commit, - * but the error happens on the commit, not when the gitconfig is parsed. - * - * We have multiple configuration locations, and two kinds of fields: - * additive and non-additive. Additive fields are fields which are the union - * of all configuration files (e.g. `registryURLs`). Non-additive fields - * will ignore values set in lower priorities configuration, although parsing - * must still succeed. Additive fields are marked as `@Optional`, - * non-additive are marked as `SetInfo`. - */ -private struct UserConfiguration { - import dub.internal.configy.Attributes : Optional, SetInfo; - - @Optional string[] registryUrls; - @Optional NativePath[] customCachePaths; - - SetInfo!(SkipPackageSuppliers) skipRegistry; - SetInfo!(string) defaultCompiler; - SetInfo!(string) defaultArchitecture; - SetInfo!(bool) defaultLowMemory; - - SetInfo!(string[string]) defaultEnvironments; - SetInfo!(string[string]) defaultBuildEnvironments; - SetInfo!(string[string]) defaultRunEnvironments; - SetInfo!(string[string]) defaultPreGenerateEnvironments; - SetInfo!(string[string]) defaultPostGenerateEnvironments; - SetInfo!(string[string]) defaultPreBuildEnvironments; - SetInfo!(string[string]) defaultPostBuildEnvironments; - SetInfo!(string[string]) defaultPreRunEnvironments; - SetInfo!(string[string]) defaultPostRunEnvironments; - SetInfo!(string) dubHome; - - /// Merge a lower priority config (`this`) with a `higher` priority config - public UserConfiguration merge(UserConfiguration higher) - return @safe pure nothrow - { - import std.traits : hasUDA; - UserConfiguration result; - - static foreach (idx, _; UserConfiguration.tupleof) { - static if (hasUDA!(UserConfiguration.tupleof[idx], Optional)) - result.tupleof[idx] = higher.tupleof[idx] ~ this.tupleof[idx]; - else static if (IsSetInfo!(typeof(this.tupleof[idx]))) { - if (higher.tupleof[idx].set) - result.tupleof[idx] = higher.tupleof[idx]; - else - result.tupleof[idx] = this.tupleof[idx]; - } else - static assert(false, - "Expect `@Optional` or `SetInfo` on: `" ~ - __traits(identifier, this.tupleof[idx]) ~ - "` of type : `" ~ - typeof(this.tupleof[idx]).stringof ~ "`"); - } - - return result; - } - - /// Workaround multiple `E` declaration in `static foreach` when inline - private template IsSetInfo(T) { enum bool IsSetInfo = is(T : SetInfo!E, E); } -} - -unittest { - import dub.internal.configy.Read; - - const str1 = `{ - "registryUrls": [ "http://foo.bar\/optional\/escape" ], - "customCachePaths": [ "foo/bar", "foo/foo" ], - - "skipRegistry": "all", - "defaultCompiler": "dmd", - "defaultArchitecture": "fooarch", - "defaultLowMemory": false, - - "defaultEnvironments": { - "VAR2": "settings.VAR2", - "VAR3": "settings.VAR3", - "VAR4": "settings.VAR4" - } -}`; - - const str2 = `{ - "registryUrls": [ "http://bar.foo" ], - "customCachePaths": [ "bar/foo", "bar/bar" ], - - "skipRegistry": "none", - "defaultCompiler": "ldc", - "defaultArchitecture": "bararch", - "defaultLowMemory": true, - - "defaultEnvironments": { - "VAR": "Hi", - } -}`; - - auto c1 = parseConfigString!UserConfiguration(str1, "/dev/null"); - assert(c1.registryUrls == [ "http://foo.bar/optional/escape" ]); - assert(c1.customCachePaths == [ NativePath("foo/bar"), NativePath("foo/foo") ]); - assert(c1.skipRegistry == SkipPackageSuppliers.all); - assert(c1.defaultCompiler == "dmd"); - assert(c1.defaultArchitecture == "fooarch"); - assert(c1.defaultLowMemory == false); - assert(c1.defaultEnvironments.length == 3); - assert(c1.defaultEnvironments["VAR2"] == "settings.VAR2"); - assert(c1.defaultEnvironments["VAR3"] == "settings.VAR3"); - assert(c1.defaultEnvironments["VAR4"] == "settings.VAR4"); - - auto c2 = parseConfigString!UserConfiguration(str2, "/dev/null"); - assert(c2.registryUrls == [ "http://bar.foo" ]); - assert(c2.customCachePaths == [ NativePath("bar/foo"), NativePath("bar/bar") ]); - assert(c2.skipRegistry == SkipPackageSuppliers.none); - assert(c2.defaultCompiler == "ldc"); - assert(c2.defaultArchitecture == "bararch"); - assert(c2.defaultLowMemory == true); - assert(c2.defaultEnvironments.length == 1); - assert(c2.defaultEnvironments["VAR"] == "Hi"); - - auto m1 = c2.merge(c1); - // c1 takes priority, so its registryUrls is first - assert(m1.registryUrls == [ "http://foo.bar/optional/escape", "http://bar.foo" ]); - // Same with CCP - assert(m1.customCachePaths == [ - NativePath("foo/bar"), NativePath("foo/foo"), - NativePath("bar/foo"), NativePath("bar/bar"), - ]); - - // c1 fields only - assert(m1.skipRegistry == c1.skipRegistry); - assert(m1.defaultCompiler == c1.defaultCompiler); - assert(m1.defaultArchitecture == c1.defaultArchitecture); - assert(m1.defaultLowMemory == c1.defaultLowMemory); - assert(m1.defaultEnvironments == c1.defaultEnvironments); - - auto m2 = c1.merge(c2); - assert(m2.registryUrls == [ "http://bar.foo", "http://foo.bar/optional/escape" ]); - assert(m2.customCachePaths == [ - NativePath("bar/foo"), NativePath("bar/bar"), - NativePath("foo/bar"), NativePath("foo/foo"), - ]); - assert(m2.skipRegistry == c2.skipRegistry); - assert(m2.defaultCompiler == c2.defaultCompiler); - assert(m2.defaultArchitecture == c2.defaultArchitecture); - assert(m2.defaultLowMemory == c2.defaultLowMemory); - assert(m2.defaultEnvironments == c2.defaultEnvironments); - - auto m3 = UserConfiguration.init.merge(c1); - assert(m3 == c1); -}