| |
---|
| | |
---|
| | /// Dependency specification used to select a particular version of the package. |
---|
| | Dependency spec; |
---|
| | } |
---|
| | |
---|
| | |
---|
| | /** |
---|
| | Represents a dependency specification. |
---|
| | |
---|
| |
---|
| | Version m_versB; |
---|
| | NativePath m_path; |
---|
| | bool m_optional = false; |
---|
| | bool m_default = false; |
---|
| | Repository m_repository; |
---|
| | } |
---|
| | |
---|
| | /// A Dependency, which matches every valid version. |
---|
| | static @property Dependency any() { return Dependency(ANY_IDENT); } |
---|
| |
---|
| | this(ANY_IDENT); |
---|
| | m_path = path; |
---|
| | } |
---|
| | |
---|
| | this(Repository repository, string spec) { |
---|
| | this.versionSpec = spec; |
---|
| | this.repository = repository; |
---|
| | } |
---|
| | |
---|
| | /// If set, overrides any version based dependency selection. |
---|
| | @property void path(NativePath value) { m_path = value; } |
---|
| | /// ditto |
---|
| | @property NativePath path() const { return m_path; } |
---|
| | |
---|
| | /// If set, overrides any version based dependency selection. |
---|
| | @property void repository(Repository value) |
---|
| | { |
---|
| | m_repository = value; |
---|
| | } |
---|
| | |
---|
| | /// ditto |
---|
| | @property Repository repository() const |
---|
| | { |
---|
| | return m_repository; |
---|
| | } |
---|
| | |
---|
| | /// Determines if the dependency is required or optional. |
---|
| | @property bool optional() const { return m_optional; } |
---|
| | /// ditto |
---|
| | @property void optional(bool optional) { m_optional = optional; } |
---|
| |
---|
| | @property void default_(bool value) { m_default = value; } |
---|
| | |
---|
| | /// Returns true $(I iff) the version range only matches a specific version. |
---|
| | @property bool isExactVersion() const { return m_versA == m_versB; } |
---|
| | |
---|
| | @property bool isGit() const { return !repository.empty; } |
---|
| | |
---|
| | /// Returns the exact version matched by the version range. |
---|
| | @property Version version_() const { |
---|
| | enforce(m_versA == m_versB, "Dependency "~this.versionSpec~" is no exact version."); |
---|
| |
---|
| | m_inclusiveB = false; |
---|
| | ves = ves[2..$]; |
---|
| | m_versA = Version(expandVersion(ves)); |
---|
| | m_versB = Version(bumpVersion(ves) ~ "-0"); |
---|
| | } else if (ves[0] == Version.branchPrefix) { |
---|
| | } else if (ves[0] == Version.branchPrefix || ves.isHash) { |
---|
| | m_inclusiveA = true; |
---|
| | m_inclusiveB = true; |
---|
| | m_versA = m_versB = Version(ves); |
---|
| | } else if (std.string.indexOf("><=", ves[0]) == -1) { |
---|
| |
---|
| | |
---|
| | string r; |
---|
| | |
---|
| | if (this == invalid) return "invalid"; |
---|
| | |
---|
| | if (m_versA == m_versB && m_inclusiveA && m_inclusiveB) { |
---|
| | // Special "==" case |
---|
| | if (m_versA == Version.masterBranch) return "~master"; |
---|
| | else return m_versA.toString(); |
---|
| |
---|
| | specification. |
---|
| | */ |
---|
| | string toString()() |
---|
| | const { |
---|
| | auto ret = versionSpec; |
---|
| | string ret; |
---|
| | |
---|
| | if (!repository.empty) { |
---|
| | ret ~= "#"; |
---|
| | } |
---|
| | ret ~= versionSpec; |
---|
| | if (optional) { |
---|
| | if (default_) ret ~= " (optional, default)"; |
---|
| | else ret ~= " (optional)"; |
---|
| | } |
---|
| |
---|
| | } else { |
---|
| | json = Json.emptyObject; |
---|
| | json["version"] = this.versionSpec; |
---|
| | if (!path.empty) json["path"] = path.toString(); |
---|
| | if (!repository.empty) json["repository"] = repository.remote; |
---|
| | if (optional) json["optional"] = true; |
---|
| | if (default_) json["default"] = true; |
---|
| | } |
---|
| | return json; |
---|
| |
---|
| | logDiagnostic("Ignoring version specification (%s) for path based dependency %s", pv.get!string, pp.get!string); |
---|
| | |
---|
| | dep = Dependency.any; |
---|
| | dep.path = NativePath(verspec["path"].get!string); |
---|
| | } else if (auto repository = "repository" in verspec) { |
---|
| | enforce("version" in verspec, "No version field specified!"); |
---|
| | dep = Dependency(Repository(repository.get!string), |
---|
| | verspec["version"].get!string); |
---|
| | } else { |
---|
| | enforce("version" in verspec, "No version field specified!"); |
---|
| | auto ver = verspec["version"].get!string; |
---|
| | // Using the string to be able to specify a range of versions. |
---|
| |
---|
| | |
---|
| | A specification is valid if it can match at least one version. |
---|
| | */ |
---|
| | bool valid() const { |
---|
| | if (isGit) return true; |
---|
| | return m_versA <= m_versB && doCmp(m_inclusiveA && m_inclusiveB, m_versA, m_versB); |
---|
| | } |
---|
| | |
---|
| | /** Determines if this dependency specification matches arbitrary versions. |
---|
| |
---|
| | bool matches(const(Version) v) const { return matches(v); } |
---|
| | /// ditto |
---|
| | bool matches(ref const(Version) v) const { |
---|
| | if (this.matchesAny) return true; |
---|
| | if (this.isGit) return true; |
---|
| | //logDebug(" try match: %s with: %s", v, this); |
---|
| | // Master only matches master |
---|
| | if(m_versA.isBranch) { |
---|
| | enforce(m_versA == m_versB); |
---|
| |
---|
| | result can be invalid (i.e. not match any version). |
---|
| | */ |
---|
| | Dependency merge(ref const(Dependency) o) |
---|
| | const { |
---|
| | if (this.isGit) { |
---|
| | if (!o.isGit) return this; |
---|
| | if (this.m_versA == o.m_versB) return this; |
---|
| | return invalid; |
---|
| | } |
---|
| | if (o.isGit) return o; |
---|
| | |
---|
| | if (this.matchesAny) return o; |
---|
| | if (o.matchesAny) return this; |
---|
| | if (m_versA.isBranch != o.m_versA.isBranch) return invalid; |
---|
| | if (m_versB.isBranch != o.m_versB.isBranch) return invalid; |
---|
| |
---|
| | assert(Dependency("~>2").versionSpec == "~>2"); |
---|
| | assert(Dependency("~>1.0.4+1.2.3").versionSpec == "~>1.0.4"); |
---|
| | } |
---|
| | |
---|
| | /** |
---|
| | Represents an SCM repository. |
---|
| | */ |
---|
| | struct Repository |
---|
| | { |
---|
| | private string m_remote; |
---|
| | |
---|
| | /** |
---|
| | Returns: |
---|
| | Repository URL or path. |
---|
| | */ |
---|
| | @property string remote() @nogc nothrow pure @safe |
---|
| | in { assert(m_remote !is null); } |
---|
| | body |
---|
| | { |
---|
| | return m_remote; |
---|
| | } |
---|
| | |
---|
| | /** |
---|
| | Returns: |
---|
| | Whether the repository was initialized with an URL or path. |
---|
| | */ |
---|
| | @property bool empty() const @nogc nothrow pure @safe |
---|
| | { |
---|
| | return m_remote is null; |
---|
| | } |
---|
| | } |
---|
| | |
---|
| | |
---|
| | /** |
---|
| | Represents a version in semantic version format, or a branch identifier. |
---|
| | |
---|
| |
---|
| | */ |
---|
| | this(string vers) |
---|
| | { |
---|
| | enforce(vers.length > 1, "Version strings must not be empty."); |
---|
| | if (vers[0] != branchPrefix && vers.ptr !is UNKNOWN_VERS.ptr) |
---|
| | if (vers[0] != branchPrefix && !vers.isHash && vers.ptr !is UNKNOWN_VERS.ptr) |
---|
| | enforce(vers.isValidVersion(), "Invalid SemVer format: " ~ vers); |
---|
| | m_version = vers; |
---|
| | } |
---|
| | |
---|
| |
---|
| | static Version fromString(string vers) { return Version(vers); } |
---|
| | |
---|
| | bool opEquals(const Version oth) const { return opCmp(oth) == 0; } |
---|
| | |
---|
| | /// Tests if this represents a hash instead of a version. |
---|
| | @property bool isGit() const { return m_version.isHash; } |
---|
| | |
---|
| | /// Tests if this represents a branch instead of a version. |
---|
| | @property bool isBranch() const { return m_version.length > 0 && m_version[0] == branchPrefix; } |
---|
| | |
---|
| | /// Tests if this represents the master branch "~master". |
---|
| |
---|
| | |
---|
| | Note that branches are always considered pre-release versions. |
---|
| | */ |
---|
| | @property bool isPreRelease() const { |
---|
| | if (isBranch) return true; |
---|
| | if (isBranch || isGit) return true; |
---|
| | return isPreReleaseVersion(m_version); |
---|
| | } |
---|
| | |
---|
| | /// Tests if this represents the special unknown version constant. |
---|
| |
---|
| | const { |
---|
| | if (isUnknown || other.isUnknown) { |
---|
| | throw new Exception("Can't compare unknown versions! (this: %s, other: %s)".format(this, other)); |
---|
| | } |
---|
| | |
---|
| | if (isGit || other.isGit) { |
---|
| | if (!isGit) return -1; |
---|
| | if (!other.isGit) return 1; |
---|
| | return (m_version == m_version) ? 0 : 1; |
---|
| | } |
---|
| | |
---|
| | if (isBranch || other.isBranch) { |
---|
| | if(m_version == other.m_version) return 0; |
---|
| | if (!isBranch) return 1; |
---|
| | else if (!other.isBranch) return -1; |
---|
| |
---|
| | b = Version("1.0.0"); |
---|
| | assert(a == b, "a == b with a:'1.0.0', b:'1.0.0' failed"); |
---|
| | b = Version("2.0.0"); |
---|
| | assert(a != b, "a != b with a:'1.0.0', b:'2.0.0' failed"); |
---|
| | |
---|
| | a = Version.masterBranch; |
---|
| | b = Version("~BRANCH"); |
---|
| | assert(a != b, "a != b with a:MASTER, b:'~branch' failed"); |
---|
| | assert(a > b); |
---|
| |
---|
| | b = Version.unknown; |
---|
| | assertThrown(a == b, "Failed: UNKNOWN == UNKNOWN"); |
---|
| | |
---|
| | assert(Version("1.0.0+a") == Version("1.0.0+b")); |
---|
| | |
---|
| | assert(Version("73535568b79a0b124bc1653002637a830ce0fcb8").isGit); |
---|
| | } |
---|
| | |
---|
| | private bool isHash(string hash) @nogc nothrow pure @safe |
---|
| | { |
---|
| | import std.ascii : isAlphaNum; |
---|
| | import std.utf : byCodeUnit; |
---|
| | |
---|
| | return hash.length == 40 && hash.byCodeUnit.all!isAlphaNum; |
---|
| | } |
---|
| | |
---|
| | @nogc nothrow pure @safe unittest { |
---|
| | assert(isHash("73535568b79a0b124bc1653002637a830ce0fcb8")); |
---|
| | assert(!isHash("735")); |
---|
| | assert(!isHash("73535568b79a0b124bc1-53002637a830ce0fcb8")); |
---|
| | } |
---|
| | |
---|
| | |