diff --git a/source/app.d b/source/app.d index b7e866c..808a754 100644 --- a/source/app.d +++ b/source/app.d @@ -139,28 +139,25 @@ enforce(!install_local || !install_system, "Cannot install locally and system wide at the same time."); if( install_local ) location = InstallLocation.local; else if( install_system ) location = InstallLocation.systemWide; - if( install_version.length ) dub.install(name, new Dependency(install_version), location, true); + if( install_version.length ) dub.install(name, new Dependency(install_version), location); else { - try dub.install(name, new Dependency(">=0.0.0"), location, true); + try dub.install(name, new Dependency(">=0.0.0"), location); catch(Exception e){ logInfo("Installing a release version failed: %s", e.msg); logInfo("Retry with ~master..."); - dub.install(name, new Dependency("~master"), location, true); + dub.install(name, new Dependency("~master"), location); } } break; case "uninstall": enforce(args.length >= 2, "Missing package name."); - /*auto location = InstallLocation.userWide; - auto name = args[1]; + auto location = InstallLocation.userWide; + auto package_id = args[1]; enforce(!install_local || !install_system, "Cannot install locally and system wide at the same time."); if( install_local ) location = InstallLocation.local; else if( install_system ) location = InstallLocation.systemWide; - if( install_version.length ) dub.uninstall(name, new Dependency(install_version), location); - else { - assert(false); - }*/ - enforce(false, "Not implemented."); + try dub.uninstall(package_id, install_version, location); + catch logError("Please specify a individual version or use the wildcard identifier '%s' (without quotes).", Dub.UninstallVersionWildcard); break; case "add-local": enforce(args.length >= 3, "Missing arguments."); @@ -256,8 +253,40 @@ private void showHelp(string command) { - // This help is actually a mixup of help for this application and the - // supporting vibe script / .cmd file. + if(command == "uninstall" || command == "install") { + logInfo( +`Usage: dub [] + +Note: use dependencies (package.json) if you want to add a dependency, you + don't have to fiddle with installation stuff. + +(Un)Installation of packages is only needed when you want to put packages to a +place where several applications can share these. If you just have an +dependency to a package, just add it to your package.json, dub will do the rest +for you. + +Without specified options, (un)installation will default to a user wide shared +location. + +Complete applications can be installed and run easily by e.g. + dub install vibelog --local + cd vibelog + dub +This will grab all needed dependencies and compile and run the application. + +Install options: + --version Use the specified version/branch instead of the latest + For the uninstall command, this may be a wildcard + string: "*", which will remove all packages from the + specified location. + --system Install system wide instead of user local + --local Install as in a sub folder of the current directory + Note that system and local cannot be mixed. +`); + return; + } + + // No specific help, show general help. logInfo( `Usage: dub [] [] [-- ] @@ -271,8 +300,8 @@ run Compiles and runs the application (default command) build Just compiles the application in the project directory upgrade Forces an upgrade of all dependencies - install Manually installs a package - uninstall Uninstalls a package + install Manually installs a package. See 'dub help install'. + uninstall Uninstalls a package. See 'dub help uninstall'. add-local Adds a local package directory (e.g. a git repository) remove-local Removes a local package directory diff --git a/source/dub/dub.d b/source/dub/dub.d index 60f57ca..a9b2be8 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -180,19 +180,11 @@ string[string] installedPackages() const { return m_project.installedPackagesIDs(); } /// Installs the package matching the dependency into the application. - /// @param addToApplication if true, this will also add an entry in the - /// list of dependencies in the application's package.json - void install(string packageId, const Dependency dep, InstallLocation location = InstallLocation.projectLocal, bool addToApplication = false) + void install(string packageId, const Dependency dep, InstallLocation location = InstallLocation.projectLocal) { auto pinfo = m_packageSupplier.packageJson(packageId, dep); string ver = pinfo["version"].get!string; - // Perform addToApplication - if(addToApplication && !m_project.tryAddDependency(packageId, dep)) { - logError("Installation of '%s' failed.", packageId); - return; - } - if( m_packageManager.hasPackage(packageId, ver, location) ){ logInfo("Package %s %s (%s) is already installed with the latest version, skipping upgrade.", packageId, ver, location); @@ -220,10 +212,70 @@ void uninstall(in Package pack) { logInfo("Uninstalling %s in %s", pack.name, pack.path.toNativeString()); - m_packageManager.uninstall(pack); } + /// @see uninstall(string, string, InstallLocation) + immutable string UninstallVersionWildcard = "*"; + + /// This will uninstall a given package with a specified version from the + /// location. + /// It will remove at most one package, unless @param version_ is + /// specified as wildcard "*". + /// @param package_id Package to be removed + /// @param version_ Identifying a version or a wild card. An empty string + /// may be passed into. In this case the package will be removed from the + /// location, if there is only one version installed. This will throw an + /// exception, if there are multiple versions installed. + /// Note: as wildcard string only "*" is supported. + /// @param location_ + void uninstall(string package_id, string version_, InstallLocation location_) { + enforce(!package_id.empty); + Package[] packages; + const bool wildcardOrEmpty = version_ == UninstallVersionWildcard || version_.empty; + if(location_ == InstallLocation.local) { + // Try folder named like the package_id in the cwd. + try { + Package pack = new Package(InstallLocation.local, Path(package_id)); + if(!wildcardOrEmpty && to!string(pack.vers) != version_) { + logError("Installed package is of different version, uninstallation aborted."); + logError("Installed: %s, provided %s@", pack.vers, version_); + throw new Exception("Found package locally, but the versions don't match!"); + } + packages ~= pack; + } catch {/* noop */} + } else { + // Use package manager + foreach(pack; m_packageManager.getPackageIterator(package_id)){ + if( pack.installLocation == location_ && (wildcardOrEmpty || pack.vers == version_ )) { + packages ~= pack; + } + } + } + + if(packages.empty) { + logError("Cannot find package to uninstall. (id:%s, version:%s, location:%s)", package_id, version_, location_); + return; + } + + if(version_.empty && packages.length > 1) { + logError("Cannot uninstall package '%s', there multiple possibilities at location '%s'.", package_id, location_); + logError("Installed versions:"); + foreach(pack; packages) + logError(to!string(pack.vers())); + throw new Exception("Failed to uninstall package."); + } + + logTrace("Uninstalling %s packages.", packages.length); + foreach(pack; packages) { + try { + uninstall(pack); + logInfo("Uninstalled %s, version %s.", package_id, pack.vers); + } + catch logError("Failed to uninstall %s, version %s. Continuing with other packages (if any).", package_id, pack.vers); + } + } + void addLocalPackage(string path, string ver, bool system) { auto abs_path = Path(path); diff --git a/source/dub/package_.d b/source/dub/package_.d index 4eac613..03ebe04 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -28,12 +28,10 @@ /// Indicates where a package has been or should be installed to. enum InstallLocation { /// Packages installed with 'local' will be placed in the current folder - /// using the package name as destination. To uninstall just remove this - /// folder, but take care for your own files. + /// using the package name as destination. local, /// Packages with 'projectLocal' will be placed in a folder managed by - /// dub (i.e. inside the .dub subfolder). These can be uninstalled by - /// dub. + /// dub (i.e. inside the .dub subfolder). projectLocal, /// Packages with 'userWide' will be placed in a folder accessible by /// all of the applications from the current user. diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index 176c404..5b177f5 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -68,8 +68,8 @@ bool hasPackage(string name, string ver, InstallLocation location) { - foreach(ep; getPackageIterator()){ - if( ep.installLocation == location && ep.name == name && ep.vers == ver ) + foreach(ep; getPackageIterator(name)){ + if( ep.installLocation == location && ep.vers == ver ) return true; } return false; @@ -274,26 +274,32 @@ void uninstall(in Package pack) { + logTrace("Uninstall %s, version %s, path '%s'", pack.name, pack.vers, pack.path); enforce(!pack.path.empty, "Cannot uninstall package "~pack.name~" without a path."); // remove package from package list final switch(pack.installLocation){ - case InstallLocation.local: assert(false, "Cannot uninstall locally installed package."); + case InstallLocation.local: + logTrace("Uninstall local"); + break; case InstallLocation.projectLocal: + logTrace("Uninstall projectLocal"); auto pp = pack.name in m_projectPackages; assert(pp !is null, "Package "~pack.name~" at "~pack.path.toNativeString()~" is not installed in project."); assert(*pp is pack); m_projectPackages.remove(pack.name); break; case InstallLocation.userWide: - auto pv = pack.name in m_systemPackages; + logTrace("Uninstall userWide"); + auto pv = pack.name in m_userPackages; assert(pv !is null, "Package "~pack.name~" at "~pack.path.toNativeString()~" is not installed in user repository."); auto idx = countUntil(*pv, pack); assert(idx < 0 || (*pv)[idx] is pack); if( idx >= 0 ) *pv = (*pv)[0 .. idx] ~ (*pv)[idx+1 .. $]; break; case InstallLocation.systemWide: - auto pv = pack.name in m_userPackages; + logTrace("Uninstall systemWide"); + auto pv = pack.name in m_systemPackages; assert(pv !is null, "Package "~pack.name~" at "~pack.path.toNativeString()~" is not installed system repository."); auto idx = countUntil(*pv, pack); assert(idx < 0 || (*pv)[idx] is pack); @@ -302,13 +308,14 @@ } // delete package files physically + logTrace("Looking up journal"); auto journalFile = pack.path~JournalJsonFilename; if( !existsFile(journalFile) ) - throw new Exception("Uninstall failed, no journal found for '"~pack.name~"'. Please uninstall manually."); + throw new Exception("Uninstall failed, no installation journal found for '"~pack.name~"'. Please uninstall manually."); auto packagePath = pack.path; auto journal = new Journal(journalFile); - logDebug("Erasing files"); + logTrace("Erasing files"); foreach( Journal.Entry e; filter!((Journal.Entry a) => a.type == Journal.Type.RegularFile)(journal.entries)) { logTrace("Deleting file '%s'", e.relFilename); auto absFile = pack.path~e.relFilename; @@ -334,6 +341,15 @@ rmdir(p.toNativeString()); } + // Erase .dub folder, this is completely erased. + auto dubDir = (pack.path ~ ".dub/").toNativeString(); + enforce(!existsFile(dubDir) || isDir(dubDir), ".dub should be a directory, but is a file."); + if(existsFile(dubDir) && isDir(dubDir)) { + logTrace(".dub directory found, removing directory including content."); + rmdirRecurse(dubDir); + } + + logTrace("About to delete root folder for package '%s'.", pack.path); if(!isEmptyDir(pack.path)) throw new Exception("Alien files found in '"~pack.path.toNativeString()~"', needs to be deleted manually.");