Merge pull request #1765 from belka-ew/pr_vesion_commit2
support dependencies as git url with exact commit
merged-on-behalf-of: Nicholas Wilson <thewilsonator@users.noreply.github.com>
commit 24a2ef96cb71a4cdc550bf2694dd68cd624996e2
2 parents 070635d + 38fbe4c
@The Dlang Bot The Dlang Bot authored on 8 Oct 2019
GitHub committed on 8 Oct 2019
Showing 4 changed files
View
109
source/dub/dependency.d
 
/// 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"));
}
View
25
source/dub/dub.d
auto path = dep.path;
if (!path.absolute) path = this.rootPath ~ path;
try if (m_packageManager.getOrLoadPackage(path)) continue;
catch (Exception e) { logDebug("Failed to load path based selection: %s", e.toString().sanitize); }
} else if (!dep.repository.empty) {
if (m_packageManager.clonePackage(getBasePackageName(p), dep))
continue;
} else {
if (m_packageManager.getPackage(p, dep.version_)) continue;
foreach (ps; m_packageSuppliers) {
try {
bool any = false;
string rootbasename = getBasePackageName(m_project.rootPackage.name);
 
foreach (p, ver; versions) {
if (!ver.path.empty) continue;
if (!ver.path.empty || !ver.repository.empty) continue;
 
auto basename = getBasePackageName(p);
if (basename == rootbasename) continue;
 
any = true;
continue;
}
auto sver = m_project.selections.getSelectedVersion(basename);
if (!sver.path.empty) continue;
if (!sver.path.empty || !sver.repository.empty) continue;
if (ver.version_ <= sver.version_) continue;
logInfo("Package %s would be upgraded from %s to %s.",
basename, sver, ver);
any = true;
catch (Exception e) {
logDebug("Failed to load path based selection: %s", e.toString().sanitize);
continue;
}
} else if (!ver.repository.empty) {
pack = m_packageManager.clonePackage(p, ver);
} else {
pack = m_packageManager.getBestPackage(p, ver);
if (pack && m_packageManager.isManagedPackage(pack)
&& ver.version_.isBranch && (options & UpgradeOptions.upgrade) != 0)
FetchOptions fetchOpts;
fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none;
if (!pack) fetch(p, ver, defaultPlacementLocation, fetchOpts, "getting selected version");
if ((options & UpgradeOptions.select) && p != m_project.rootPackage.name) {
if (ver.path.empty) m_project.selections.selectVersion(p, ver.version_);
if (!ver.repository.empty) {
m_project.selections.selectVersion(p, ver.repository, ver.versionSpec);
} else if (ver.path.empty) m_project.selections.selectVersion(p, ver.version_);
else {
NativePath relpath = ver.path;
if (relpath.absolute) relpath = relpath.relativeTo(m_project.rootPackage.path);
m_project.selections.selectVersion(p, relpath);
 
protected override Dependency[] getSpecificConfigs(string pack, TreeNodes nodes)
{
if (!nodes.configs.path.empty && getPackage(pack, nodes.configs)) return [nodes.configs];
else if (!nodes.configs.repository.empty && getPackage(pack, nodes.configs))
return [nodes.configs];
else return null;
}
 
 
// shortcut if the referenced package is the root package
if (basename == m_rootPackage.basePackage.name)
return m_rootPackage.basePackage;
 
if (!dep.path.empty) {
if (!dep.repository.empty) {
auto ret = m_dub.packageManager.clonePackage(name, dep);
return ret !is null && dep.matches(ret.version_) ? ret : null;
} else if (!dep.path.empty) {
try {
auto ret = m_dub.packageManager.getOrLoadPackage(dep.path);
if (dep.matches(ret.version_)) return ret;
} catch (Exception e) {
View
49
source/dub/packagemanager.d
addPackages(m_temporaryPackages, pack);
return pack;
}
 
/** For a given Git repository, returns the corresponding package.
 
Git repository is provided as its remote URL, the repository is cloned
and in the dependency speicfied commit is checked out.
 
If the target directory already exists, just returns the package
without cloning.
 
Params:
name = Package name
dependency = Dependency that contains the repository URL and a specific commit
 
Returns:
The package loaded from the given Git repository or null if the
package couldn't be loaded.
*/
Package clonePackage(string name, Dependency dependency)
in { assert(!dependency.repository.empty); }
body {
import std.process : escapeShellCommand, executeShell;
 
string gitReference = dependency.versionSpec;
if (gitReference.startsWith("~")) gitReference.popFront;
 
const destination = m_repositories[LocalPackageType.user].packagePath ~
NativePath(name~"-"~gitReference) ~ name;
const nativeDestination = destination.toNativeString();
 
if (!exists(nativeDestination)) {
auto command = escapeShellCommand("git", "clone",
dependency.repository.remote,
nativeDestination);
if (executeShell(command).status != 0) {
return null;
}
 
command = escapeShellCommand("git",
"-C", nativeDestination,
"checkout", gitReference);
if (executeShell(command).status != 0) {
rmdirRecurse(nativeDestination);
return null;
}
}
auto pack = Package.load(destination);
addPackages(m_temporaryPackages, pack);
 
return pack;
}
 
/** Searches for the latest version of a package matching the given dependency.
*/
Package getBestPackage(string name, Dependency version_spec, bool enable_overrides = true)
View
42
source/dub/project.d
}
enforce(!m_rootPackage.name.canFind(' '), "Aborting due to the package name containing spaces.");
 
foreach (d; m_rootPackage.getAllDependencies())
if (d.spec.isExactVersion && d.spec.version_.isBranch) {
if (d.spec.isExactVersion && d.spec.version_.isBranch && !d.spec.isGit) {
logWarn("WARNING: A deprecated branch based version specification is used "
~ "for the dependency %s. Please use numbered versions instead. Also "
~ "note that you can still use the %s file to override a certain "
~ "dependency to use a branch instead.",
continue;
}
} else if (m_selections.hasSelectedVersion(basename)) {
vspec = m_selections.getSelectedVersion(basename);
if (vspec.path.empty) p = m_packageManager.getBestPackage(dep.name, vspec);
if (!vspec.repository.empty) {
p = m_packageManager.clonePackage(basename, vspec);
if (subname.length) p = m_packageManager.getSubPackage(p, subname, true);
} else if (vspec.path.empty) p = m_packageManager.getBestPackage(dep.name, vspec);
else {
auto path = vspec.path;
if (!path.absolute) path = m_rootPackage.path ~ path;
p = m_packageManager.getOrLoadPackage(path, NativePath.init, true);
else p = bp;
} else {
logDiagnostic("%sVersion selection for dependency %s (%s) of %s is missing.",
indent, basename, dep.name, pack.name);
}
 
if (!p && !vspec.repository.empty) {
p = m_packageManager.clonePackage(basename, vspec);
if (subname.length) p = m_packageManager.getSubPackage(p, subname, false);
}
 
if (!p && !vspec.path.empty) {
NativePath path = vspec.path;
m_selections[package_id] = Selected(Dependency(path));
m_dirty = true;
}
 
void selectVersion(string package_id, Repository repository, string spec)
{
const dependency = Dependency(repository, spec);
if (auto ps = package_id in m_selections) {
if (ps.dep == dependency)
return;
}
m_selections[package_id] = Selected(dependency);
m_dirty = true;
}
 
/// Removes the selection for a particular package.
void deselectVersion(string package_id)
{
m_selections.remove(package_id);
}
 
static Json dependencyToJson(Dependency d)
{
if (d.path.empty) return Json(d.version_.toString());
if (!d.repository.empty) {
return serializeToJson([
"version": d.version_.toString(),
"repository": d.repository.remote,
]);
} else if (d.path.empty) return Json(d.version_.toString());
else return serializeToJson(["path": d.path.toString()]);
}
 
static Dependency dependencyFromJson(Json j)
{
if (j.type == Json.Type.string)
return Dependency(Version(j.get!string));
else if (j.type == Json.Type.object)
else if (j.type == Json.Type.object && "path" in j)
return Dependency(NativePath(j["path"].get!string));
else if (j.type == Json.Type.object && "repository" in j)
return Dependency(Repository(j["repository"].get!string), j["version"].get!string);
else throw new Exception(format("Unexpected type for dependency: %s", j.type));
}
 
Json serialize()