From 0801612166e261ff1ec7cd7278d15e5296216953 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Mon, 7 Apr 2025 21:03:33 +0300 Subject: [PATCH] Create functions for basic package management in bpmlib --- src/bpm/main.go | 246 ++++++++--------------------------------- src/bpmlib/general.go | 250 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 203 deletions(-) create mode 100644 src/bpmlib/general.go diff --git a/src/bpm/main.go b/src/bpm/main.go index 58de8d7..f263423 100644 --- a/src/bpm/main.go +++ b/src/bpm/main.go @@ -202,107 +202,41 @@ func resolveCommand() { } } case install: + // Check for required permissions if os.Getuid() != 0 { log.Fatalf("Error: this subcommand needs to be run with superuser permissions") } - pkgs := subcommandArgs - if len(pkgs) == 0 { + + // Return if no packages are specified + if len(subcommandArgs) == 0 { fmt.Println("No packages or files were given to install") return } // Check if installationReason argument is valid ir := bpmlib.Unknown - if installationReason == "manual" { + switch installationReason { + case "manual": ir = bpmlib.Manual - } else if installationReason == "dependency" { + case "dependency": ir = bpmlib.Dependency - } else if installationReason != "" { + case "": + default: log.Fatalf("Error: %s is not a valid installation reason", installationReason) } - operation := bpmlib.BPMOperation{ - Actions: make([]bpmlib.OperationAction, 0), - UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), - RootDir: rootDir, - ForceInstallationReason: ir, + // Get reinstall method + var reinstallMethod bpmlib.ReinstallMethod + if reinstallAll { + reinstallMethod = bpmlib.ReinstallMethodAll + } else if reinstall { + reinstallMethod = bpmlib.ReinstallMethodSpecified + } else { + reinstallMethod = bpmlib.ReinstallMethodNone } - // Search for packages - for _, pkg := range pkgs { - if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() { - bpmpkg, err := bpmlib.ReadPackage(pkg) - if err != nil { - log.Fatalf("Error: could not read package: %s\n", err) - } - if !reinstall && bpmlib.IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && bpmlib.GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() { - continue - } - operation.AppendAction(&bpmlib.InstallPackageAction{ - File: pkg, - IsDependency: false, - BpmPackage: bpmpkg, - }) - } else { - var entry *bpmlib.RepositoryEntry - - if e, _, err := bpmlib.GetRepositoryEntry(pkg); err == nil { - entry = e - } else if isVirtual, p := bpmlib.IsVirtualPackage(pkg, rootDir); isVirtual { - entry, _, err = bpmlib.GetRepositoryEntry(p) - if err != nil { - log.Fatalf("Error: could not find package (%s) in any repository\n", p) - } - } else if e := bpmlib.ResolveVirtualPackage(pkg); e != nil { - entry = e - } else { - log.Fatalf("Error: could not find package (%s) in any repository\n", pkg) - } - if !reinstall && bpmlib.IsPackageInstalled(entry.Info.Name, rootDir) && bpmlib.GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() { - continue - } - operation.AppendAction(&bpmlib.FetchPackageAction{ - IsDependency: false, - RepositoryEntry: entry, - }) - } - } - - // Resolve dependencies - err := operation.ResolveDependencies(reinstallAll, !noOptional, verbose) - if err != nil { - log.Fatalf("Error: could not resolve dependencies: %s\n", err) - } - if len(operation.UnresolvedDepends) != 0 { - if !force { - log.Fatalf("Error: the following dependencies could not be found in any repositories: %s\n", strings.Join(operation.UnresolvedDepends, ", ")) - } else { - log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(operation.UnresolvedDepends, ", ")) - } - } - - // Replace obsolete packages - operation.ReplaceObsoletePackages() - - // Check for conflicts - conflicts, err := operation.CheckForConflicts() - if err != nil { - log.Fatalf("Error: could not complete package conflict check: %s\n", err) - } - if len(conflicts) > 0 { - if !force { - log.Println("Error: conflicting packages found") - } else { - log.Fatalf("Warning: conflicting packages found") - } - for pkg, conflict := range conflicts { - fmt.Printf("%s is in conflict with the following packages: %s\n", pkg, strings.Join(conflict, ", ")) - } - if !force { - os.Exit(0) - } - } + // Create Installation Operation + operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, !noOptional, force, verbose, subcommandArgs...) // Show operation summary operation.ShowOperationSummary() @@ -336,81 +270,17 @@ func resolveCommand() { log.Fatalf("Error: could not run hooks: %s\n", err) } case update: + // Check for required permissions if os.Getuid() != 0 { log.Fatalf("Error: this subcommand needs to be run with superuser permissions") } - // Sync repositories - if !nosync { - for _, repo := range bpmlib.BPMConfig.Repositories { - fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name) - err := repo.SyncLocalDatabase() - if err != nil { - log.Fatalf("Error: could not sync local database for repository (%s): %s\n", repo.Name, err) - } - } - fmt.Println("All package databases synced successfully!") - } - - bpmlib.ReadConfig() - - // Get installed packages and check for updates - pkgs, err := bpmlib.GetInstalledPackages(rootDir) + // Create Update Operation + operation, err := bpmlib.UpdatePackages(rootDir, !nosync, !noOptional, force, verbose) if err != nil { - log.Fatalf("Error: could not get installed packages: %s\n", err) + log.Fatalf("Error: could not update packages: %s\n", err) } - operation := bpmlib.BPMOperation{ - Actions: make([]bpmlib.OperationAction, 0), - UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), - RootDir: rootDir, - ForceInstallationReason: bpmlib.Unknown, - } - - // Search for packages - for _, pkg := range pkgs { - if slices.Contains(bpmlib.BPMConfig.IgnorePackages, pkg) { - continue - } - var entry *bpmlib.RepositoryEntry - // Check if installed package can be replaced and install that instead - if e := bpmlib.FindReplacement(pkg); e != nil { - entry = e - } else if entry, _, err = bpmlib.GetRepositoryEntry(pkg); err != nil { - continue - } - - installedInfo := bpmlib.GetPackageInfo(pkg, rootDir) - if installedInfo == nil { - log.Fatalf("Error: could not get package info for (%s)\n", pkg) - } else { - comparison := bpmlib.ComparePackageVersions(*entry.Info, *installedInfo) - if comparison > 0 || reinstall { - operation.AppendAction(&bpmlib.FetchPackageAction{ - IsDependency: false, - RepositoryEntry: entry, - }) - } - } - } - - // Check for new dependencies in updated packages - err = operation.ResolveDependencies(reinstallAll, !noOptional, verbose) - if err != nil { - log.Fatalf("Error: could not resolve dependencies: %s\n", err) - } - if len(operation.UnresolvedDepends) != 0 { - if !force { - log.Fatalf("Error: the following dependencies could not be found in any repositories: %s\n", strings.Join(operation.UnresolvedDepends, ", ")) - } else { - log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(operation.UnresolvedDepends, ", ")) - } - } - - // Replace obsolete packages - operation.ReplaceObsoletePackages() - // Show operation summary operation.ShowOperationSummary() @@ -438,9 +308,12 @@ func resolveCommand() { log.Fatalf("Error: could not run hooks: %s\n", err) } case sync: + // Check for required permissions if os.Getuid() != 0 { log.Fatalf("Error: this subcommand needs to be run with superuser permissions") } + + // Confirmation Prompt if !yesAll { fmt.Printf("Are you sure you wish to sync all databases? [y\\N] ") reader := bufio.NewReader(os.Stdin) @@ -450,54 +323,28 @@ func resolveCommand() { os.Exit(1) } } - for _, repo := range bpmlib.BPMConfig.Repositories { - fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name) - err := repo.SyncLocalDatabase() - if err != nil { - log.Fatalf("Error: could not sync local database for repository (%s): %s\n", repo.Name, err) - } + + err := bpmlib.SyncDatabase(verbose) + if err != nil { + log.Fatalf("Error: could not sync local database: %s\n", err) } + fmt.Println("All package databases synced successfully!") case remove: + // Check for required permissions if os.Getuid() != 0 { log.Fatalf("Error: this subcommand needs to be run with superuser permissions") } - packages := subcommandArgs - if len(packages) == 0 { + + if len(subcommandArgs) == 0 { fmt.Println("No packages were given") return } - operation := &bpmlib.BPMOperation{ - Actions: make([]bpmlib.OperationAction, 0), - UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), - RootDir: rootDir, - } - - // Search for packages - for _, pkg := range packages { - bpmpkg := bpmlib.GetPackage(pkg, rootDir) - if bpmpkg == nil { - continue - } - operation.AppendAction(&bpmlib.RemovePackageAction{BpmPackage: bpmpkg}) - } - - // Skip needed packages if the --unused flag is on - if removeUnused { - err := operation.RemoveNeededPackages() - if err != nil { - log.Fatalf("Error: could not skip needed packages: %s\n", err) - } - } - - // Do package cleanup - if doCleanup { - err := operation.Cleanup(verbose) - if err != nil { - log.Fatalf("Error: could not perform cleanup for operation: %s\n", err) - } + // Remove packages + operation, err := bpmlib.RemovePackages(rootDir, removeUnused, doCleanup, verbose, subcommandArgs...) + if err != nil { + log.Fatalf("Error: could not remove packages: %s\n", err) } // Show operation summary @@ -515,7 +362,7 @@ func resolveCommand() { } // Execute operation - err := operation.Execute(verbose, force) + err = operation.Execute(verbose, force) if err != nil { log.Fatalf("Error: could not complete operation: %s\n", err) } @@ -527,21 +374,15 @@ func resolveCommand() { log.Fatalf("Error: could not run hooks: %s\n", err) } case cleanup: + // Check for required permissions if os.Getuid() != 0 { log.Fatalf("Error: this subcommand needs to be run with superuser permissions") } - operation := &bpmlib.BPMOperation{ - Actions: make([]bpmlib.OperationAction, 0), - UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), - RootDir: rootDir, - } - - // Do package cleanup - err := operation.Cleanup(verbose) + // Do cleanup + operation, err := bpmlib.CleanupPackages(rootDir, verbose) if err != nil { - log.Fatalf("Error: could not perform cleanup for operation: %s\n", err) + log.Fatalf("Error: could not cleanup packages: %s\n", err) } // Show operation summary @@ -711,7 +552,6 @@ func resolveFlags() { updateFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing") 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, "no-sync", false, "Skips package database syncing") updateFlagSet.Usage = printHelp // Sync flags diff --git a/src/bpmlib/general.go b/src/bpmlib/general.go new file mode 100644 index 0000000..57d7eb2 --- /dev/null +++ b/src/bpmlib/general.go @@ -0,0 +1,250 @@ +package bpmlib + +import ( + "fmt" + "log" + "os" + "slices" + "strings" +) + +type ReinstallMethod uint8 + +const ( + ReinstallMethodNone ReinstallMethod = iota + ReinstallMethodSpecified ReinstallMethod = iota + ReinstallMethodAll ReinstallMethod = iota +) + +// InstallPackages installs the specified packages into the given root directory by fetching them from repositories or directly from local bpm archives +func InstallPackages(rootDir string, installationReason InstallationReason, reinstallMethod ReinstallMethod, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) { + // Setup operation struct + operation = &BPMOperation{ + Actions: make([]OperationAction, 0), + UnresolvedDepends: make([]string, 0), + Changes: make(map[string]string), + RootDir: rootDir, + ForceInstallationReason: installationReason, + } + + // Resolve packages + for _, pkg := range packages { + if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() { + bpmpkg, err := ReadPackage(pkg) + if err != nil { + return nil, fmt.Errorf("could not read package: %s", err) + } + if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() { + continue + } + operation.AppendAction(&InstallPackageAction{ + File: pkg, + IsDependency: false, + BpmPackage: bpmpkg, + }) + } else { + var entry *RepositoryEntry + + if e, _, err := GetRepositoryEntry(pkg); err == nil { + entry = e + } else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual { + entry, _, err = GetRepositoryEntry(p) + if err != nil { + return nil, fmt.Errorf("could not find package (%s) in any repositor", p) + } + } else if e := ResolveVirtualPackage(pkg); e != nil { + entry = e + } else { + return nil, fmt.Errorf("could not find package (%s) in any repository", pkg) + } + if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() { + continue + } + operation.AppendAction(&FetchPackageAction{ + IsDependency: false, + RepositoryEntry: entry, + }) + } + } + + // Resolve dependencies + err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installOptionalDependencies, verbose) + if err != nil { + return nil, fmt.Errorf("could not resolve dependencies: %s", err) + } + if len(operation.UnresolvedDepends) != 0 { + if !forceInstallation { + return nil, fmt.Errorf("dependencies (%s) could not be found in any repositories", strings.Join(operation.UnresolvedDepends, ", ")) + } else if verbose { + log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(operation.UnresolvedDepends, ", ")) + } + } + + // Replace obsolete packages + operation.ReplaceObsoletePackages() + + // Check for conflicts + conflicts, err := operation.CheckForConflicts() + if err != nil { + return nil, fmt.Errorf("could not complete package conflict check: %s", err) + } + if len(conflicts) > 0 { + if verbose { + for pkg, conflict := range conflicts { + fmt.Printf("%s is in conflict with packages (%s)\n", pkg, strings.Join(conflict, ", ")) + } + } + if !forceInstallation { + return nil, fmt.Errorf("conflicting packages found") + } else { + log.Println("Warning: conflicting packages found") + } + } + + return operation, nil +} + +// RemovePackages removes the specified packages from the given root directory +func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencies, verbose bool, packages ...string) (operation *BPMOperation, err error) { + operation = &BPMOperation{ + Actions: make([]OperationAction, 0), + UnresolvedDepends: make([]string, 0), + Changes: make(map[string]string), + RootDir: rootDir, + } + + // Search for packages + for _, pkg := range packages { + bpmpkg := GetPackage(pkg, rootDir) + if bpmpkg == nil { + continue + } + operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg}) + } + + // Do not remove packages which other packages depend on + if removeUnusedPackagesOnly { + err := operation.RemoveNeededPackages() + if err != nil { + return nil, fmt.Errorf("could not skip needed packages: %s", err) + } + } + + // Do package cleanup + if cleanupDependencies { + err := operation.Cleanup(verbose) + if err != nil { + return nil, fmt.Errorf("could not perform cleanup for operation: %s", err) + } + } + return operation, nil +} + +// CleanupPackages finds packages installed as dependencies which are no longer required by the rest of the system in the given root directory +func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err error) { + operation = &BPMOperation{ + Actions: make([]OperationAction, 0), + UnresolvedDepends: make([]string, 0), + Changes: make(map[string]string), + RootDir: rootDir, + } + + // Do package cleanup + err = operation.Cleanup(verbose) + if err != nil { + return nil, fmt.Errorf("could not perform cleanup for operation: %s", err) + } + + return operation, nil +} + +// UpdatePackages fetches the newest versions of all installed packages from +func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) { + // Sync repositories + if syncDatabase { + err := SyncDatabase(verbose) + if err != nil { + return nil, fmt.Errorf("could not sync local database: %s", err) + } + if verbose { + fmt.Println("All package databases synced successfully!") + } + } + + // Reload config and local databases + ReadConfig() + + // Get installed packages and check for updates + pkgs, err := GetInstalledPackages(rootDir) + if err != nil { + return nil, fmt.Errorf("could not get installed packages: %s", err) + } + + operation = &BPMOperation{ + Actions: make([]OperationAction, 0), + UnresolvedDepends: make([]string, 0), + Changes: make(map[string]string), + RootDir: rootDir, + ForceInstallationReason: Unknown, + } + + // Search for packages + for _, pkg := range pkgs { + if slices.Contains(BPMConfig.IgnorePackages, pkg) { + continue + } + var entry *RepositoryEntry + // Check if installed package can be replaced and install that instead + if e := FindReplacement(pkg); e != nil { + entry = e + } else if entry, _, err = GetRepositoryEntry(pkg); err != nil { + continue + } + + installedInfo := GetPackageInfo(pkg, rootDir) + if installedInfo == nil { + return nil, fmt.Errorf("could not get package info for package (%s)", pkg) + } else { + comparison := ComparePackageVersions(*entry.Info, *installedInfo) + if comparison > 0 { + operation.AppendAction(&FetchPackageAction{ + IsDependency: false, + RepositoryEntry: entry, + }) + } + } + } + + // Check for new dependencies in updated packages + err = operation.ResolveDependencies(false, installOptionalDependencies, verbose) + if err != nil { + return nil, fmt.Errorf("could not resolve dependencies: %s", err) + } + if len(operation.UnresolvedDepends) != 0 { + if !forceInstallation { + return nil, fmt.Errorf("dependencies (%s) could not be found in any repositories", strings.Join(operation.UnresolvedDepends, ", ")) + } else if verbose { + log.Printf("Warning: dependencies (%s) could not be found in any repositories\n", strings.Join(operation.UnresolvedDepends, ", ")) + } + } + + // Replace obsolete packages + operation.ReplaceObsoletePackages() + return operation, nil +} + +// SyncDatabase syncs all databases declared in /etc/bpm.conf +func SyncDatabase(verbose bool) (err error) { + for _, repo := range BPMConfig.Repositories { + if verbose { + fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name) + } + + err := repo.SyncLocalDatabase() + if err != nil { + return err + } + } + + return nil +}