diff --git a/Gopkg.lock b/Gopkg.lock index 626b49f3..45405dc4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -5,7 +5,7 @@ branch = "master" name = "github.com/jguer/go-alpm" packages = ["."] - revision = "ec031c9cd5f6050edc3c2f23df2bff3bbb9511cc" + revision = "bc954af9b2ced79e4db54ce6ab3c6a24d769e98b" [[projects]] branch = "master" diff --git a/clean.go b/clean.go index 3fb23355..4773004a 100644 --- a/clean.go +++ b/clean.go @@ -20,16 +20,13 @@ func removeVCSPackage(pkgs []string) { } // CleanDependencies removes all dangling dependencies in system -func cleanDependencies() error { - hanging, err := hangingPackages() +func cleanDependencies(removeOptional bool) error { + hanging, err := hangingPackages(removeOptional) if err != nil { return err } if len(hanging) != 0 { - if !continueTask("Confirm Removal?", "nN") { - return nil - } err = cleanRemove(hanging) } @@ -42,12 +39,9 @@ func cleanRemove(pkgNames []string) (err error) { return nil } - oldvalue := config.NoConfirm - config.NoConfirm = true arguments := makeArguments() arguments.addArg("R") arguments.addTarget(pkgNames...) err = passToPacman(arguments) - config.NoConfirm = oldvalue return err } diff --git a/cmd.go b/cmd.go index 0d051cba..4efbdacd 100644 --- a/cmd.go +++ b/cmd.go @@ -323,8 +323,10 @@ func handleYay() (err error) { //_, options, targets := cmdArgs.formatArgs() if cmdArgs.existsArg("gendb") { err = createDevelDB() + } else if cmdArgs.existsDouble("c") { + err = cleanDependencies(true) } else if cmdArgs.existsArg("c", "clean") { - err = cleanDependencies() + err = cleanDependencies(false) } else if len(cmdArgs.targets) > 0 { err = handleYogurt() } diff --git a/query.go b/query.go index 46b624df..c2f0478f 100644 --- a/query.go +++ b/query.go @@ -313,26 +313,91 @@ func packageSlices(toCheck []string) (aur []string, repo []string, err error) { // HangingPackages returns a list of packages installed as deps // and unneeded by the system -func hangingPackages() (hanging []string, err error) { +// removeOptional decides whether optional dependencies are counted or not +func hangingPackages(removeOptional bool) (hanging []string, err error) { localDb, err := alpmHandle.LocalDb() if err != nil { return } - f := func(pkg alpm.Package) error { - if pkg.Reason() != alpm.PkgReasonDepend { + // safePackages represents every package in the system in one of 3 states + // State = 0 - Remove package from the system + // State = 1 - Keep package in the system; need to iterate over dependencies + // State = 2 - Keep package and have iterated over dependencies + safePackages := make(map[string]uint8) + // provides stores a mapping from the provides name back to the original package name + provides := make(map[string]stringSet) + packages := localDb.PkgCache() + + // Mark explicit dependencies and enumerate the provides list + setupResources := func(pkg alpm.Package) error { + if pkg.Reason() == alpm.PkgReasonExplicit { + safePackages[pkg.Name()] = 1 + } else { + safePackages[pkg.Name()] = 0 + } + + pkg.Provides().ForEach(func(dep alpm.Depend) error { + addMapStringSet(provides, dep.Name, pkg.Name()) + return nil + }) + return nil + } + packages.ForEach(setupResources) + + iterateAgain := true + processDependencies := func(pkg alpm.Package) error { + if state, _ := safePackages[pkg.Name()]; state == 0 || state == 2 { return nil } - requiredby := pkg.ComputeRequiredBy() - if len(requiredby) == 0 { - hanging = append(hanging, pkg.Name()) - fmt.Println(pkg.Name() + ": " + magenta(human(pkg.ISize()))) + safePackages[pkg.Name()] = 2 + + // Update state for dependencies + markDependencies := func(dep alpm.Depend) error { + // Don't assume a dependency is installed + state, ok := safePackages[dep.Name] + if !ok { + // Check if dep is a provides rather than actual package name + if pset, ok2 := provides[dep.Name]; ok2 { + for p := range pset { + if safePackages[p] == 0 { + iterateAgain = true + safePackages[p] = 1 + } + } + } + + return nil + } + + if state == 0 { + iterateAgain = true + safePackages[dep.Name] = 1 + } + return nil + } + + pkg.Depends().ForEach(markDependencies) + if !removeOptional { + pkg.OptionalDepends().ForEach(markDependencies) } return nil } - err = localDb.PkgCache().ForEach(f) + for iterateAgain { + iterateAgain = false + packages.ForEach(processDependencies) + } + + // Build list of packages to be removed + packages.ForEach(func(pkg alpm.Package) error { + if safePackages[pkg.Name()] == 0 { + hanging = append(hanging, pkg.Name()) + } + return nil + }) + return } diff --git a/vendor/github.com/jguer/go-alpm/package.go b/vendor/github.com/jguer/go-alpm/package.go index dd342ccc..ef589dd0 100644 --- a/vendor/github.com/jguer/go-alpm/package.go +++ b/vendor/github.com/jguer/go-alpm/package.go @@ -282,6 +282,21 @@ func (pkg Package) ComputeRequiredBy() []string { return requiredby } +// ComputeOptionalFor returns the names of packages that optionally require the given package +func (pkg Package) ComputeOptionalFor() []string { + result := C.alpm_pkg_compute_optionalfor(pkg.pmpkg) + optionalfor := make([]string, 0) + for i := (*list)(unsafe.Pointer(result)); i != nil; i = i.Next { + defer C.free(unsafe.Pointer(i)) + if i.Data != nil { + defer C.free(unsafe.Pointer(i.Data)) + name := C.GoString((*C.char)(unsafe.Pointer(i.Data))) + optionalfor = append(optionalfor, name) + } + } + return optionalfor +} + // NewVersion checks if there is a new version of the package in the Synced DBs. func (pkg Package) NewVersion(l DbList) *Package { ptr := C.alpm_sync_newversion(pkg.pmpkg,