diff --git a/source/dub/dependency.d b/source/dub/dependency.d index ffe6081..97df822 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -25,17 +25,6 @@ static import std.compiler; -Dependency[string] dependencies(const Json json) -{ - if( "dependencies" !in json ) return null; - Dependency[string] dep; - foreach( string pkg, ref const Json vers; json["dependencies"] ) { - enforce( pkg !in dep, "The dependency '"~pkg~"' is specified more than once." ); - dep[pkg] = new Dependency(cast(string)vers); - } - return dep; -} - /** A version in the format "major.update.bugfix". */ diff --git a/source/dub/dub.d b/source/dub/dub.d index b9b4e97..88d61a7 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -122,20 +122,42 @@ void reinit() { m_dependencies = null; m_main = null; + m_packageManager.refresh(); try m_json = jsonFromFile(m_root ~ ".dub/dub.json", true); catch(Exception t) logDebug("Failed to read .dub/dub.json: %s", t.msg); - if(!exists(to!string(m_root~PackageJsonFilename))) { + if( !exists(to!string(m_root~PackageJsonFilename)) ){ logWarn("There was no '"~PackageJsonFilename~"' found for the application in '%s'.", m_root); - } else { - m_main = new Package(InstallLocation.Local, m_root); - foreach( name, vspec; m_main.dependencies ){ + return; + } + + m_main = new Package(InstallLocation.Local, m_root); + + // TODO: compute the set of mutual dependencies first + // (i.e. ">=0.0.1 <=0.0.5" and "<= 0.0.4" get ">=0.0.1 <=0.0.4") + // conflicts would then also be detected. + void collectDependenciesRec(Package pack) + { + logDebug("Collecting dependencies for %s", pack.name); + foreach( ldef; pack.localPackageDefs ){ + logDebug("Adding local %s %s", ldef.name, ldef.version_); + m_packageManager.addLocalPackage(ldef.path, ldef.version_, LocalPackageType.temporary); + } + + foreach( name, vspec; pack.dependencies ){ auto p = m_packageManager.getBestPackage(name, vspec); + if( !m_dependencies.canFind(p) ){ + logDebug("Found dependency %s %s: %s", name, vspec.toString(), p !is null); + if( p ){ + m_dependencies ~= p; + collectDependenciesRec(p); + } + } //enforce(p !is null, "Failed to resolve dependency "~name~" "~vspec.toString()); - if( p ) m_dependencies ~= p; } } + collectDependenciesRec(m_main); } /// Returns the applications name. @@ -179,7 +201,7 @@ } /// Actions which can be performed to update the application. - Action[] actions(PackageSupplier packageSupplier, int option) { + Action[] determineActions(PackageSupplier packageSupplier, int option) { scope(exit) writeDubJson(); if(!m_main) { @@ -487,7 +509,7 @@ /// the application. /// @param options bit combination of UpdateOptions bool update(UpdateOptions options) { - Action[] actions = m_app.actions(m_packageSupplier, options); + Action[] actions = m_app.determineActions(m_packageSupplier, options); if( actions.length == 0 ) return true; logInfo("The following changes could be performed:"); @@ -522,7 +544,7 @@ install(a.packageId, a.vers); m_app.reinit(); - Action[] newActions = m_app.actions(m_packageSupplier, 0); + Action[] newActions = m_app.determineActions(m_packageSupplier, 0); if(newActions.length > 0) { logInfo("There are still some actions to perform:"); foreach(Action a; newActions) @@ -583,14 +605,14 @@ { auto abs_path = Path(path); if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path; - m_packageManager.addLocalPackage(abs_path, Version(ver), system); + m_packageManager.addLocalPackage(abs_path, Version(ver), system ? LocalPackageType.system : LocalPackageType.user); } void removeLocalPackage(string path, bool system) { auto abs_path = Path(path); if( !abs_path.absolute ) abs_path = m_cwd ~ abs_path; - m_packageManager.removeLocalPackage(abs_path, system); + m_packageManager.removeLocalPackage(abs_path, system ? LocalPackageType.system : LocalPackageType.user); } } diff --git a/source/dub/package_.d b/source/dub/package_.d index 1203db8..615a8e7 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -12,6 +12,7 @@ import std.array; import std.conv; +import std.exception; import vibe.core.file; import vibe.data.json; import vibe.inet.url; @@ -102,34 +103,45 @@ // "dependencies": { // "black-sabbath": ">=1.0.0", // "CowboysFromHell": "<1.0.0", -// "BeneathTheRemains": ">=1.0.3" +// "BeneathTheRemains": {"version": "0.4.1", "path": "./beneath-0.4.1"} // } // "licenses": { // ... // } // } class Package { + static struct LocalPacageDef { string name; Version version_; Path path; } + private { InstallLocation m_location; Path m_path; Json m_meta; Dependency[string] m_dependencies; - } - - this(InstallLocation location, Path root) - { - m_location = location; - m_path = root; - m_meta = jsonFromFile(root ~ PackageJsonFilename); - m_dependencies = .dependencies(m_meta); + LocalPacageDef[] m_localPackageDefs; } - this(Json json, InstallLocation location = InstallLocation.Local, Path root = Path()) + this(InstallLocation location, Path root) + { + this(jsonFromFile(root ~ PackageJsonFilename), location, root); + } + + this(Json package_info, InstallLocation location = InstallLocation.Local, Path root = Path()) { m_location = location; m_path = root; - m_meta = json; - m_dependencies = .dependencies(m_meta); + m_meta = package_info; + + // extract dependencies and local package definitions + if( auto pd = "dependencies" in package_info ){ + foreach( string pkg, verspec; *pd ) { + enforce(pkg !in m_dependencies, "The dependency '"~pkg~"' is specified more than once." ); + if( verspec.type == Json.Type.Object ){ + auto ver = verspec["version"].get!string; + m_dependencies[pkg] = new Dependency("==", ver); + m_localPackageDefs ~= LocalPacageDef(pkg, Version(ver), Path(verspec.path.get!string())); + } else m_dependencies[pkg] = new Dependency(verspec.get!string()); + } + } } @property string name() const { return cast(string)m_meta["name"]; } @@ -139,6 +151,7 @@ @property Path path() const { return m_path; } @property const(Url) url() const { return Url.parse(cast(string)m_meta["url"]); } @property const(Dependency[string]) dependencies() const { return m_dependencies; } + @property const(LocalPacageDef)[] localPackageDefs() const { return m_localPackageDefs; } @property string[] configurations() const { diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index 25e1495..d17b804 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -27,6 +27,12 @@ enum JournalJsonFilename = "journal.json"; enum LocalPackagesFilename = "local-packages.json"; +enum LocalPackageType { + temporary, + user, + system +} + class PackageManager { private { @@ -36,6 +42,7 @@ Package[][string] m_systemPackages; Package[][string] m_userPackages; Package[string] m_projectPackages; + Package[] m_localTemporaryPackages; Package[] m_localUserPackages; Package[] m_localSystemPackages; } @@ -78,6 +85,8 @@ int iterator(int delegate(ref Package) del) { // first search project local packages + foreach( p; m_localTemporaryPackages ) + if( auto ret = del(p) ) return ret; foreach( p; m_projectPackages ) if( auto ret = del(p) ) return ret; @@ -112,6 +121,9 @@ int iterator(int delegate(ref Package) del) { // first search project local packages + foreach( p; m_localTemporaryPackages ) + if( p.name == name ) + if( auto ret = del(p) ) return ret; if( auto pp = name in m_projectPackages ) if( auto ret = del(*pp) ) return ret; @@ -308,48 +320,40 @@ logInfo("Uninstalled package: '"~pack.name~"'"); } - void addLocalPackage(Path path, Version ver, bool system) + void addLocalPackage(in Path path, in Version ver, LocalPackageType type) { - Package[]* packs = system ? &m_localSystemPackages : &m_localUserPackages; + Package[]* packs = getLocalPackageList(type); auto info = jsonFromFile(path ~ PackageJsonFilename, false); string name; if( "name" !in info ) info["name"] = path.head.toString(); info["version"] = ver.toString(); + // don't double-add packages + foreach( p; *packs ){ + if( p.path == path ){ + enforce(p.ver == ver, "Adding local twice with different versions is not allowed."); + return; + } + } + *packs ~= new Package(info, InstallLocation.Local, path); - writeLocalPackageList(system); + writeLocalPackageList(type); } - void removeLocalPackage(Path path, bool system) + void removeLocalPackage(in Path path, LocalPackageType type) { - Package[]* packs = system ? &m_localSystemPackages : &m_localUserPackages; + Package[]* packs = getLocalPackageList(type); size_t[] to_remove; foreach( i, entry; *packs ) if( entry.path == path ) to_remove ~= i; - enforce(to_remove.length > 0, "No "~(system?"system":"user")~" package found at "~path.toNativeString()); + enforce(to_remove.length > 0, "No "~type.to!string()~" package found at "~path.toNativeString()); foreach_reverse( i; to_remove ) *packs = (*packs)[0 .. i] ~ (*packs)[i+1 .. $]; - writeLocalPackageList(system); - } - - void writeLocalPackageList(bool system) - { - Package[]* packs = system ? &m_localSystemPackages : &m_localUserPackages; - Json[] newlist; - foreach( p; *packs ){ - auto entry = Json.EmptyObject; - entry["name"] = p.name; - entry["version"] = p.ver.toString(); - entry["path"] = p.path.toNativeString(); - newlist ~= entry; - } - auto path = system ? m_systemPackagePath : m_userPackagePath; - if( !existsDirectory(path) ) mkdirRecurse(path.toNativeString()); - writeJsonFile(path ~ LocalPackagesFilename, Json(newlist)); + writeLocalPackageList(type); } void refresh() @@ -436,4 +440,35 @@ scanLocalPackages(m_systemPackagePath, m_localSystemPackages); scanLocalPackages(m_userPackagePath, m_localUserPackages); } + + private Package[]* getLocalPackageList(LocalPackageType type) + { + final switch(type){ + case LocalPackageType.user: return &m_localUserPackages; + case LocalPackageType.system: return &m_localSystemPackages; + case LocalPackageType.temporary: return &m_localTemporaryPackages; + } + } + + private void writeLocalPackageList(LocalPackageType type) + { + Package[]* packs = getLocalPackageList(type); + Json[] newlist; + foreach( p; *packs ){ + auto entry = Json.EmptyObject; + entry["name"] = p.name; + entry["version"] = p.ver.toString(); + entry["path"] = p.path.toNativeString(); + newlist ~= entry; + } + + Path path; + final switch(type){ + case LocalPackageType.user: path = m_userPackagePath; + case LocalPackageType.system: path = m_systemPackagePath; + case LocalPackageType.temporary: return; + } + if( !existsDirectory(path) ) mkdirRecurse(path.toNativeString()); + writeJsonFile(path ~ LocalPackagesFilename, Json(newlist)); + } } \ No newline at end of file