diff --git a/install.go b/install.go index b2e1d2d5..80916c8e 100644 --- a/install.go +++ b/install.go @@ -8,6 +8,7 @@ import ( "os/exec" "strconv" "strings" + "sync" alpm "github.com/jguer/go-alpm" rpc "github.com/mikkeloscar/aur" @@ -126,7 +127,7 @@ func install(parser *arguments) error { fmt.Println() if !parser.existsArg("gendb") { - err = checkForConflicts(dc) + err = checkForAllConflicts(dc) if err != nil { return err } @@ -460,43 +461,346 @@ func cleanBuilds(pkgs []*rpc.Pkg) { } } -func checkForConflicts(dc *depCatagories) error { - localDb, err := alpmHandle.LocalDb() - if err != nil { - return err +func checkInnerConflict(name string, conflict string, conflicts map[string]stringSet, dc *depCatagories) { + add := func(h map[string]stringSet, n string, v string) { + _, ok := h[n] + if !ok { + h[n] = make(stringSet) + } + h[n].set(v) } - toRemove := make(map[string]stringSet) + + + deps, err := gopkg.ParseDeps([]string{conflict}) + if err != nil { + return + } + dep := deps[0] for _, pkg := range dc.Aur { - for _, cpkg := range pkg.Conflicts { - if _, err := localDb.PkgByName(cpkg); err == nil { - _, ok := toRemove[pkg.Name] - if !ok { - toRemove[pkg.Name] = make(stringSet) - } - toRemove[pkg.Name].set(cpkg) + if name == pkg.Name { + continue + } + + version, err := gopkg.NewCompleteVersion(pkg.Version) + if err != nil { + return + } + if dep.Name == pkg.Name && version.Satisfies(dep) { + add(conflicts, name, pkg.Name) + continue + } + + for _, provide := range pkg.Provides { + // Provides are not versioned unless explicitly defined as + // such. If a conflict is versioned but a provide is + // not it can not conflict. + if (dep.MaxVer != nil || dep.MinVer != nil) && !strings.ContainsAny(provide, "><=") { + continue } + + var version *gopkg.CompleteVersion + var err error + + pname, pversion := splitNameFromDep(provide) + + if dep.Name != pname { + continue + } + + if pversion != "" { + version, err = gopkg.NewCompleteVersion(provide) + if err != nil { + return + } + } + + if version != nil && version.Satisfies(dep) { + add(conflicts, name, pkg.Name) + break + } + } } for _, pkg := range dc.Repo { - pkg.Conflicts().ForEach(func(conf alpm.Depend) error { - if _, err := localDb.PkgByName(conf.Name); err == nil { - _, ok := toRemove[pkg.Name()] - if !ok { - toRemove[pkg.Name()] = make(stringSet) - } - toRemove[pkg.Name()].set(conf.Name) + if name == pkg.Name() { + continue + } + + version, err := gopkg.NewCompleteVersion(pkg.Version()) + if err != nil { + return + } + + if dep.Name == pkg.Name() && version.Satisfies(dep) { + add(conflicts, name, pkg.Name()) + continue + } + + pkg.Provides().ForEach(func(provide alpm.Depend) error { + // Provides are not versioned unless explicitly defined as + // such. If a conflict is versioned but a provide is + // not it can not conflict. + if (dep.MaxVer != nil || dep.MinVer != nil) && provide.Mod == alpm.DepModAny { + return nil } + + if dep.Name != pkg.Name() { + return nil + } + + if provide.Mod == alpm.DepModAny { + add(conflicts, name, pkg.Name()) + return fmt.Errorf("") + } + + version, err := gopkg.NewCompleteVersion(provide.Version) + if err != nil { + return nil + } + + if version.Satisfies(dep) { + add(conflicts, name, pkg.Name()) + return fmt.Errorf("") + } + + return nil + }) + } +} + +func checkForInnerConflicts(dc *depCatagories) (map[string]stringSet) { + conflicts := make(map[string]stringSet) + + for _, pkg := range dc.Aur { + for _, cpkg := range pkg.Conflicts { + checkInnerConflict(pkg.Name, cpkg, conflicts, dc) + } + } + + for _, pkg := range dc.Repo { + pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { + checkInnerConflict(pkg.Name(), conflict.String(), conflicts, dc) + return nil + }) + } + + return conflicts +} + +func checkReverseConflict(name string, provide string, conflicts map[string]stringSet) error { + add := func(h map[string]stringSet, n string, v string) { + _, ok := h[n] + if !ok { + h[n] = make(stringSet) + } + h[n].set(v) + } + + var version *gopkg.CompleteVersion + var err error + + localDb, err := alpmHandle.LocalDb() + if err != nil { + return err + } + + pname, pversion := splitNameFromDep(provide) + if pversion != "" { + version, err = gopkg.NewCompleteVersion(pversion) + if err != nil { + return nil + } + } + + + localDb.PkgCache().ForEach(func(pkg alpm.Package) error { + if name == pkg.Name() { + return nil + } + + pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { + deps, err := gopkg.ParseDeps([]string{conflict.String()}) + if err != nil { + return nil + } + + dep := deps[0] + // Provides are not versioned unless explicitly defined as + // such. If a conflict is versioned but a provide is + // not it can not conflict. + if (dep.MaxVer != nil || dep.MinVer != nil) && version == nil { + return nil + } + + if dep.Name != pname { + return nil + } + + if version == nil || version.Satisfies(dep) { + // Todo + add(conflicts, name, pkg.Name() + " (" + provide + ")") + return fmt.Errorf("") + } + + return nil + }) + + return nil + }) + + return nil +} + +func checkConflict(name string, conflict string, conflicts map[string]stringSet) error { + add := func(h map[string]stringSet, n string, v string) { + _, ok := h[n] + if !ok { + h[n] = make(stringSet) + } + h[n].set(v) + } + + localDb, err := alpmHandle.LocalDb() + if err != nil { + return err + } + + deps, err := gopkg.ParseDeps([]string{conflict}) + if err != nil { + return nil + } + + dep := deps[0] + + localDb.PkgCache().ForEach(func(pkg alpm.Package) error { + if name == pkg.Name() { + return nil + } + + version, err := gopkg.NewCompleteVersion(pkg.Version()) + if err != nil { + return nil + } + + if dep.Name == pkg.Name() && version.Satisfies(dep) { + add(conflicts, name, pkg.Name()) + return nil + } + + pkg.Provides().ForEach(func(provide alpm.Depend) error { + if dep.Name != provide.Name { + return nil + } + + // Provides arent version unless explicitly defined as + // such. If a conflict is versioned but a provide is + // not it can not conflict. + if (dep.MaxVer != nil || dep.MinVer != nil) && provide.Mod == alpm.DepModAny { + return nil + } + + if provide.Mod == alpm.DepModAny { + add(conflicts, name, pkg.Name() + " (" + provide.Name + ")") + return fmt.Errorf("") + } + + version, err := gopkg.NewCompleteVersion(provide.Version) + if err != nil { + return nil + } + + if version.Satisfies(dep) { + add(conflicts, name, pkg.Name() + " (" + provide.Name + ")") + return fmt.Errorf("") + } + + return nil + }) + + return nil + }) + + return nil +} + + +func checkForConflicts(dc *depCatagories) (map[string]stringSet, error) { + conflicts := make(map[string]stringSet) + + for _, pkg := range dc.Aur { + for _, cpkg := range pkg.Conflicts { + checkConflict(pkg.Name, cpkg, conflicts) + } + } + + for _, pkg := range dc.Repo { + pkg.Conflicts().ForEach(func(conflict alpm.Depend) error { + checkConflict(pkg.Name(), conflict.String(), conflicts) return nil }) } - if len(toRemove) != 0 { + for _, pkg := range dc.Aur { + checkReverseConflict(pkg.Name, pkg.Name, conflicts) + for _, ppkg := range pkg.Provides { + checkReverseConflict(pkg.Name, ppkg, conflicts) + } + } + + for _, pkg := range dc.Repo { + checkReverseConflict(pkg.Name(), pkg.Name(), conflicts) + pkg.Provides().ForEach(func(provide alpm.Depend) error { + checkReverseConflict(pkg.Name(), provide.String(), conflicts) + return nil + }) + } + + return conflicts, nil +} + +func checkForAllConflicts(dc *depCatagories) error { + var err error + var conflicts map[string]stringSet + var innerConflicts map[string]stringSet + var wg sync.WaitGroup + wg.Add(2) + + + fmt.Println(bold(cyan("::")+ " Checking for conflicts...")) + go func() { + conflicts, err = checkForConflicts(dc) + wg.Done() + }() + + fmt.Println(bold(cyan("::")+ " Checking for inner conflicts...")) + go func() { + innerConflicts = checkForInnerConflicts(dc) + wg.Done() + }() + + wg.Wait() + if len(innerConflicts) != 0 { fmt.Println( - red("Package conflicts found:")) - for name, pkgs := range toRemove { - str := "\tInstalling " + magenta(name) + " will remove" + red("\nInner conflicts found:")) + for name, pkgs := range innerConflicts { + str := "\t" + name + ":" + for pkg := range pkgs { + str += " " + magenta(pkg) + } + + fmt.Println(str) + } + + return fmt.Errorf("Aborting") + } + + if len(conflicts) != 0 { + fmt.Println( + red("\nPackage conflicts found:")) + for name, pkgs := range conflicts { + str := "\tInstalling " + magenta(name) + " will remove:" for pkg := range pkgs { str += " " + magenta(pkg) } @@ -736,6 +1040,7 @@ func clean(pkgs []*rpc.Pkg) { } func completeFileName(dir, name string) (string, error) { + files, err := ioutil.ReadDir(dir) if err != nil { return "", err