1 /** 2 * Contains type definition for `dub.selections.json` 3 */ 4 module dub.recipe.selection; 5 6 import dub.dependency; 7 import dub.internal.vibecompat.core.file : NativePath; 8 9 import dub.internal.configy.Attributes; 10 11 import std.exception; 12 13 public struct Selected 14 { 15 /// The current version of the file format 16 public uint fileVersion; 17 18 /// The selected package and their matching versions 19 public SelectedDependency[string] versions; 20 } 21 22 23 /// Wrapper around `SelectedDependency` to do deserialization but still provide 24 /// a `Dependency` object to client code. 25 private struct SelectedDependency 26 { 27 public Dependency actual; 28 alias actual this; 29 30 /// Constructor, used in `fromYAML` 31 public this (inout(Dependency) dep) inout @safe pure nothrow @nogc 32 { 33 this.actual = dep; 34 } 35 36 /// Allow external code to assign to this object as if it was a `Dependency` 37 public ref SelectedDependency opAssign (Dependency dep) return pure nothrow @nogc 38 { 39 this.actual = dep; 40 return this; 41 } 42 43 /// Read a `Dependency` from the config file - Required to support both short and long form 44 static SelectedDependency fromYAML (scope ConfigParser!SelectedDependency p) 45 { 46 import dub.internal.dyaml.node; 47 48 if (p.node.nodeID == NodeID.scalar) 49 return SelectedDependency(Dependency(Version(p.node.as!string))); 50 51 auto d = p.parseAs!YAMLFormat; 52 if (d.path.length) 53 return SelectedDependency(Dependency(NativePath(d.path))); 54 else 55 { 56 assert(d.version_.length); 57 if (d.repository.length) 58 return SelectedDependency(Dependency(Repository(d.repository, d.version_))); 59 return SelectedDependency(Dependency(Version(d.version_))); 60 } 61 } 62 63 /// In-file representation of a dependency as permitted in `dub.selections.json` 64 private struct YAMLFormat 65 { 66 @Optional @Name("version") string version_; 67 @Optional string path; 68 @Optional string repository; 69 70 public void validate () const scope @safe pure 71 { 72 enforce(this.version_.length || this.path.length || this.repository.length, 73 "Need to provide a version string, or an object with one of the following fields: `version`, `path`, or `repository`"); 74 enforce(!this.path.length || !this.repository.length, 75 "Cannot provide a `path` dependency if a repository dependency is used"); 76 enforce(!this.path.length || !this.version_.length, 77 "Cannot provide a `path` dependency if a `version` dependency is used"); 78 enforce(!this.repository.length || this.version_.length, 79 "Cannot provide a `repository` dependency without a `version`"); 80 } 81 } 82 } 83 84 // Ensure we can read all type of dependencies 85 unittest 86 { 87 import dub.internal.configy.Read : parseConfigString; 88 import dub.internal.vibecompat.core.file : NativePath; 89 90 immutable string content = `{ 91 "fileVersion": 1, 92 "versions": { 93 "simple": "1.5.6", 94 "branch": "~master", 95 "branch2": "~main", 96 "path": { "path": "../some/where" }, 97 "repository": { "repository": "git+https://github.com/dlang/dub", "version": "123456123456123456" } 98 } 99 }`; 100 101 auto s = parseConfigString!Selected(content, "/dev/null"); 102 assert(s.fileVersion == 1); 103 assert(s.versions.length == 5); 104 assert(s.versions["simple"] == Dependency(Version("1.5.6"))); 105 assert(s.versions["branch"] == Dependency(Version("~master"))); 106 assert(s.versions["branch2"] == Dependency(Version("~main"))); 107 assert(s.versions["path"] == Dependency(NativePath("../some/where"))); 108 assert(s.versions["repository"] == Dependency(Repository("git+https://github.com/dlang/dub", "123456123456123456"))); 109 }