From c24b7c85e3b2647df5994252f979cf2ec9f14369 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Sun, 8 Sep 2024 11:49:27 +0300 Subject: [PATCH] Improved dependency resolution and improved the 'install' subcommand --- main.go | 144 +++++++++++++++++++++++++---------------- utils/package_utils.go | 78 +++++++++------------- 2 files changed, 119 insertions(+), 103 deletions(-) diff --git a/main.go b/main.go index 5f34ceb..17bdd18 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ var showInstalled = false var pkgListNumbers = false var pkgListNames = false var reinstall = false +var reinstallAll = false var noOptional = false var nosync = true @@ -208,13 +209,13 @@ func resolveCommand() { }]() for _, pkg := range clone.Keys() { value := clone.GetElement(pkg).Value - resolved, u, err := utils.ResolveAll(value.pkgInfo, false, !noOptional, !reinstall, rootDir) - if err != nil { - log.Fatalf("Could not resolve dependencies for package (%s). Error: %s\n", pkg, err) - } - unresolvedDepends = append(unresolvedDepends, u...) + resolved, unresolved := value.pkgInfo.ResolveAll(&[]string{}, &[]string{}, false, !noOptional, !reinstall, rootDir) + unresolvedDepends = append(unresolvedDepends, unresolved...) for _, depend := range resolved { if _, ok := pkgsToFetch.Get(depend); !ok && depend != value.pkgInfo.Name { + if !reinstallAll && utils.IsPackageInstalled(depend, rootDir) { + continue + } entry, _, err := utils.GetRepositoryEntry(depend) if err != nil { log.Fatalf("Could not find package (%s) in any repository\n", pkg) @@ -228,24 +229,15 @@ func resolveCommand() { pkgsToFetch.Set(pkg, value) } - clone = pkgsToFetch.Copy() - pkgsToFetch = orderedmap.NewOrderedMap[string, *struct { - isDependency bool - pkgInfo *utils.PackageInfo - }]() - for _, pkg := range clone.Keys() { + for _, pkg := range pkgsToInstall.Keys() { value := clone.GetElement(pkg).Value - entry, _, err := utils.GetRepositoryEntry(pkg) - if err != nil { - log.Fatalf("Could not read package. Error: %s\n", err) - } - resolved, u, err := utils.ResolveAll(entry.Info, false, !noOptional, !reinstall, rootDir) - if err != nil { - log.Fatalf("Could not resolve dependencies for package (%s). Error: %s\n", pkg, err) - } - unresolvedDepends = append(unresolvedDepends, u...) + resolved, unresolved := value.pkgInfo.ResolveAll(&[]string{}, &[]string{}, false, !noOptional, !reinstall, rootDir) + unresolvedDepends = append(unresolvedDepends, unresolved...) for _, depend := range resolved { if _, ok := clone.Get(depend); !ok { + if !reinstallAll && utils.IsPackageInstalled(depend, rootDir) { + continue + } entry, _, err := utils.GetRepositoryEntry(depend) if err != nil { log.Fatalf("Could not find package (%s) in any repository\n", pkg) @@ -259,7 +251,64 @@ func resolveCommand() { pkgsToFetch.Set(pkg, value) } + // Show summary + if len(unresolvedDepends) != 0 { + if force { + log.Fatalf("The following dependencies could not be found in any repositories: %s\n", strings.Join(unresolvedDepends, ", ")) + } else { + log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(unresolvedDepends, ", ")) + } + } + if pkgsToInstall.Len()+pkgsToFetch.Len() == 0 { + fmt.Println("All packages are up to date!") + os.Exit(0) + } + for _, pkg := range pkgsToInstall.Keys() { + pkgInfo, err := utils.ReadPackage(pkg) + if err != nil { + if err != nil { + log.Fatalf("Could not read package. Error: %s\n", err) + } + } + installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false) + if installedInfo == nil { + fmt.Printf("%s: %s (Install)\n", pkgInfo.Name, pkgInfo.Version) + } else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 { + fmt.Printf("%s: %s -> %s (Downgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version) + } else if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 { + fmt.Printf("%s: %s -> %s (Upgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version) + } else { + fmt.Printf("%s: %s (Reinstall)\n", pkgInfo.Name, pkgInfo.Version) + } + } + for _, pkg := range pkgsToFetch.Keys() { + pkgInfo := pkgsToFetch.GetElement(pkg).Value.pkgInfo + installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false) + if installedInfo == nil { + fmt.Printf("%s: %s (Install)\n", pkgInfo.Name, pkgInfo.Version) + } else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 { + fmt.Printf("%s: %s -> %s (Downgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version) + } else if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 { + fmt.Printf("%s: %s -> %s (Upgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version) + } else { + fmt.Printf("%s: %s (Reinstall)\n", pkgInfo.Name, pkgInfo.Version) + } + } + if rootDir != "/" { + fmt.Println("Warning: Operating in " + rootDir) + } + if !yesAll { + reader := bufio.NewReader(os.Stdin) + fmt.Print("Do you wish to install these packages? [y\\N] ") + text, _ := reader.ReadString('\n') + if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { + fmt.Println("Cancelling...") + os.Exit(1) + } + } + // Fetch packages from repositories + fmt.Println("Fetching packages from available repositories...") for _, pkg := range pkgsToFetch.Keys() { isDependency, _ := pkgsToFetch.Get(pkg) entry, repo, err := utils.GetRepositoryEntry(pkg) @@ -273,40 +322,16 @@ func resolveCommand() { pkgsToInstall.Set(fetchedPackage, isDependency) } - // Install Packages - if rootDir != "/" { - fmt.Println("Warning: Operating in " + rootDir) - } - if pkgsToInstall.Len() == 0 { - fmt.Println("All packages are up to date!") - os.Exit(0) - } - for _, pkg := range pkgsToInstall.Keys() { - pkgInfo := pkgsToInstall.GetElement(pkg).Value.pkgInfo - installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false) - if installedInfo == nil { - fmt.Printf("%s: %s (Install)\n", pkgInfo.Name, pkgInfo.Version) - } else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 { - fmt.Printf("%s: %s -> %s (Downgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version) - } else if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 { - fmt.Printf("%s: %s -> %s (Upgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version) - } else { - fmt.Printf("%s: %s (Reinstall)\n", pkgInfo.Name, pkgInfo.Version) - } - } - if !yesAll { - reader := bufio.NewReader(os.Stdin) - fmt.Print("Do you wish to install these packages? [y\\N] ") - text, _ := reader.ReadString('\n') - if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { - fmt.Println("Cancelling...") - os.Exit(1) - } - } for _, pkg := range pkgsToInstall.Keys() { value, _ := pkgsToInstall.Get(pkg) pkgInfo := value.pkgInfo - err := utils.InstallPackage(pkg, rootDir, verbose, force, buildSource, skipCheck, keepTempDir) + var err error + if value.isDependency { + err = utils.InstallPackage(pkg, rootDir, verbose, true, buildSource, skipCheck, keepTempDir) + } else { + err = utils.InstallPackage(pkg, rootDir, verbose, force, buildSource, skipCheck, keepTempDir) + } + if err != nil { if pkgInfo.Type == "source" && keepTempDir { fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name) @@ -441,14 +466,14 @@ func printHelp() { fmt.Println("-> flags will be read if passed right after the subcommand otherwise they will be read as subcommand arguments") fmt.Println("\033[1m---- Command List ----\033[0m") fmt.Println("-> bpm version | shows information on the installed version of bpm") - fmt.Println("-> bpm info [-R] | shows information on an installed package") + fmt.Println("-> bpm info [-R, -i] | shows information on an installed package") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -i shows information about the currently installed package") fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -c lists the amount of installed packages") fmt.Println(" -n lists only the names of installed packages") - fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --no-optional] | installs the following files") + fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --reinstall-all, --no-optional] | installs the following files") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -y skips the confirmation prompt") @@ -458,15 +483,16 @@ func printHelp() { fmt.Println(" -b creates a binary package from a source package after compilation and saves it in the binary package output directory") fmt.Println(" -k keeps the compilation directory created by BPM after source package installation") fmt.Println(" --reinstall Reinstalls packages even if they do not have a newer version available") + fmt.Println(" --reinstall-all Same as --reinstall but also reinstalls dependencies") fmt.Println(" --no-optional Prevents installation of optional dependencies") - fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --nosync] | updates all packages that are available in the repositories") + fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --no-sync] | updates all packages that are available in the repositories") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -f skips dependency, conflict and architecture checking") fmt.Println(" --reinstall Fetches and reinstalls all packages even if they do not have a newer version available") - fmt.Println(" --nosync Skips package database syncing") - fmt.Println("-> bpm sync [-R, -v] | Syncs package databases without updating packages") + fmt.Println(" --no-sync Skips package database syncing") + fmt.Println("-> bpm sync [-R, -v, -y] | Syncs package databases without updating packages") fmt.Println(" -R= lets you define the root path which will be used") fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -y skips the confirmation prompt") @@ -503,6 +529,7 @@ func resolveFlags() { installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation") installFlagSet.BoolVar(&force, "f", false, "Force installation by skipping architecture and dependency resolution") installFlagSet.BoolVar(&reinstall, "reinstall", false, "Reinstalls packages even if they do not have a newer version available") + installFlagSet.BoolVar(&reinstallAll, "reinstall-all", false, "Same as --reinstall but also reinstalls dependencies") installFlagSet.BoolVar(&noOptional, "no-optional", false, "Prevents installation of optional dependencies") installFlagSet.Usage = printHelp // Update flags @@ -512,7 +539,7 @@ func resolveFlags() { updateFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") updateFlagSet.BoolVar(&force, "f", false, "Force update by skipping architecture and dependency resolution") updateFlagSet.BoolVar(&reinstall, "reinstall", false, "Fetches and reinstalls all packages even if they do not have a newer version available") - updateFlagSet.BoolVar(&nosync, "nosync", false, "Skips package database syncing") + updateFlagSet.BoolVar(&nosync, "no-sync", false, "Skips package database syncing") updateFlagSet.Usage = printHelp // Sync flags syncFlagSet := flag.NewFlagSet("Sync flags", flag.ExitOnError) @@ -578,5 +605,8 @@ func resolveFlags() { } subcommandArgs = fileFlagSet.Args() } + if reinstallAll { + reinstall = true + } } } diff --git a/utils/package_utils.go b/utils/package_utils.go index 22286f5..fbf92c6 100644 --- a/utils/package_utils.go +++ b/utils/package_utils.go @@ -1002,7 +1002,7 @@ func InstallPackage(filename, rootDir string, verbose, force, binaryPkgFromSrc, if pkgInfo.Arch != "any" && pkgInfo.Arch != GetArch() { return errors.New("cannot install a package with a different architecture") } - if unresolved := CheckDependencies(pkgInfo, pkgInfo.Type == "source", true, rootDir); len(unresolved) != 0 { + if unresolved := pkgInfo.CheckDependencies(pkgInfo.Type == "source", true, rootDir); len(unresolved) != 0 { return errors.New("Could not resolve all dependencies. Missing " + strings.Join(unresolved, ", ")) } } @@ -1225,7 +1225,19 @@ func GetSourceScript(filename string) (string, error) { return "", errors.New("package does not contain a source.sh file") } -func CheckDependencies(pkgInfo *PackageInfo, checkMake, checkOptional bool, rootDir string) []string { +func (pkgInfo *PackageInfo) GetAllDependencies(checkMake, checkOptional bool) []string { + allDepends := make([]string, 0) + allDepends = append(allDepends, pkgInfo.Depends...) + if checkMake { + allDepends = append(allDepends, pkgInfo.MakeDepends...) + } + if checkOptional { + allDepends = append(allDepends, pkgInfo.OptionalDepends...) + } + return allDepends +} + +func (pkgInfo *PackageInfo) CheckDependencies(checkMake, checkOptional bool, rootDir string) []string { var ret []string for _, dependency := range pkgInfo.Depends { if !IsPackageProvided(dependency, rootDir) { @@ -1250,7 +1262,7 @@ func CheckDependencies(pkgInfo *PackageInfo, checkMake, checkOptional bool, root return ret } -func CheckConflicts(pkgInfo *PackageInfo, checkConditional bool, rootDir string) []string { +func (pkgInfo *PackageInfo) CheckConflicts(rootDir string) []string { var ret []string for _, conflict := range pkgInfo.Conflicts { if IsPackageInstalled(conflict, rootDir) { @@ -1260,52 +1272,26 @@ func CheckConflicts(pkgInfo *PackageInfo, checkConditional bool, rootDir string) return ret } -func ResolveAll(pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled bool, rootDir string) ([]string, []string, error) { - resolved := make([]string, 0) - unresolved := make([]string, 0) - toResolve := make([]string, 0) - - toResolve = append(toResolve, pkgInfo.Depends...) - if checkMake { - toResolve = append(toResolve, pkgInfo.MakeDepends...) - } - if checkOptional { - toResolve = append(toResolve, pkgInfo.OptionalDepends...) - } - - resolve := func(depend string) { - if slices.Contains(resolved, depend) { - return - } - if ignoreInstalled && IsPackageInstalled(depend, rootDir) { - return - } - entry, _, err := GetRepositoryEntry(depend) - if err != nil { - if !slices.Contains(unresolved, depend) { - unresolved = append(unresolved, depend) +func (pkgInfo *PackageInfo) ResolveAll(resolved, unresolved *[]string, checkMake, checkOptional, ignoreInstalled bool, rootDir string) ([]string, []string) { + *unresolved = append(*unresolved, pkgInfo.Name) + for _, depend := range pkgInfo.GetAllDependencies(checkMake, checkOptional) { + if !slices.Contains(*resolved, depend) { + if slices.Contains(*unresolved, depend) || (ignoreInstalled && IsPackageInstalled(depend, rootDir)) { + continue } - return - } - resolved = append(resolved, depend) - toResolve = append(toResolve, entry.Info.Depends...) - if checkMake { - toResolve = append(toResolve, entry.Info.MakeDepends...) - } - if checkOptional { - toResolve = append(toResolve, entry.Info.OptionalDepends...) + entry, _, err := GetRepositoryEntry(depend) + if err != nil { + if !slices.Contains(*unresolved, depend) { + *unresolved = append(*unresolved, depend) + } + continue + } + entry.Info.ResolveAll(resolved, unresolved, checkMake, checkOptional, ignoreInstalled, rootDir) } } - - for len(toResolve) > 0 { - clone := slices.Clone(toResolve) - toResolve = make([]string, 0) - for _, depend := range clone { - resolve(depend) - } - } - - return resolved, unresolved, nil + *resolved = append(*resolved, pkgInfo.Name) + *unresolved = stringSliceRemove(*unresolved, pkgInfo.Name) + return *resolved, *unresolved } func IsPackageInstalled(pkg, rootDir string) bool {