18 Commits
Author SHA1 Message Date
EnumDev ad89650dc4 Sort packages by name correctly in 'list' subcommand 2026-02-05 18:08:34 +02:00
EnumDev 3a7adb27ab Replace 'human-readable' flag with 'show-bytes' 2026-02-05 18:02:02 +02:00
EnumDev f6aa01339e Show array length in readable info 2026-02-05 17:57:11 +02:00
EnumDev a29de2e52d Show virtual package providers in readable info 2026-02-05 17:52:40 +02:00
EnumDev 619335bbe6 Replace ResolveVirtualPackage function with GetDatabaseVirtualPackageEntry 2026-02-05 14:54:08 +02:00
EnumDev 8579c688d7 Use BFS for finding unused dependencies 2026-02-05 14:22:16 +02:00
EnumDev 48cec35f0c Cache installed virtual packages 2026-02-05 13:21:50 +02:00
EnumDev e19d64bf04 Replace an instance of GetPackage with GetPackageInfo 2026-02-04 14:50:25 +02:00
EnumDev ee80640153 Move installation reason to package local info 2026-02-04 14:48:52 +02:00
EnumDev e4d94e378f Move bpm locks to var/cache/bpm 2026-02-04 13:03:52 +02:00
EnumDev b37360ed82 Add package installation and last update dates 2026-02-03 21:25:33 +02:00
EnumDev ba0d288e45 Add check dependencies 2026-01-28 18:50:25 +02:00
EnumDev c1fa9d9474 Allow skipping tests during compilation in 'install' and 'update' subcommands 2026-01-28 13:02:37 +02:00
EnumDev 0742c81f2f Fix file ownership issues with package compilation as the root user 2026-01-28 12:33:26 +02:00
EnumDev dd20574470 More improvements for the readable info functions 2026-01-27 12:47:25 +02:00
EnumDev b3dc6e3e34 Add optional dependency descriptions 2026-01-26 13:25:25 +02:00
EnumDev 0a06808303 Improve readable info functions 2026-01-25 19:55:09 +02:00
EnumDev 9f8a20c671 Show arrays in multiple lines in readable info 2026-01-25 19:23:39 +02:00
9 changed files with 707 additions and 252 deletions
+129 -20
View File
@@ -13,6 +13,7 @@ import (
"slices" "slices"
"sort" "sort"
"strings" "strings"
"time"
"github.com/EnumeratedDev/bpm/src/bpmlib" "github.com/EnumeratedDev/bpm/src/bpmlib"
@@ -54,7 +55,7 @@ func main() {
currentFlagSet = flag.NewFlagSet("query", flag.ExitOnError) currentFlagSet = flag.NewFlagSet("query", flag.ExitOnError)
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory") currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("database", "d", false, "Show package information from remote databases") currentFlagSet.BoolP("database", "d", false, "Show package information from remote databases")
currentFlagSet.BoolP("human-readable", "h", false, "Show package installed size in a human readable format") currentFlagSet.BoolP("show-bytes", "b", false, "Show package installed size in bytes")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Show information on the specified packages", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Show information on the specified packages", os.Args[2:])
showPackageInfo() showPackageInfo()
@@ -70,7 +71,7 @@ func main() {
currentFlagSet.Bool("make-depends", false, "Show packages installed as make dependencies") currentFlagSet.Bool("make-depends", false, "Show packages installed as make dependencies")
currentFlagSet.String("sort", "", "Sort listed packages by 'name' or 'size") currentFlagSet.String("sort", "", "Sort listed packages by 'name' or 'size")
currentFlagSet.Bool("reverse", false, "Reverse the order in which packages are listed") currentFlagSet.Bool("reverse", false, "Reverse the order in which packages are listed")
currentFlagSet.BoolP("human-readable", "h", false, "Show package installed size in a human readable format") currentFlagSet.BoolP("show-bytes", "b", false, "Show package installed size in bytes")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "List packages", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "List packages", os.Args[2:])
showPackageList() showPackageList()
@@ -93,6 +94,7 @@ func main() {
currentFlagSet.BoolP("reinstall", "r", false, "Reinstall the specified packages") currentFlagSet.BoolP("reinstall", "r", false, "Reinstall the specified packages")
currentFlagSet.BoolP("reinstall-all", "a", false, "Reinstall the specified packages and their dependencies") currentFlagSet.BoolP("reinstall-all", "a", false, "Reinstall the specified packages and their dependencies")
currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation") currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation")
currentFlagSet.BoolP("skip-checks", "s", false, "Skip the check function in source.sh scripts")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Install the specified packages", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Install the specified packages", os.Args[2:])
installPackages() installPackages()
@@ -142,6 +144,7 @@ func main() {
currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases") currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases")
currentFlagSet.Bool("allow-downgrades", false, "Allow package downgrades") currentFlagSet.Bool("allow-downgrades", false, "Allow package downgrades")
currentFlagSet.BoolP("optional", "o", false, "Install all optional dependencies") currentFlagSet.BoolP("optional", "o", false, "Install all optional dependencies")
currentFlagSet.BoolP("skip-checks", "s", false, "Skip the check function in source.sh scripts")
currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation") currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Update installed packages", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Update installed packages", os.Args[2:])
@@ -174,6 +177,18 @@ func main() {
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compare two version numbers", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compare two version numbers", os.Args[2:])
compareVersions() compareVersions()
case "upgrade-persistent-data":
currentFlagSet = flag.NewFlagSet("upgrade-persistent-data", flag.ExitOnError)
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Upgrade BPM's persistent data directory contents", os.Args[2:])
rootDir, _ := currentFlagSet.GetString("root")
err = bpmlib.UpgradePersistentData(rootDir)
if err != nil {
log.Printf("Error: could not upgrade persistent data directory: %s", err)
exitCode = 1
return
}
default: default:
printUsage() printUsage()
exitCode = 1 exitCode = 1
@@ -188,7 +203,15 @@ func showPackageInfo() {
// Get flags // Get flags
rootDir, _ := currentFlagSet.GetString("root") rootDir, _ := currentFlagSet.GetString("root")
showDatabaseInfo, _ := currentFlagSet.GetBool("database") showDatabaseInfo, _ := currentFlagSet.GetBool("database")
showHumanReadableSize, _ := currentFlagSet.GetBool("human-readable") showBytes, _ := currentFlagSet.GetBool("show-bytes")
// Initialize installed packages map
err := bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Get packages // Get packages
packages := currentFlagSet.Args() packages := currentFlagSet.Args()
@@ -198,7 +221,7 @@ func showPackageInfo() {
} }
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err = bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Printf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1 exitCode = 1
@@ -211,7 +234,9 @@ func showPackageInfo() {
var entry *bpmlib.BPMDatabaseEntry var entry *bpmlib.BPMDatabaseEntry
entry, _, err = bpmlib.GetDatabaseEntry(pkg) entry, _, err = bpmlib.GetDatabaseEntry(pkg)
if err != nil { if err != nil {
if entry = bpmlib.ResolveVirtualPackage(pkg); entry == nil { if providers := bpmlib.GetDatabaseVirtualPackageEntry(pkg); len(providers) > 0 {
entry = providers[0]
} else {
log.Printf("Error: could not find package (%s) in any database\n", pkg) log.Printf("Error: could not find package (%s) in any database\n", pkg)
exitCode = 1 exitCode = 1
return return
@@ -221,7 +246,7 @@ func showPackageInfo() {
if n != 0 { if n != 0 {
fmt.Println() fmt.Println()
} }
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize)) fmt.Println(entry.CreateReadableInfo(rootDir, showBytes))
return return
} }
@@ -237,8 +262,8 @@ func showPackageInfo() {
} }
isFile = true isFile = true
} else { } else {
if isVirtual, p := bpmlib.IsVirtualPackage(pkg, rootDir); isVirtual { if providers := bpmlib.GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 {
bpmpkg = bpmlib.GetPackage(p, rootDir) bpmpkg = bpmlib.GetPackage(providers[0].Name, rootDir)
} else { } else {
bpmpkg = bpmlib.GetPackage(pkg, rootDir) bpmpkg = bpmlib.GetPackage(pkg, rootDir)
} }
@@ -262,12 +287,20 @@ func showPackageInfo() {
} }
fmt.Println(bpmpkg.PkgInfo.CreateReadableInfo(rootDir)) fmt.Println(bpmpkg.PkgInfo.CreateReadableInfo(rootDir))
if bpmpkg.PkgInfo.Type == "binary" { if bpmpkg.PkgInfo.Type == "binary" {
if !showHumanReadableSize { if showBytes {
fmt.Printf("Installed size: %d\n", bpmpkg.GetInstalledSize()) fmt.Printf("Installed size: %d\n", bpmpkg.GetInstalledSize())
} else { } else {
fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(bpmpkg.GetInstalledSize())) fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(bpmpkg.GetInstalledSize()))
} }
} }
if bpmlib.IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
format := "02/01/2006 15:04"
installedOnDate := time.Unix(bpmpkg.LocalInfo.InstalledOn, 0).Format(format)
lastUpdatedOn := time.Unix(bpmpkg.LocalInfo.LastUpdatedOn, 0).Format(format)
fmt.Printf("Installed on: %s\n", installedOnDate)
fmt.Printf("Last updated on: %s\n", lastUpdatedOn)
}
} }
} }
@@ -282,7 +315,7 @@ func showPackageList() {
showMakeDepends, _ := currentFlagSet.GetBool("make-depends") showMakeDepends, _ := currentFlagSet.GetBool("make-depends")
sortPackages, _ := currentFlagSet.GetString("sort") sortPackages, _ := currentFlagSet.GetString("sort")
reversePackages, _ := currentFlagSet.GetBool("reverse") reversePackages, _ := currentFlagSet.GetBool("reverse")
showHumanReadableSize, _ := currentFlagSet.GetBool("human-readable") showBytes, _ := currentFlagSet.GetBool("show-bytes")
if !isFlagSet(currentFlagSet, "manual") && !isFlagSet(currentFlagSet, "depends") && !isFlagSet(currentFlagSet, "make-depends") { if !isFlagSet(currentFlagSet, "manual") && !isFlagSet(currentFlagSet, "depends") && !isFlagSet(currentFlagSet, "make-depends") {
showManual = true showManual = true
@@ -290,8 +323,16 @@ func showPackageList() {
showMakeDepends = true showMakeDepends = true
} }
// Initialize installed packages map
err := bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err = bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Printf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1 exitCode = 1
@@ -329,6 +370,15 @@ func showPackageList() {
switch sortPackages { switch sortPackages {
case "", "name": case "", "name":
slices.SortFunc(installedPackages, func(a, b struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}) int {
return strings.Compare(a.pkgInfo.Name, b.pkgInfo.Name)
})
slices.SortFunc(databaseEntries, func(a, b *bpmlib.BPMDatabaseEntry) int {
return strings.Compare(a.Info.Name, b.Info.Name)
})
case "size": case "size":
slices.SortFunc(installedPackages, func(a, b struct { slices.SortFunc(installedPackages, func(a, b struct {
pkgInfo bpmlib.PackageInfo pkgInfo bpmlib.PackageInfo
@@ -363,7 +413,7 @@ func showPackageList() {
} }
} else { } else {
for _, pkg := range installedPackages { for _, pkg := range installedPackages {
installationReason := bpmlib.GetInstallationReason(pkg.pkgInfo.Name, rootDir) installationReason := bpmlib.GetPackage(pkg.pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
if installationReason == bpmlib.InstallationReasonManual && !showManual { if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends { } else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
@@ -387,7 +437,7 @@ func showPackageList() {
if n != 0 { if n != 0 {
fmt.Println() fmt.Println()
} }
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize)) fmt.Println(entry.CreateReadableInfo(rootDir, showBytes))
} }
} else { } else {
if len(installedPackages) == 0 { if len(installedPackages) == 0 {
@@ -395,7 +445,7 @@ func showPackageList() {
return return
} }
for n, pkg := range installedPackages { for n, pkg := range installedPackages {
installationReason := bpmlib.GetInstallationReason(pkg.pkgInfo.Name, rootDir) installationReason := bpmlib.GetPackage(pkg.pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
if installationReason == bpmlib.InstallationReasonManual && !showManual { if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends { } else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
@@ -412,12 +462,20 @@ func showPackageList() {
fmt.Println(pkg.pkgInfo.CreateReadableInfo(rootDir)) fmt.Println(pkg.pkgInfo.CreateReadableInfo(rootDir))
if pkg.pkgInfo.Type == "binary" { if pkg.pkgInfo.Type == "binary" {
if !showHumanReadableSize { if showBytes {
fmt.Printf("Installed size: %d\n", pkg.installedSize) fmt.Printf("Installed size: %d\n", pkg.installedSize)
} else { } else {
fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(pkg.installedSize)) fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(pkg.installedSize))
} }
} }
localInfo := bpmlib.GetPackage(pkg.pkgInfo.Name, rootDir).LocalInfo
format := "02/01/2006 15:04"
installedOnDate := time.Unix(localInfo.InstalledOn, 0).Format(format)
lastUpdatedOn := time.Unix(localInfo.LastUpdatedOn, 0).Format(format)
fmt.Printf("Installed on: %s\n", installedOnDate)
fmt.Printf("Last updated on: %s\n", lastUpdatedOn)
} }
} }
} }
@@ -489,6 +547,7 @@ func installPackages() {
installationReason, _ := currentFlagSet.GetString("installation-reason") installationReason, _ := currentFlagSet.GetString("installation-reason")
reinstall, _ := currentFlagSet.GetBool("reinstall") reinstall, _ := currentFlagSet.GetBool("reinstall")
reinstallAll, _ := currentFlagSet.GetBool("reinstall-all") reinstallAll, _ := currentFlagSet.GetBool("reinstall-all")
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
compilationJobs, _ := currentFlagSet.GetInt("jobs") compilationJobs, _ := currentFlagSet.GetInt("jobs")
// Get packages // Get packages
@@ -540,6 +599,14 @@ func installPackages() {
} }
defer fileLock.Unlock() defer fileLock.Unlock()
// Initialize installed packages map
err = bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Read local databases // Read local databases
err = bpmlib.ReadLocalDatabaseFiles() err = bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
@@ -549,7 +616,7 @@ func installPackages() {
} }
// Create installation operation // Create installation operation
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, installRuntime, installOptional, force, verbose, packages...) operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, installRuntime, installOptional, force, !skipChecks, verbose, packages...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
@@ -673,6 +740,14 @@ func removePackages() {
} }
defer fileLock.Unlock() defer fileLock.Unlock()
// Initialize installed packages map
err = bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Read local databases // Read local databases
err = bpmlib.ReadLocalDatabaseFiles() err = bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
@@ -786,6 +861,14 @@ func doCleanup() {
} }
defer fileLock.Unlock() defer fileLock.Unlock()
// Initialize installed packages map
err = bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
err = bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupBinaryPackages, cleanupFetchedPackages, verbose) err = bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupBinaryPackages, cleanupFetchedPackages, verbose)
if err != nil { if err != nil {
log.Printf("Error: could not complete cache cleanup: %s", err) log.Printf("Error: could not complete cache cleanup: %s", err)
@@ -907,6 +990,7 @@ func updatePackages() {
noSync, _ := currentFlagSet.GetBool("no-sync") noSync, _ := currentFlagSet.GetBool("no-sync")
allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades") allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades")
installOptional, _ := currentFlagSet.GetBool("optional") installOptional, _ := currentFlagSet.GetBool("optional")
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
compilationJobs, _ := currentFlagSet.GetInt("jobs") compilationJobs, _ := currentFlagSet.GetInt("jobs")
// Check for required permissions // Check for required permissions
@@ -925,6 +1009,14 @@ func updatePackages() {
} }
defer fileLock.Unlock() defer fileLock.Unlock()
// Initialize installed packages map
err = bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Read local databases if no sync // Read local databases if no sync
if noSync { if noSync {
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
@@ -945,7 +1037,7 @@ func updatePackages() {
} }
// Create update operation // Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, installOptional, force, verbose) operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, installOptional, force, !skipChecks, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
@@ -1046,6 +1138,14 @@ func getFileOwner() {
// Get flags // Get flags
rootDir, _ := currentFlagSet.GetString("root") rootDir, _ := currentFlagSet.GetString("root")
// Initialize installed packages map
err := bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Get files // Get files
files := currentFlagSet.Args() files := currentFlagSet.Args()
if len(files) == 0 { if len(files) == 0 {
@@ -1139,6 +1239,14 @@ func compilePackage() {
outputFd, _ := currentFlagSet.GetInt("output-fd") outputFd, _ := currentFlagSet.GetInt("output-fd")
compilationJobs, _ := currentFlagSet.GetInt("jobs") compilationJobs, _ := currentFlagSet.GetInt("jobs")
// Initialize installed packages map
err := bpmlib.InitializeLocalPackageInformation(rootDir)
if err != nil {
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Get files // Get files
sourcePackages := currentFlagSet.Args() sourcePackages := currentFlagSet.Args()
if len(sourcePackages) == 0 { if len(sourcePackages) == 0 {
@@ -1147,7 +1255,7 @@ func compilePackage() {
} }
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err = bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Printf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1 exitCode = 1
@@ -1179,7 +1287,7 @@ func compilePackage() {
// Get direct common and make dependencies // Get direct common and make dependencies
totalDepends := make([]string, 0) totalDepends := make([]string, 0)
for _, depend := range bpmpkg.PkgInfo.GetDependencies(true, false, false) { for _, depend := range bpmpkg.PkgInfo.GetDependencies(true, !skipChecks, false, false) {
if !slices.Contains(totalDepends, depend.PkgName) { if !slices.Contains(totalDepends, depend.PkgName) {
totalDepends = append(totalDepends, depend.PkgName) totalDepends = append(totalDepends, depend.PkgName)
} }
@@ -1196,7 +1304,7 @@ func compilePackage() {
for i := len(unmetDepends) - 1; i >= 0; i-- { for i := len(unmetDepends) - 1; i >= 0; i-- {
if slices.Contains(installedPackages, unmetDepends[i]) { if slices.Contains(installedPackages, unmetDepends[i]) {
unmetDepends = append(unmetDepends[:i], unmetDepends[i+1:]...) unmetDepends = append(unmetDepends[:i], unmetDepends[i+1:]...)
} else if ok, _ := bpmlib.IsVirtualPackage(unmetDepends[i], rootDir); ok { } else if providers := bpmlib.GetVirtualPackageInfo(unmetDepends[i], rootDir); len(providers) > 0 {
unmetDepends = append(unmetDepends[:i], unmetDepends[i+1:]...) unmetDepends = append(unmetDepends[:i], unmetDepends[i+1:]...)
} }
} }
@@ -1388,6 +1496,7 @@ func printUsage() {
fmt.Println(" o, owner Show what packages own the specified paths") fmt.Println(" o, owner Show what packages own the specified paths")
fmt.Println(" c, compile Compile source packages and convert them to binary ones") fmt.Println(" c, compile Compile source packages and convert them to binary ones")
fmt.Println(" p, vercmp Compare package version numbers") fmt.Println(" p, vercmp Compare package version numbers")
} }
func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) { func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) {
+37
View File
@@ -456,6 +456,24 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
} }
func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bool) error { func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bool) error {
// Get UID and GID to use for compilation
var uid, gid int
if os.Getuid() == 0 {
_uid, err := strconv.ParseInt(rootCompilationUID, 10, 32)
if err != nil {
return fmt.Errorf("could not convert UID '%s' to int", rootCompilationUID)
}
_gid, err := strconv.ParseInt(rootCompilationGID, 10, 32)
if err != nil {
return fmt.Errorf("could not convert GID '%s' to int", rootCompilationGID)
}
uid = int(_uid)
gid = int(_gid)
} else {
uid = os.Getuid()
gid = os.Getgid()
}
for _, download := range pkgInfo.Downloads { for _, download := range pkgInfo.Downloads {
// Replace variables // Replace variables
replaceVars := func(s string) string { replaceVars := func(s string) string {
@@ -534,12 +552,22 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
fmt.Println("Skipping checksum checking...") fmt.Println("Skipping checksum checking...")
} }
// Change downloaded file ownership
err = os.Chown(filepath, uid, gid)
if err != nil {
return err
}
if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) { if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) {
cmd := exec.Command("tar", "xf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents)) cmd := exec.Command("tar", "xf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
if verbose { if verbose {
cmd.Args[1] = "xvf" cmd.Args[1] = "xvf"
} }
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
if extractTo != "" { if extractTo != "" {
err := os.MkdirAll(extractTo, 0755) err := os.MkdirAll(extractTo, 0755)
if err != nil { if err != nil {
@@ -557,6 +585,11 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
} }
} else if !download.NoExtract && strings.HasSuffix(filepath, ".zip") { } else if !download.NoExtract && strings.HasSuffix(filepath, ".zip") {
cmd := exec.Command("unzip", filepath) cmd := exec.Command("unzip", filepath)
cmd.Dir = tempDirectory
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
if extractTo != "" { if extractTo != "" {
err := os.MkdirAll(extractTo, 0755) err := os.MkdirAll(extractTo, 0755)
if err != nil { if err != nil {
@@ -601,6 +634,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
cmd := exec.Command("git", "clone", "--depth=1", downloadUrl) cmd := exec.Command("git", "clone", "--depth=1", downloadUrl)
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
if gitBranch != "" { if gitBranch != "" {
cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch) cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch)
} }
+116 -55
View File
@@ -20,7 +20,7 @@ import (
type BPMDatabase struct { type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"` DatabaseVersion int `yaml:"database_version"`
Entries map[string]*BPMDatabaseEntry `yaml:"entries"` Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
VirtualPackages map[string][]string VirtualPackages map[string][]*BPMDatabaseEntry
Name string Name string
Source string Source string
} }
@@ -59,7 +59,7 @@ func (db *configDatabase) ReadLocalDatabase() error {
} }
// Initialize struct values // Initialize struct values
database.VirtualPackages = make(map[string][]string) database.VirtualPackages = make(map[string][]*BPMDatabaseEntry)
database.Name = db.Name database.Name = db.Name
database.Source = db.Source database.Source = db.Source
@@ -105,13 +105,13 @@ func (db *configDatabase) ReadLocalDatabase() error {
// Add virtual packages to database // Add virtual packages to database
for _, p := range splitPkg.Provides { for _, p := range splitPkg.Provides {
database.VirtualPackages[p] = append(database.VirtualPackages[p], splitPkg.Name) database.VirtualPackages[p] = append(database.VirtualPackages[p], database.Entries[splitPkg.Name])
} }
} }
} else { } else {
// Add virtual packages to database // Add virtual packages to database
for _, p := range entry.Info.Provides { for _, p := range entry.Info.Provides {
database.VirtualPackages[p] = append(database.VirtualPackages[p], entry.Info.Name) database.VirtualPackages[p] = append(database.VirtualPackages[p], entry)
} }
} }
} }
@@ -231,15 +231,12 @@ func FindReplacement(pkg string) *BPMDatabaseEntry {
return nil return nil
} }
func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry { func GetDatabaseVirtualPackageEntry(vpkg string) (providers []*BPMDatabaseEntry) {
for _, db := range BPMDatabases { for _, db := range BPMDatabases {
if v, ok := db.VirtualPackages[vpkg]; ok { providers = append(providers, db.VirtualPackages[vpkg]...)
slices.Sort(v)
return db.Entries[v[0]]
}
} }
return nil return providers
} }
func (db *BPMDatabase) FetchPackage(pkg string) (string, error) { func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
@@ -297,7 +294,9 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
dependantsMap := make(map[string][]string) dependantsMap := make(map[string][]string)
for _, db := range BPMDatabases { for _, db := range BPMDatabases {
for _, e := range db.Entries { for _, e := range db.Entries {
if slices.Contains(e.Info.OptionalDepends, entry.Info.Name) { if slices.ContainsFunc(e.Info.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name) dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
} }
} }
@@ -322,65 +321,124 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
return dependants return dependants
} }
func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableSize bool) string { func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool) string {
ret := make([]string, 0) builder := strings.Builder{}
appendArray := func(label string, array []string, sort bool) { builderWriteStringNotEmpty := func(label string, value string) {
if value != "" {
builder.WriteString(label + ": " + value + "\n")
}
}
builderWriteArray := func(label string, array []string, sort bool) {
if len(array) == 0 { if len(array) == 0 {
return return
} }
// Sort array
if sort { if sort {
// Sort array
slices.Sort(array) slices.Sort(array)
} }
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", "))) builder.WriteString(label + " (" + strconv.Itoa(len(array)) + "):\n")
for _, val := range array {
builder.WriteString(" - " + val + "\n")
}
}
builderWriteDependencyArray := func(label string, depends []string) {
if len(depends) == 0 {
return
}
// Sort array
slices.Sort(depends)
builder.WriteString(label + " (" + strconv.Itoa(len(depends)) + "):\n")
for _, val := range depends {
builder.WriteString(" - " + val)
// Show virtual package providers
if providers := GetDatabaseVirtualPackageEntry(val); len(providers) > 0 {
builder.WriteString(" (")
for i, vpkg := range providers {
if i == len(providers)-1 {
builder.WriteString(vpkg.Info.Name)
} else {
builder.WriteString(vpkg.Info.Name + ", ")
}
}
builder.WriteString(")")
}
builder.WriteString("\n")
}
} }
ret = append(ret, "Name: "+entry.Info.Name) // Main information
ret = append(ret, "Database: "+entry.Database.Name) builder.WriteString("Name: " + entry.Info.Name + "\n")
ret = append(ret, "Description: "+entry.Info.Description) builder.WriteString("Database: " + entry.Database.Name + "\n")
ret = append(ret, "Version: "+entry.Info.GetFullVersion()) builder.WriteString("Description: " + entry.Info.Description + "\n")
if entry.Info.Url != "" { builder.WriteString("Version: " + entry.Info.GetFullVersion() + "\n")
ret = append(ret, "URL: "+entry.Info.Url) builderWriteStringNotEmpty("URL", entry.Info.Url)
} builderWriteStringNotEmpty("License", entry.Info.License)
if entry.Info.License != "" { builderWriteArray("Maintainers", entry.Info.Maintainers, false)
ret = append(ret, "License: "+entry.Info.License) builder.WriteString("Architecture: " + entry.Info.Arch + "\n")
}
appendArray("Maintainers", entry.Info.Maintainers, false)
ret = append(ret, "Architecture: "+entry.Info.Arch)
if entry.Info.Type == "source" && entry.Info.OutputArch != "" && entry.Info.OutputArch != GetArch() { if entry.Info.Type == "source" && entry.Info.OutputArch != "" && entry.Info.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+entry.Info.OutputArch) builder.WriteString("Output architecture: " + entry.Info.OutputArch + "\n")
} }
ret = append(ret, "Type: "+entry.Info.Type) builder.WriteString("Type: " + entry.Info.Type + "\n")
appendArray("Dependencies", entry.Info.Depends, true)
if entry.Info.Type == "source" {
appendArray("Make Dependencies", entry.Info.MakeDepends, true)
}
appendArray("Runtime dependencies", entry.Info.RuntimeDepends, true)
appendArray("Optional dependencies", entry.Info.OptionalDepends, true)
dependants := entry.GetEntryDependants()
if len(dependants) > 0 {
appendArray("Dependant packages", dependants, false)
}
optionalDependants := entry.GetEntryOptionalDependants()
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants, false)
}
appendArray("Conflicting packages", entry.Info.Conflicts, true)
appendArray("Provided packages", entry.Info.Provides, true)
appendArray("Replaces packages", entry.Info.Replaces, true)
// Dependencies
builderWriteDependencyArray("Dependencies", entry.Info.Depends)
if entry.Info.Type == "source" {
builderWriteDependencyArray("Make dependencies", entry.Info.MakeDepends)
builderWriteDependencyArray("Check dependencies", entry.Info.CheckDepends)
}
builderWriteDependencyArray("Runtime dependencies", entry.Info.RuntimeDepends)
if len(entry.Info.OptionalDepends) > 0 {
builder.WriteString("Optional dependencies: (" + strconv.Itoa(len(entry.Info.OptionalDepends)) + ")\n")
for _, depend := range entry.Info.OptionalDepends {
dependSplit := strings.SplitN(depend, ":", 2)
if len(dependSplit) == 2 {
builder.WriteString(fmt.Sprintf(" - %s (%s)", dependSplit[0], dependSplit[1]))
} else {
builder.WriteString(" - " + dependSplit[0])
}
// Show virtual package providers
if providers := GetDatabaseVirtualPackageEntry(dependSplit[0]); len(providers) > 0 {
builder.WriteString(" (")
for i, vpkg := range providers {
if i == len(providers)-1 {
builder.WriteString(vpkg.Info.Name)
} else {
builder.WriteString(vpkg.Info.Name + ", ")
}
}
builder.WriteString(")")
}
builder.WriteString("\n")
}
}
builderWriteArray("Dependant packages", entry.GetEntryDependants(), true)
builderWriteArray("Optionally dependant packages", entry.GetEntryOptionalDependants(), true)
// Other package relations
builderWriteArray("Conflicting packages", entry.Info.Conflicts, true)
builderWriteArray("Provided packages", entry.Info.Provides, true)
builderWriteArray("Replaces packages", entry.Info.Replaces, true)
// Split packages
if entry.Info.Type == "source" && len(entry.Info.SplitPackages) != 0 { if entry.Info.Type == "source" && len(entry.Info.SplitPackages) != 0 {
splitPkgs := make([]string, len(entry.Info.SplitPackages)) splitPkgs := make([]string, len(entry.Info.SplitPackages))
for i, splitPkgInfo := range entry.Info.SplitPackages { for i, splitPkgInfo := range entry.Info.SplitPackages {
splitPkgs[i] = splitPkgInfo.Name splitPkgs[i] = splitPkgInfo.Name
} }
appendArray("Split Packages", splitPkgs, true) builderWriteArray("Split packages", splitPkgs, true)
} }
// Installation reason
if rootDir != "" && IsPackageInstalled(entry.Info.Name, rootDir) { if rootDir != "" && IsPackageInstalled(entry.Info.Name, rootDir) {
installationReason := GetInstallationReason(entry.Info.Name, rootDir) installationReason := GetPackage(entry.Info.Name, rootDir).LocalInfo.GetInstallationReason()
var installationReasonString string var installationReasonString string
switch installationReason { switch installationReason {
case InstallationReasonManual: case InstallationReasonManual:
@@ -392,17 +450,20 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableS
default: default:
installationReasonString = "Unknown" installationReasonString = "Unknown"
} }
ret = append(ret, "Installation Reason: "+installationReasonString) builder.WriteString("Installation reason: " + installationReasonString + "\n")
} }
// Installed size
if entry.Info.Type == "binary" { if entry.Info.Type == "binary" {
installedSize := entry.InstalledSize installedSize := entry.InstalledSize
var installedSizeStr string var installedSizeStr string
if humanReadableSize { if showBytes {
installedSizeStr = BytesToHumanReadable(installedSize)
} else {
installedSizeStr = strconv.FormatInt(installedSize, 10) installedSizeStr = strconv.FormatInt(installedSize, 10)
} else {
installedSizeStr = BytesToHumanReadable(installedSize)
} }
ret = append(ret, "Installed size: "+installedSizeStr) builder.WriteString("Installed size: " + installedSizeStr + "\n")
} }
return strings.Join(ret, "\n")
return strings.TrimSpace(builder.String())
} }
+32 -17
View File
@@ -3,6 +3,7 @@ package bpmlib
import ( import (
"fmt" "fmt"
"slices" "slices"
"strings"
) )
type pkgInstallationReason struct { type pkgInstallationReason struct {
@@ -10,7 +11,7 @@ type pkgInstallationReason struct {
InstallationReason InstallationReason InstallationReason InstallationReason
} }
func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeRuntimeDepends, includeOptionalDepends bool) []pkgInstallationReason { func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeCheckDepends, includeRuntimeDepends, includeOptionalDepends bool) []pkgInstallationReason {
allDepends := make([]pkgInstallationReason, 0) allDepends := make([]pkgInstallationReason, 0)
for _, depend := range pkgInfo.Depends { for _, depend := range pkgInfo.Depends {
@@ -25,6 +26,7 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeRuntimeDe
} }
if includeOptionalDepends { if includeOptionalDepends {
for _, depend := range pkgInfo.OptionalDepends { for _, depend := range pkgInfo.OptionalDepends {
depend = strings.SplitN(depend, ":", 2)[0]
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool { if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend return p.PkgName == depend
}) { }) {
@@ -59,6 +61,18 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeRuntimeDe
} }
} }
} }
if includeCheckDepends {
for _, depend := range pkgInfo.CheckDepends {
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend
}) {
allDepends = append(allDepends, pkgInstallationReason{
PkgName: depend,
InstallationReason: InstallationReasonMakeDependency,
})
}
}
}
// Skip ignored packages // Skip ignored packages
allDepends = slices.DeleteFunc(allDepends, func(depend pkgInstallationReason) bool { allDepends = slices.DeleteFunc(allDepends, func(depend pkgInstallationReason) bool {
@@ -68,27 +82,27 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeRuntimeDe
return allDepends return allDepends
} }
func (pkgInfo *PackageInfo) GetDependenciesRecursive(includeRuntimeDepends, includeMakeDepends bool, rootDir string) (resolved []string) { func (pkgInfo *PackageInfo) GetDependenciesRecursive(includeRuntimeDepends, includeCheckDepends, includeMakeDepends bool, rootDir string) (resolved []string) {
// Initialize slices // Initialize slices
resolved = make([]string, 0) resolved = make([]string, 0)
unresolved := make([]string, 0) unresolved := make([]string, 0)
// Call unexported function // Call unexported function
pkgInfo.getDependenciesRecursive(&resolved, &unresolved, includeRuntimeDepends, includeMakeDepends, rootDir) pkgInfo.getDependenciesRecursive(&resolved, &unresolved, includeRuntimeDepends, includeMakeDepends, includeCheckDepends, rootDir)
return resolved return resolved
} }
func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresolved *[]string, includeRuntimeDepends, includeMakeDepends bool, rootDir string) { func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresolved *[]string, includeRuntimeDepends, includeMakeDepends, includeCheckDepends bool, rootDir string) {
// Add current package name to unresolved slice // Add current package name to unresolved slice
*unresolved = append(*unresolved, pkgInfo.Name) *unresolved = append(*unresolved, pkgInfo.Name)
// Loop through all dependencies // Loop through all dependencies
for _, pkgIR := range pkgInfo.GetDependencies(includeMakeDepends, includeRuntimeDepends, false) { for _, pkgIR := range pkgInfo.GetDependencies(includeMakeDepends, includeCheckDepends, includeRuntimeDepends, false) {
depend := pkgIR.PkgName depend := pkgIR.PkgName
if isVirtual, p := IsVirtualPackage(depend, rootDir); isVirtual { if providers := GetVirtualPackageInfo(depend, rootDir); len(providers) > 0 {
depend = p depend = providers[0].Name
} }
if !slices.Contains(*resolved, depend) { if !slices.Contains(*resolved, depend) {
@@ -103,7 +117,7 @@ func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresol
dependInfo := GetPackageInfo(depend, rootDir) dependInfo := GetPackageInfo(depend, rootDir)
if dependInfo != nil { if dependInfo != nil {
dependInfo.getDependenciesRecursive(resolved, unresolved, includeRuntimeDepends, includeMakeDepends, rootDir) dependInfo.getDependenciesRecursive(resolved, unresolved, includeRuntimeDepends, includeMakeDepends, includeCheckDepends, rootDir)
} }
} }
} }
@@ -113,7 +127,7 @@ func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresol
*unresolved = stringSliceRemove(*unresolved, pkgInfo.Name) *unresolved = stringSliceRemove(*unresolved, pkgInfo.Name)
} }
func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, resolvedVirtualPkgs map[string]string, checkMake, checkRuntime, checkOptional, ignoreInstalled, verbose bool, rootDir string) (resolved []pkgInstallationReason, unresolved []string) { func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, resolvedVirtualPkgs map[string]string, checkMake, checkCheck, checkRuntime, checkOptional, ignoreInstalled, verbose bool, rootDir string) (resolved []pkgInstallationReason, unresolved []string) {
// Initialize slices and maps // Initialize slices and maps
resolved = make([]pkgInstallationReason, 0) resolved = make([]pkgInstallationReason, 0)
unresolved = make([]string, 0) unresolved = make([]string, 0)
@@ -122,7 +136,7 @@ func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, resolvedVi
} }
// Call unexported function // Call unexported function
resolvePackageDependenciesFromDatabase(&resolved, &unresolved, resolvedVirtualPkgs, pkgInfo, checkMake, checkRuntime, checkOptional, ignoreInstalled, verbose, rootDir) resolvePackageDependenciesFromDatabase(&resolved, &unresolved, resolvedVirtualPkgs, pkgInfo, checkMake, checkCheck, checkRuntime, checkOptional, ignoreInstalled, verbose, rootDir)
// Remove main package from unresolved slice // Remove main package from unresolved slice
unresolved = stringSliceRemove(unresolved, pkgInfo.Name) unresolved = stringSliceRemove(unresolved, pkgInfo.Name)
@@ -130,7 +144,7 @@ func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, resolvedVi
return resolved, unresolved return resolved, unresolved
} }
func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, unresolved *[]string, resolvedVirtualPkgs map[string]string, pkgInfo *PackageInfo, checkMake, checkRuntime, checkOptional, ignoreInstalled, verbose bool, rootDir string) { func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, unresolved *[]string, resolvedVirtualPkgs map[string]string, pkgInfo *PackageInfo, checkMake, checkCheck, checkRuntime, checkOptional, ignoreInstalled, verbose bool, rootDir string) {
// Add current package name to unresolved slice // Add current package name to unresolved slice
*unresolved = append(*unresolved, pkgInfo.Name) *unresolved = append(*unresolved, pkgInfo.Name)
@@ -141,7 +155,7 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
} }
// Loop through all dependencies // Loop through all dependencies
for _, pkgIR := range pkgInfo.GetDependencies(pkgInfo.Type == "source", checkRuntime, checkOptional) { for _, pkgIR := range pkgInfo.GetDependencies(pkgInfo.Type == "source", pkgInfo.Type == "source" && checkCheck, checkRuntime, checkOptional) {
// Skip dependency if it has already been resolved // Skip dependency if it has already been resolved
if slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool { if slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
return p.PkgName == pkgIR.PkgName return p.PkgName == pkgIR.PkgName
@@ -163,7 +177,7 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
} }
// Skip dependency if it is already installed or provided // Skip dependency if it is already installed or provided
if ignoreInstalled && IsPackageProvided(pkgIR.PkgName, rootDir) { if providers := GetVirtualPackageInfo(pkgIR.PkgName, rootDir); ignoreInstalled && len(providers) > 0 {
continue continue
} }
@@ -187,8 +201,9 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
*unresolved = stringSliceRemove(*unresolved, resolvedVirtualPkg) *unresolved = stringSliceRemove(*unresolved, resolvedVirtualPkg)
continue continue
} else if entry = ResolveVirtualPackage(pkgIR.PkgName); entry != nil { } else if providers := GetDatabaseVirtualPackageEntry(pkgIR.PkgName); len(providers) > 0 {
// Virtual package found in database // Virtual package found in database
entry = providers[0]
} else { } else {
// Virtual package not found // Virtual package not found
if !slices.Contains(*unresolved, pkgIR.PkgName) { if !slices.Contains(*unresolved, pkgIR.PkgName) {
@@ -199,7 +214,7 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
} }
// Resolve the dependencies of this dependency // Resolve the dependencies of this dependency
resolvePackageDependenciesFromDatabase(resolved, unresolved, resolvedVirtualPkgs, entry.Info, checkMake, checkRuntime, false, ignoreInstalled, verbose, rootDir) resolvePackageDependenciesFromDatabase(resolved, unresolved, resolvedVirtualPkgs, entry.Info, checkMake, checkCheck, checkRuntime, false, ignoreInstalled, verbose, rootDir)
// Move dependency from the unresolved slice to the resolved slice // Move dependency from the unresolved slice to the resolved slice
if !slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool { if !slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
@@ -283,7 +298,7 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
// Add installed package to list if its optional dependencies include pkgName // Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool { if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return n == pkgInfo.Name return strings.SplitN(n, ":", 2)[0] == pkgInfo.Name
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
continue continue
@@ -293,7 +308,7 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
for _, vpkg := range pkgInfo.Provides { for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its optional dependencies contain a provided virtual package // Add installed package to list if its optional dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool { if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return n == vpkg return strings.SplitN(n, ":", 2)[0] == vpkg
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
break break
+14 -12
View File
@@ -20,12 +20,13 @@ const (
) )
// InstallPackages installs the specified packages into the given root directory by fetching them from databases or directly from local bpm archives // InstallPackages installs the specified packages into the given root directory by fetching them from databases or directly from local bpm archives
func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallMethod ReinstallMethod, installRuntimeDependencies, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) { func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallMethod ReinstallMethod, installRuntimeDependencies, installOptionalDependencies, forceInstallation, runChecks bool, verbose bool, packages ...string) (operation *BPMOperation, err error) {
// Setup operation struct // Setup operation struct
operation = &BPMOperation{ operation = &BPMOperation{
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string), Changes: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
@@ -52,7 +53,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
installationReason := forceInstallationReason installationReason := forceInstallationReason
if installationReason == InstallationReasonUnknown { if installationReason == InstallationReasonUnknown {
if IsPackageInstalled(splitPkg.Name, rootDir) { if IsPackageInstalled(splitPkg.Name, rootDir) {
installationReason = GetInstallationReason(splitPkg.Name, rootDir) installationReason = GetPackage(splitPkg.Name, rootDir).LocalInfo.GetInstallationReason()
} else { } else {
installationReason = InstallationReasonManual installationReason = InstallationReasonManual
} }
@@ -76,7 +77,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
installationReason := forceInstallationReason installationReason := forceInstallationReason
if installationReason == InstallationReasonUnknown { if installationReason == InstallationReasonUnknown {
if IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) { if IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
installationReason = GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir) installationReason = GetPackage(bpmpkg.PkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
} else { } else {
installationReason = InstallationReasonManual installationReason = InstallationReasonManual
} }
@@ -92,14 +93,14 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
if e, _, err := GetDatabaseEntry(pkg); err == nil { if e, _, err := GetDatabaseEntry(pkg); err == nil {
entry = e entry = e
} else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual { } else if providers := GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 {
entry, _, err = GetDatabaseEntry(p) entry, _, err = GetDatabaseEntry(providers[0].Name)
if err != nil { if err != nil {
pkgsNotFound = append(pkgsNotFound, pkg) pkgsNotFound = append(pkgsNotFound, pkg)
continue continue
} }
} else if e := ResolveVirtualPackage(pkg); e != nil { } else if providers := GetDatabaseVirtualPackageEntry(pkg); len(providers) > 0 {
entry = e entry = providers[0]
} else { } else {
pkgsNotFound = append(pkgsNotFound, pkg) pkgsNotFound = append(pkgsNotFound, pkg)
continue continue
@@ -112,7 +113,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
installationReason := forceInstallationReason installationReason := forceInstallationReason
if installationReason == InstallationReasonUnknown { if installationReason == InstallationReasonUnknown {
if IsPackageInstalled(entry.Info.Name, rootDir) { if IsPackageInstalled(entry.Info.Name, rootDir) {
installationReason = GetInstallationReason(entry.Info.Name, rootDir) installationReason = GetPackage(entry.Info.Name, rootDir).LocalInfo.GetInstallationReason()
} else { } else {
installationReason = InstallationReasonManual installationReason = InstallationReasonManual
} }
@@ -201,8 +202,8 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Search for packages // Search for packages
for _, pkg := range packages { for _, pkg := range packages {
bpmpkg := GetPackage(pkg, rootDir) bpmpkg := GetPackage(pkg, rootDir)
if isVirutal, vpkg := IsVirtualPackage(pkg, rootDir); isVirutal { if providers := GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 {
bpmpkg = GetPackage(vpkg, rootDir) bpmpkg = GetPackage(providers[0].Name, rootDir)
} }
if bpmpkg == nil { if bpmpkg == nil {
continue continue
@@ -365,7 +366,7 @@ func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackag
} }
// UpdatePackages fetches the newest versions of all installed packages from // UpdatePackages fetches the newest versions of all installed packages from
func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) { func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, installOptionalDependencies, forceInstallation, runChecks, verbose bool) (operation *BPMOperation, err error) {
// Sync databases // Sync databases
if syncDatabase { if syncDatabase {
err := SyncDatabase(verbose) err := SyncDatabase(verbose)
@@ -397,6 +398,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string), Changes: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
@@ -421,7 +423,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion()) comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) { if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
operation.AppendAction(&FetchPackageAction{ operation.AppendAction(&FetchPackageAction{
InstallationReason: GetInstallationReason(pkg, rootDir), InstallationReason: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(),
DatabaseEntry: entry, DatabaseEntry: entry,
}) })
} }
+167 -44
View File
@@ -7,20 +7,42 @@ import (
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
"gopkg.in/yaml.v3"
) )
var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo) var persistentDataVersion int = 1
func initializeLocalPackageInformation(rootDir string) (err error) { var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo)
var installedVirtualPackages map[string]map[string][]*PackageInfo = make(map[string]map[string][]*PackageInfo)
func InitializeLocalPackageInformation(rootDir string) (err error) {
// Return if information is already initialized // Return if information is already initialized
if _, ok := localPackageInformation[rootDir]; ok { if _, ok := localPackageInformation[rootDir]; ok {
return nil return nil
} }
tempPackageInformation := make(map[string]*PackageInfo) tempPackageInformation := make(map[string]*PackageInfo)
tempInstalledVirtualPackages := make(map[string][]*PackageInfo)
// Get path to installed package information directory // Get paths
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") persistentDataDir := path.Join(rootDir, "var/lib/bpm")
installedDir := path.Join(persistentDataDir, "installed")
// Ensure persistent data directory is up-to-date
if _, err := os.Stat(persistentDataDir); err == nil {
data, err := os.ReadFile(path.Join(persistentDataDir, ".version"))
if err != nil {
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
}
currentPersistentDataVersion, err := strconv.Atoi(strings.TrimSpace(string(data)))
if err != nil {
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
}
if currentPersistentDataVersion != persistentDataVersion {
return fmt.Errorf("persistent data is not up-to-date! Please run 'bpm upgrade-persistent-data' first")
}
}
// Get directory content // Get directory content
items, err := os.ReadDir(installedDir) items, err := os.ReadDir(installedDir)
@@ -51,15 +73,21 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
// Add package to slice // Add package to slice
tempPackageInformation[info.Name] = info tempPackageInformation[info.Name] = info
// Add virtual packages
for _, vpkg := range info.Provides {
tempInstalledVirtualPackages[vpkg] = append(tempInstalledVirtualPackages[vpkg], info)
}
} }
localPackageInformation[rootDir] = tempPackageInformation localPackageInformation[rootDir] = tempPackageInformation
installedVirtualPackages[rootDir] = tempInstalledVirtualPackages
return nil return nil
} }
func GetInstalledPackages(rootDir string) (ret []string, err error) { func GetInstalledPackages(rootDir string) (ret []string, err error) {
// Initialize local package information // Initialize local package information
err = initializeLocalPackageInformation(rootDir) err = InitializeLocalPackageInformation(rootDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -77,7 +105,7 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
func IsPackageInstalled(pkg, rootDir string) bool { func IsPackageInstalled(pkg, rootDir string) bool {
// Initialize local package information // Initialize local package information
err := initializeLocalPackageInformation(rootDir) err := InitializeLocalPackageInformation(rootDir)
if err != nil { if err != nil {
return false return false
} }
@@ -88,48 +116,17 @@ func IsPackageInstalled(pkg, rootDir string) bool {
return true return true
} }
func IsVirtualPackage(pkg, rootDir string) (bool, string) { func GetVirtualPackageInfo(vpkg, rootDir string) []*PackageInfo {
pkgs, err := GetInstalledPackages(rootDir) err := InitializeLocalPackageInformation(rootDir)
if err != nil { if err != nil {
return false, "" return nil
} }
for _, p := range pkgs {
if p == pkg {
return false, ""
}
i := GetPackageInfo(p, rootDir)
if i == nil {
continue
}
if slices.Contains(i.Provides, pkg) {
return true, p
}
}
return false, ""
}
func IsPackageProvided(pkg, rootDir string) bool { return installedVirtualPackages[rootDir][vpkg]
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return false
}
for _, p := range pkgs {
if p == pkg {
return true
}
i := GetPackageInfo(p, rootDir)
if i == nil {
continue
}
if slices.Contains(i.Provides, pkg) {
return true
}
}
return false
} }
func GetPackageInfo(pkg string, rootDir string) *PackageInfo { func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
err := initializeLocalPackageInformation(rootDir) err := InitializeLocalPackageInformation(rootDir)
if err != nil { if err != nil {
return nil return nil
} }
@@ -144,10 +141,12 @@ func GetPackage(pkg, rootDir string) *BPMPackage {
} }
files := getPackageFiles(pkgInfo.Name, rootDir) files := getPackageFiles(pkgInfo.Name, rootDir)
localInfo := getPackageLocalInfo(pkgInfo.Name, rootDir)
return &BPMPackage{ return &BPMPackage{
PkgInfo: pkgInfo, PkgInfo: pkgInfo,
PkgFiles: files, PkgFiles: files,
LocalInfo: localInfo,
} }
} }
@@ -234,3 +233,127 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
return pkgFiles return pkgFiles
} }
func getPackageLocalInfo(pkg, rootDir string) PackageLocalInfo {
localInfo := PackageLocalInfo{}
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
localInfoFile := path.Join(path.Join(pkgDir, "local"))
if _, err := os.Stat(localInfoFile); os.IsNotExist(err) {
return localInfo
}
file, err := os.Open(localInfoFile)
if err != nil {
return localInfo
}
defer file.Close()
err = yaml.NewDecoder(file).Decode(&localInfo)
if err != nil {
return localInfo
}
return localInfo
}
func SetPackageLocalInfo(pkg string, localInfo PackageLocalInfo, rootDir string) error {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
localFile, err := os.OpenFile(path.Join(pkgDir, "local"), os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer localFile.Close()
err = yaml.NewEncoder(localFile).Encode(localInfo)
if err != nil {
return err
}
return nil
}
func UpgradePersistentData(rootDir string) error {
persistentDataDir := path.Join(rootDir, "var/lib/bpm")
// Create persistent data directory
os.MkdirAll(persistentDataDir, 0755)
// Upgrade installed package directories
dirEntries, err := os.ReadDir(path.Join(persistentDataDir, "installed"))
if err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
for _, entry := range dirEntries {
pkgDir := path.Join(persistentDataDir, "installed", entry.Name())
// Generate default local package information file
if _, err := os.Stat(path.Join(pkgDir, "local")); err != nil && !os.IsNotExist(err) {
return err
} else if os.IsNotExist(err) {
fmt.Printf("Generating local package information for package (%s)\n", entry.Name())
out, err := yaml.Marshal(PackageLocalInfo{
InstallationReason: "unknown",
InstalledOn: 0,
LastUpdatedOn: 0,
})
if err != nil {
return err
}
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644)
if err != nil {
return err
}
}
// Move installation reason to local package information file
if installationReason, err := os.ReadFile(path.Join(pkgDir, "installation_reason")); err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
fmt.Printf("Moving installation reason to local package information for package (%s)\n", entry.Name())
data, err := os.ReadFile(path.Join(pkgDir, "local"))
if err != nil {
return err
}
localInfo := &PackageLocalInfo{}
err = yaml.Unmarshal(data, localInfo)
if err != nil {
return err
}
localInfo.InstallationReason = strings.TrimSpace(string(installationReason))
out, err := yaml.Marshal(localInfo)
if err != nil {
return err
}
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644)
if err != nil {
return err
}
err = os.Remove(path.Join(pkgDir, "installation_reason"))
if err != nil {
return err
}
}
}
}
// Set persistent data version number
err = os.WriteFile(path.Join(persistentDataDir, ".version"), []byte(strconv.Itoa(persistentDataVersion)), 0644)
if err != nil {
return err
}
return nil
}
+67 -31
View File
@@ -16,6 +16,7 @@ type BPMOperation struct {
UnresolvedDepends []string UnresolvedDepends []string
Changes map[string]string Changes map[string]string
CompilationJobs int CompilationJobs int
RunChecks bool
RootDir string RootDir string
compiledPackages map[string]string compiledPackages map[string]string
@@ -146,7 +147,7 @@ func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, instal
continue continue
} }
resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, resolvedVirtualPkgs, pkgInfo.Type == "source", installRuntimeDependencies, installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir) resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, resolvedVirtualPkgs, pkgInfo.Type == "source", pkgInfo.Type == "source" && operation.RunChecks, installRuntimeDependencies, installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...) operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
@@ -204,11 +205,11 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
} }
installedPackages := make([]*PackageInfo, len(installedPackageNames)) installedPackages := make([]*PackageInfo, len(installedPackageNames))
for i, value := range installedPackageNames { for i, value := range installedPackageNames {
bpmpkg := GetPackage(value, operation.RootDir) pkgInfo := GetPackageInfo(value, operation.RootDir)
if bpmpkg == nil { if pkgInfo == nil {
return errors.New("could not find installed package (" + value + ")") return errors.New("could not find installed package (" + value + ")")
} }
installedPackages[i] = bpmpkg.PkgInfo installedPackages[i] = pkgInfo
} }
// Get packages to remove // Get packages to remove
@@ -219,34 +220,67 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
} }
} }
// Get manually installed packages, resolve all their dependencies and add them to the keepPackages slice // Run BFS on all manually installed packages
keepPackages := make([]string, 0) visited := make([]string, 0)
for _, pkg := range slices.Clone(installedPackages) { for _, pkg := range installedPackages {
if GetInstallationReason(pkg.Name, operation.RootDir) != InstallationReasonManual { if getPackageLocalInfo(pkg.Name, operation.RootDir).GetInstallationReason() != InstallationReasonManual {
continue continue
} }
// Do not resolve dependencies or add package to keepPackages slice if package removal action exists for it queue := make([]*PackageInfo, 0)
if _, ok := removeActions[pkg.Name]; ok {
continue
}
keepPackages = append(keepPackages, pkg.Name) queue = append(queue, pkg)
resolved := pkg.GetDependenciesRecursive(true, !cleanupMakeDepends, operation.RootDir)
for _, value := range resolved { for len(queue) > 0 {
if !slices.Contains(keepPackages, value) && !slices.Contains(MainBPMConfig.IgnorePackages, value) { v := queue[len(queue)-1]
keepPackages = append(keepPackages, value) queue = queue[:len(queue)-1]
// Skip package if it's to be removed
if _, ok := removeActions[v.Name]; ok {
continue
}
// Mark package as visited
visited = append(visited, v.Name)
// Get all package dependencies
depends := v.Depends
depends = append(depends, v.RuntimeDepends...)
if cleanupMakeDepends {
depends = append(depends, v.MakeDepends...)
depends = append(depends, v.CheckDepends...)
}
// Loop through all dependencies
for _, depend := range depends {
// Resolve dependency
var dependPkgInfo *PackageInfo
if providers := GetVirtualPackageInfo(depend, operation.RootDir); len(providers) > 0 {
dependPkgInfo = providers[0]
} else {
dependPkgInfo = GetPackageInfo(depend, operation.RootDir)
}
if dependPkgInfo == nil {
continue
}
// Mark dependency as visited and add it to the queue
if !slices.Contains(visited, dependPkgInfo.Name) {
visited = append(visited, dependPkgInfo.Name)
queue = append(queue, dependPkgInfo)
}
} }
} }
} }
// Get all installed packages that are not in the keepPackages slice and add them to the BPM operation // Remove all packages that were not discovered after running BFS
for _, pkg := range installedPackageNames { for _, pkg := range installedPackageNames {
// Do not add package removal action if there already is one // Do not add package removal action if there already is one
if _, ok := removeActions[pkg]; ok { if _, ok := removeActions[pkg]; ok {
continue continue
} }
if !slices.Contains(keepPackages, pkg) {
if !slices.Contains(visited, pkg) {
bpmpkg := GetPackage(pkg, operation.RootDir) bpmpkg := GetPackage(pkg, operation.RootDir)
if bpmpkg == nil { if bpmpkg == nil {
return errors.New("Error: could not find installed package (" + pkg + ")") return errors.New("Error: could not find installed package (" + pkg + ")")
@@ -485,17 +519,25 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
} }
for _, depend := range pkgInfo.OptionalDepends { for _, depend := range pkgInfo.OptionalDepends {
dependSplit := strings.SplitN(depend, ":", 2)
// Skip if dependency is already installed // Skip if dependency is already installed
if IsPackageInstalled(depend, operation.RootDir) { if IsPackageInstalled(dependSplit[0], operation.RootDir) {
continue continue
} }
// Skip if not a new dependency of the package // Skip if not a new dependency of the package
if installedPkg := GetPackage(pkgInfo.Name, operation.RootDir); installedPkg != nil && slices.Contains(installedPkg.PkgInfo.OptionalDepends, depend) { if installedPkg := GetPackage(pkgInfo.Name, operation.RootDir); installedPkg != nil && slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == dependSplit[0]
}) {
continue continue
} }
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], depend) if len(dependSplit) == 2 {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", dependSplit[0], dependSplit[1]))
} else {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependSplit[0])
}
} }
} }
@@ -672,7 +714,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Compile source package if not compiled already // Compile source package if not compiled already
if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok { if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok {
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, operation.CompilationJobs, false, false, verbose) outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, operation.CompilationJobs, !operation.RunChecks, false, verbose)
if err != nil { if err != nil {
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err) return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
} }
@@ -692,19 +734,13 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
} }
if value.InstallationReason != InstallationReasonManual { if value.InstallationReason != InstallationReasonManual {
err = installPackage(fileToInstall, operation.RootDir, verbose, true) err = installPackage(fileToInstall, value.InstallationReason, operation.RootDir, verbose, true)
} else { } else {
err = installPackage(fileToInstall, operation.RootDir, verbose, force) err = installPackage(fileToInstall, value.InstallationReason, operation.RootDir, verbose, force)
} }
if err != nil { if err != nil {
return fmt.Errorf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err) return fmt.Errorf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err)
} }
// Set installed package's installation reason
err = SetInstallationReason(bpmpkg.PkgInfo.Name, value.InstallationReason, operation.RootDir)
if err != nil {
return fmt.Errorf("could not set installation reason for package (%s): %s\n", value.BpmPackage.PkgInfo.Name, err)
}
} }
} }
fmt.Println("Operation complete!") fmt.Println("Operation complete!")
+141 -69
View File
@@ -16,13 +16,15 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type BPMPackage struct { type BPMPackage struct {
PkgInfo *PackageInfo PkgInfo *PackageInfo
PkgFiles []*PackageFileEntry PkgFiles []*PackageFileEntry
LocalInfo PackageLocalInfo
} }
type PackageInfo struct { type PackageInfo struct {
@@ -41,6 +43,7 @@ type PackageInfo struct {
RuntimeDepends []string `yaml:"runtime_depends,omitempty"` RuntimeDepends []string `yaml:"runtime_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"` OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"` MakeDepends []string `yaml:"make_depends,omitempty"`
CheckDepends []string `yaml:"check_depends,omitempty"`
Conflicts []string `yaml:"conflicts,omitempty"` Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"` Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"` Provides []string `yaml:"provides,omitempty"`
@@ -74,6 +77,12 @@ type PackageFileEntry struct {
SizeInBytes int64 SizeInBytes int64
} }
type PackageLocalInfo struct {
InstallationReason string `yaml:"installation_reason"`
InstalledOn int64 `yaml:"installed_on"`
LastUpdatedOn int64 `yaml:"last_updated_on"`
}
func (pkg *BPMPackage) GetInstalledSize() int64 { func (pkg *BPMPackage) GetInstalledSize() int64 {
var totalSize int64 = 0 var totalSize int64 = 0
for _, entry := range pkg.PkgFiles { for _, entry := range pkg.PkgFiles {
@@ -122,35 +131,17 @@ const (
InstallationReasonUnknown InstallationReason = "unknown" InstallationReasonUnknown InstallationReason = "unknown"
) )
func GetInstallationReason(pkg, rootDir string) InstallationReason { func (localInfo PackageLocalInfo) GetInstallationReason() InstallationReason {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") switch localInfo.InstallationReason {
pkgDir := path.Join(installedDir, pkg) case "manual":
if stat, err := os.Stat(path.Join(pkgDir, "installation_reason")); err != nil || stat.IsDir() {
return InstallationReasonManual return InstallationReasonManual
} case "dependency":
b, err := os.ReadFile(path.Join(pkgDir, "installation_reason")) return InstallationReasonDependency
if err != nil { case "make_dependency":
return InstallationReasonMakeDependency
default:
return InstallationReasonUnknown return InstallationReasonUnknown
} }
reason := strings.TrimSpace(string(b))
if reason == "manual" {
return InstallationReasonManual
} else if reason == "dependency" {
return InstallationReasonDependency
} else if reason == "make_dependency" {
return InstallationReasonMakeDependency
}
return InstallationReasonUnknown
}
func SetInstallationReason(pkg string, reason InstallationReason, rootDir string) error {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
err := os.WriteFile(path.Join(pkgDir, "installation_reason"), []byte(reason), 0644)
if err != nil {
return err
}
return nil
} }
func GetPackageInfoRaw(filename string) (string, error) { func GetPackageInfoRaw(filename string) (string, error) {
@@ -540,59 +531,122 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
} }
func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string { func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
ret := make([]string, 0) builder := strings.Builder{}
appendArray := func(label string, array []string) { builderWriteStringNotEmpty := func(label string, value string) {
if value != "" {
builder.WriteString(label + ": " + value + "\n")
}
}
builderWriteArray := func(label string, array []string, sort bool) {
if len(array) == 0 { if len(array) == 0 {
return return
} }
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", "))) // Sort array
if sort {
slices.Sort(array)
}
builder.WriteString(label + " (" + strconv.Itoa(len(array)) + "):\n")
for _, val := range array {
builder.WriteString(" - " + val + "\n")
}
}
builderWriteDependencyArray := func(label string, depends []string) {
if len(depends) == 0 {
return
}
// Sort array
slices.Sort(depends)
builder.WriteString(label + " (" + strconv.Itoa(len(depends)) + "):\n")
for _, val := range depends {
builder.WriteString(" - " + val)
// Show virtual package providers
if providers := GetVirtualPackageInfo(val, rootDir); len(providers) > 0 {
builder.WriteString(" (")
for i, vpkg := range providers {
if i == len(providers)-1 {
builder.WriteString(vpkg.Name)
} else {
builder.WriteString(vpkg.Name + ", ")
}
}
builder.WriteString(")")
}
builder.WriteString("\n")
}
} }
ret = append(ret, "Name: "+pkgInfo.Name) // Main information
ret = append(ret, "Description: "+pkgInfo.Description) builder.WriteString("Name: " + pkgInfo.Name + "\n")
ret = append(ret, "Version: "+pkgInfo.GetFullVersion()) builder.WriteString("Description: " + pkgInfo.Description + "\n")
if pkgInfo.Url != "" { builder.WriteString("Version: " + pkgInfo.GetFullVersion() + "\n")
ret = append(ret, "URL: "+pkgInfo.Url) builderWriteStringNotEmpty("URL", pkgInfo.Url)
} builderWriteStringNotEmpty("License", pkgInfo.License)
if pkgInfo.License != "" { builderWriteArray("Maintainers", pkgInfo.Maintainers, false)
ret = append(ret, "License: "+pkgInfo.License) builder.WriteString("Architecture: " + pkgInfo.Arch + "\n")
}
appendArray("Maintainers", pkgInfo.Maintainers)
ret = append(ret, "Architecture: "+pkgInfo.Arch)
if pkgInfo.Type == "source" && pkgInfo.OutputArch != "" && pkgInfo.OutputArch != GetArch() { if pkgInfo.Type == "source" && pkgInfo.OutputArch != "" && pkgInfo.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+pkgInfo.Arch) builder.WriteString("Output architecture: " + pkgInfo.OutputArch + "\n")
} }
ret = append(ret, "Type: "+pkgInfo.Type) builder.WriteString("Type: " + pkgInfo.Type + "\n")
appendArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
appendArray("Runtime Dependencies", pkgInfo.RuntimeDepends)
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Runtime dependencies", pkgInfo.RuntimeDepends)
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants := pkgInfo.GetPackageDependants(rootDir)
if len(dependants) > 0 {
appendArray("Dependant packages", dependants)
}
optionalDependants := pkgInfo.GetPackageOptionalDependants(rootDir)
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants)
}
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
// Dependencies
builderWriteDependencyArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
builderWriteDependencyArray("Make dependencies", pkgInfo.MakeDepends)
builderWriteDependencyArray("Check dependencies", pkgInfo.CheckDepends)
}
builderWriteDependencyArray("Runtime dependencies", pkgInfo.RuntimeDepends)
if len(pkgInfo.OptionalDepends) > 0 {
builder.WriteString("Optional dependencies (" + strconv.Itoa(len(pkgInfo.OptionalDepends)) + "):\n")
for _, depend := range pkgInfo.OptionalDepends {
dependSplit := strings.SplitN(depend, ":", 2)
if len(dependSplit) == 2 {
builder.WriteString(fmt.Sprintf(" - %s (%s)", dependSplit[0], dependSplit[1]))
} else {
builder.WriteString(" - " + dependSplit[0])
}
// Show virtual package providers
if providers := GetVirtualPackageInfo(dependSplit[0], rootDir); len(providers) > 0 {
builder.WriteString(" (")
for i, vpkg := range providers {
if i == len(providers)-1 {
builder.WriteString(vpkg.Name)
} else {
builder.WriteString(vpkg.Name + ", ")
}
}
builder.WriteString(")")
}
builder.WriteString("\n")
}
}
builderWriteArray("Dependant packages", pkgInfo.GetPackageDependants(rootDir), true)
builderWriteArray("Optionally dependant packages", pkgInfo.GetPackageOptionalDependants(rootDir), true)
// Other package relations
builderWriteArray("Conflicting packages", pkgInfo.Conflicts, true)
builderWriteArray("Provided packages", pkgInfo.Provides, true)
builderWriteArray("Replaces packages", pkgInfo.Replaces, true)
// Split packages
if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 { if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 {
splitPkgs := make([]string, len(pkgInfo.SplitPackages)) splitPkgs := make([]string, len(pkgInfo.SplitPackages))
for i, splitPkgInfo := range pkgInfo.SplitPackages { for i, splitPkgInfo := range pkgInfo.SplitPackages {
splitPkgs[i] = splitPkgInfo.Name splitPkgs[i] = splitPkgInfo.Name
} }
appendArray("Split Packages", splitPkgs) builderWriteArray("Split packages", splitPkgs, true)
} }
// Installation reason
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) { if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(pkgInfo.Name, rootDir) installationReason := GetPackage(pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
var installationReasonString string var installationReasonString string
switch installationReason { switch installationReason {
case InstallationReasonManual: case InstallationReasonManual:
@@ -604,10 +658,10 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
default: default:
installationReasonString = "Unknown" installationReasonString = "Unknown"
} }
ret = append(ret, "Installation Reason: "+installationReasonString) builder.WriteString("Installation reason: " + installationReasonString + "\n")
} }
return strings.Join(ret, "\n") return strings.TrimSpace(builder.String())
} }
func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string) error { func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string) error {
@@ -771,7 +825,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
return nil return nil
} }
func installPackage(filename, rootDir string, verbose, force bool) error { func installPackage(filename string, installationReason InstallationReason, rootDir string, verbose, force bool) error {
if _, err := os.Stat(filename); os.IsNotExist(err) { if _, err := os.Stat(filename); os.IsNotExist(err) {
return err return err
} }
@@ -955,6 +1009,16 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
return err return err
} }
// Write local package information
localInfo := getPackageLocalInfo(bpmpkg.PkgInfo.Name, rootDir)
if !packageInstalled {
localInfo.InstalledOn = time.Now().Unix()
}
localInfo.LastUpdatedOn = time.Now().Unix()
localInfo.InstallationReason = string(installationReason)
SetPackageLocalInfo(bpmpkg.PkgInfo.Name, localInfo, rootDir)
// Save remove package scripts // Save remove package scripts
packageScripts, err := ReadPackageScripts(filename) packageScripts, err := ReadPackageScripts(filename)
if err != nil { if err != nil {
@@ -994,8 +1058,16 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
} }
} }
// Write persistent data version number
if _, err := os.Stat(path.Join(rootDir, "var/lib/bpm/.version")); err != nil {
err = os.WriteFile(path.Join(rootDir, "var/lib/bpm/.version"), []byte(strconv.Itoa(persistentDataVersion)), 0644)
if err != nil {
return err
}
}
// Ensure local package information has been initialized for rootDir // Ensure local package information has been initialized for rootDir
err = initializeLocalPackageInformation(rootDir) err = InitializeLocalPackageInformation(rootDir)
if err != nil { if err != nil {
return err return err
} }
@@ -1115,7 +1187,7 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
} }
// Ensure local package information has been initialized for rootDir // Ensure local package information has been initialized for rootDir
err = initializeLocalPackageInformation(rootDir) err = InitializeLocalPackageInformation(rootDir)
if err != nil { if err != nil {
return err return err
} }
+4 -4
View File
@@ -19,14 +19,14 @@ type BPMLock struct {
} }
func LockBPM(rootDir string) (*BPMLock, error) { func LockBPM(rootDir string) (*BPMLock, error) {
// Create parent directories if they don't already exist // Create cache directory if it doesn't already exist
err := os.MkdirAll(path.Join(rootDir, "/var/lib/bpm"), 0755) err := os.MkdirAll(path.Join(rootDir, "/var/cache/bpm"), 0755)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Create file // Create file
f, err := os.Create(path.Join(rootDir, "var/lib/bpm/bpm.lock")) f, err := os.Create(path.Join(rootDir, "var/cache/bpm/bpm.lock"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -37,7 +37,7 @@ func LockBPM(rootDir string) (*BPMLock, error) {
return nil, err return nil, err
} }
return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil return &BPMLock{f, path.Join(rootDir, "var/cache/bpm/bpm.lock")}, nil
} }
func (lock *BPMLock) Unlock() error { func (lock *BPMLock) Unlock() error {