47 Commits
Author SHA1 Message Date
EnumDev 3950c5f0d6 Dependency resolution improvements 2026-05-12 20:03:40 +03:00
EnumDev f3c2b9f0f6 Make 'ignore_packages' and 'ignore_paths' config options only work without rootdir 2026-05-10 19:45:07 +03:00
EnumDev dc8959c2f8 Add 'ignore_paths' config option 2026-05-10 17:26:38 +03:00
EnumDev 9122ffb9ca Improve hook system and add pre-transaction hooks 2026-05-10 16:43:58 +03:00
EnumDev a4e20b7d88 Add dependency version requirement resolution 2026-05-09 19:06:20 +03:00
EnumDev a03c9ad9ce Do not add fetch action for missing dependency again if it already exists 2026-04-26 15:13:25 +03:00
EnumDev 3f205ec412 Update GetEntryDependants function 2026-04-26 15:07:23 +03:00
EnumDev 2f9487affe Sort packages by name in error messages 2026-04-24 20:41:40 +03:00
EnumDev 596d8753a7 Allow removal of packages that depend on virtual packages with more than 1 provider 2026-04-24 20:41:16 +03:00
EnumDev 2de4eea7a4 Improve ignore_package functionality 2026-04-24 18:40:08 +03:00
EnumDev 5d0312856d Install missing dependencies on update 2026-04-20 17:17:54 +03:00
EnumDev 248359c02f Add 'GetPathOwners' function 2026-03-24 18:09:54 +02:00
EnumDev 1d81690bff Readd download size to database entry readable info 2026-03-14 11:08:42 +02:00
EnumDev 618916cd22 Remove flag to install all optional dependencies 2026-03-14 09:24:07 +02:00
EnumDev 8e4177c899 Show make dependant packages in database entry readable info 2026-03-14 09:18:47 +02:00
EnumDev 3f23580d16 Add sorting by installation date and last update date in 'list' subcommand 2026-02-24 20:51:04 +02:00
EnumDev 95dee0560a Add GPG signature verification 2026-02-24 18:50:50 +02:00
EnumDev 45721cfc43 Changes in printUsage function output 2026-02-07 18:05:15 +02:00
EnumDev 2aceb9efe8 Remove 'reinstall-all' flag 2026-02-07 15:07:10 +02:00
EnumDev bc3df83936 Reimplement runtime dependency resolution toggle 2026-02-07 15:03:38 +02:00
EnumDev f18c837bc2 Remove obsolete functions 2026-02-07 14:59:25 +02:00
EnumDev 76ad8e77c5 Greatly simplify dependency resolution 2026-02-07 14:52:14 +02:00
EnumDev 9ae2d3f893 Return virtual package providers in a sorted array 2026-02-07 11:51:05 +02:00
EnumDev 89c0c0892f Cleanup make and check dependencies only if package type is 'source' 2026-02-06 13:50:01 +02:00
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
EnumDev 2072e3856f Add 'maintainers' field to pkg.info 2026-01-25 17:23:53 +02:00
EnumDev 72f574209f Keep track of resolved virtual packages during dependency resolution 2026-01-22 12:35:08 +02:00
EnumDev 9c2d4595c7 Add 'jobs' flag to the 'compile' and 'install' subcommands 2026-01-19 17:42:47 +02:00
EnumDev 72f17f8954 Rework runtime dependency system 2026-01-06 19:35:42 +02:00
EnumDev fd646a820d Add 'runtime_depends' field to package info 2026-01-04 14:40:55 +02:00
14 changed files with 1968 additions and 764 deletions
+386 -119
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()
@@ -87,10 +88,11 @@ func main() {
currentFlagSet.BoolP("verbose", "v", false, "Show additional information about the current operation") currentFlagSet.BoolP("verbose", "v", false, "Show additional information about the current operation")
currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package installation") currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package installation")
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts") currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("optional", "o", false, "Install all optional dependencies") currentFlagSet.Bool("runtime", true, "Install all runtime dependencies")
currentFlagSet.String("installation-reason", "", "Specify the installation reason to use for the specified packages") currentFlagSet.String("installation-reason", "", "Specify the installation reason to use for the specified packages")
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.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()
@@ -139,7 +141,8 @@ func main() {
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts") currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
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("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")
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:])
updatePackages() updatePackages()
@@ -149,7 +152,7 @@ func main() {
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory") currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Show what packages own the specified paths", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Show what packages own the specified paths", os.Args[2:])
getFileOwner() getPathOwners()
case "c", "compile": case "c", "compile":
// Setup flags and help // Setup flags and help
currentFlagSet = flag.NewFlagSet("compile", flag.ExitOnError) currentFlagSet = flag.NewFlagSet("compile", flag.ExitOnError)
@@ -162,6 +165,7 @@ func main() {
currentFlagSet.BoolP("keep", "k", false, "Keep compilation files after successful package compilation") currentFlagSet.BoolP("keep", "k", false, "Keep compilation files after successful package compilation")
currentFlagSet.BoolP("output-directory", "o", false, "Set the output directory for the binary packages") currentFlagSet.BoolP("output-directory", "o", false, "Set the output directory for the binary packages")
currentFlagSet.Int("output-fd", -1, "Set the file descriptor output package names will be written to") currentFlagSet.Int("output-fd", -1, "Set the file descriptor output package names will be written to")
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), "Compile source packages and convert them to binary ones", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compile source packages and convert them to binary ones", os.Args[2:])
compilePackage() compilePackage()
@@ -170,6 +174,31 @@ 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 "keyring":
currentFlagSet = flag.NewFlagSet("keyring", flag.ExitOnError)
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("init", "i", false, "Initialize keyring")
currentFlagSet.BoolP("populate", "p", false, "Populate keyring")
currentFlagSet.BoolP("add", "a", false, "Add the specified keys")
currentFlagSet.BoolP("remove", "r", false, "Remove the specified keys")
currentFlagSet.BoolP("list", "l", false, "List all keys")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Manage the BPM keyring", os.Args[2:])
manageKeyring()
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
@@ -184,7 +213,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()
@@ -194,7 +231,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
@@ -207,7 +244,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
@@ -217,7 +256,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
} }
@@ -233,8 +272,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)
} }
@@ -258,12 +297,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)
}
} }
} }
@@ -278,7 +325,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
@@ -286,8 +333,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
@@ -301,20 +356,21 @@ func showPackageList() {
return return
} }
installedPackages := make([]struct { type installedPackage struct {
pkgInfo bpmlib.PackageInfo pkgInfo bpmlib.PackageInfo
installedSize int64 installedSize int64
}, len(installedPackageNames)) localInfo bpmlib.PackageLocalInfo
}
installedPackages := make([]installedPackage, len(installedPackageNames))
for i, pkgName := range installedPackageNames { for i, pkgName := range installedPackageNames {
pkgInfo := *bpmlib.GetPackageInfo(pkgName, rootDir) pkgInfo := *bpmlib.GetPackageInfo(pkgName, rootDir)
installedSize := bpmlib.GetPackage(pkgName, rootDir).GetInstalledSize() bpmpkg := bpmlib.GetPackage(pkgName, rootDir)
installedPackages[i] = struct { installedPackages[i] = installedPackage{
pkgInfo bpmlib.PackageInfo
installedSize int64
}{
pkgInfo: pkgInfo, pkgInfo: pkgInfo,
installedSize: installedSize, installedSize: bpmpkg.GetInstalledSize(),
localInfo: bpmpkg.LocalInfo,
} }
} }
@@ -325,16 +381,27 @@ func showPackageList() {
switch sortPackages { switch sortPackages {
case "", "name": case "", "name":
slices.SortFunc(installedPackages, func(a, b installedPackage) 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 installedPackage) int {
pkgInfo bpmlib.PackageInfo
installedSize int64
}) int {
return int(b.installedSize - a.installedSize) return int(b.installedSize - a.installedSize)
}) })
slices.SortFunc(databaseEntries, func(a, b *bpmlib.BPMDatabaseEntry) int { slices.SortFunc(databaseEntries, func(a, b *bpmlib.BPMDatabaseEntry) int {
return int(b.InstalledSize - a.InstalledSize) return int(b.InstalledSize - a.InstalledSize)
}) })
case "installation-date":
slices.SortFunc(installedPackages, func(a, b installedPackage) int {
return int(b.localInfo.InstalledOn - a.localInfo.InstalledOn)
})
case "last-update-date":
slices.SortFunc(installedPackages, func(a, b installedPackage) int {
return int(b.localInfo.LastUpdatedOn - a.localInfo.LastUpdatedOn)
})
default: default:
log.Printf("Error: cannot sort by '%s'", sortPackages) log.Printf("Error: cannot sort by '%s'", sortPackages)
exitCode = 1 exitCode = 1
@@ -359,7 +426,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 {
@@ -383,7 +450,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 {
@@ -391,7 +458,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 {
@@ -408,12 +475,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)
} }
} }
} }
@@ -480,10 +555,11 @@ func installPackages() {
verbose, _ := currentFlagSet.GetBool("verbose") verbose, _ := currentFlagSet.GetBool("verbose")
force, _ := currentFlagSet.GetBool("force") force, _ := currentFlagSet.GetBool("force")
yesAll, _ := currentFlagSet.GetBool("yes") yesAll, _ := currentFlagSet.GetBool("yes")
installOptional, _ := currentFlagSet.GetBool("optional") installRuntime, _ := currentFlagSet.GetBool("runtime")
installationReason, _ := currentFlagSet.GetString("installation-reason") installationReason, _ := currentFlagSet.GetString("installation-reason")
reinstall, _ := currentFlagSet.GetBool("reinstall") reinstallPackages, _ := currentFlagSet.GetBool("reinstall")
reinstallAll, _ := currentFlagSet.GetBool("reinstall-all") skipChecks, _ := currentFlagSet.GetBool("skip-checks")
compilationJobs, _ := currentFlagSet.GetInt("jobs")
// Get packages // Get packages
packages := currentFlagSet.Args() packages := currentFlagSet.Args()
@@ -515,16 +591,6 @@ func installPackages() {
return return
} }
// Get reinstall method
var reinstallMethod bpmlib.ReinstallMethod
if reinstallAll {
reinstallMethod = bpmlib.ReinstallMethodAll
} else if reinstall {
reinstallMethod = bpmlib.ReinstallMethodSpecified
} else {
reinstallMethod = bpmlib.ReinstallMethodNone
}
// Create BPM Lock file // Create BPM Lock file
fileLock, err := bpmlib.LockBPM(rootDir) fileLock, err := bpmlib.LockBPM(rootDir)
if err != nil { if err != nil {
@@ -534,6 +600,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 {
@@ -543,7 +617,7 @@ func installPackages() {
} }
// Create installation operation // Create installation operation
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, installOptional, force, verbose, packages...) operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallPackages, installRuntime, 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
@@ -554,6 +628,9 @@ func installPackages() {
return return
} }
// Set compilation job count
operation.CompilationJobs = compilationJobs
// Exit if operation contains no actions // Exit if operation contains no actions
if len(operation.Actions) == 0 { if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken") fmt.Println("No action needs to be taken")
@@ -585,6 +662,9 @@ func installPackages() {
return return
} }
// Get files that will be modifie during this operation
operation.GetModifiedFiles()
if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" || bpmlib.MainBPMConfig.ShowSourcePackageContents == "install-only" { if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" || bpmlib.MainBPMConfig.ShowSourcePackageContents == "install-only" {
// Show source package contents // Show source package contents
sourcePackagesShown, err := operation.ShowSourcePackageContent() sourcePackagesShown, err := operation.ShowSourcePackageContent()
@@ -607,6 +687,15 @@ func installPackages() {
// Get optional dependencies // Get optional dependencies
optionalDepends := operation.GetOptionalDependencies() optionalDepends := operation.GetOptionalDependencies()
// Executing pre-operation hooks
fmt.Println("Running pre-operation hooks...")
err = operation.RunPreHooks(verbose)
if err != nil {
log.Printf("Error: could not run pre-operation hooks: %s\n", err)
exitCode = 1
return
}
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -615,17 +704,19 @@ func installPackages() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { if err != nil {
log.Printf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run post-operation hooks: %s\n", err)
exitCode = 1 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
// Show optional dependencies // Show optional dependencies
if !installOptional && len(optionalDepends) != 0 { if len(optionalDepends) != 0 {
// List optional dependencies // List optional dependencies
fmt.Println("The following optional dependenices have been discovered:") fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends { for dependant, depends := range optionalDepends {
@@ -664,6 +755,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 {
@@ -680,6 +779,7 @@ func removePackages() {
return return
} else if errors.As(err, &bpmlib.PackageRemovalDependencyErr{}) { } else if errors.As(err, &bpmlib.PackageRemovalDependencyErr{}) {
for pkg, dependants := range err.(bpmlib.PackageRemovalDependencyErr).RequiredPackages { for pkg, dependants := range err.(bpmlib.PackageRemovalDependencyErr).RequiredPackages {
slices.Sort(dependants)
fmt.Printf("The following packages depend on package (%s): %s\n", pkg, strings.Join(dependants, ", ")) fmt.Printf("The following packages depend on package (%s): %s\n", pkg, strings.Join(dependants, ", "))
} }
@@ -715,6 +815,18 @@ func removePackages() {
} }
} }
// Get files that will be modifie during this operation
operation.GetModifiedFiles()
// Executing pre-operation hooks
fmt.Println("Running pre-operation hooks...")
err = operation.RunPreHooks(verbose)
if err != nil {
log.Printf("Error: could not run pre-operation hooks: %s\n", err)
exitCode = 1
return
}
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -723,14 +835,16 @@ func removePackages() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { if err != nil {
log.Printf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run post-operation hooks: %s\n", err)
exitCode = 1 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
} }
func doCleanup() { func doCleanup() {
@@ -777,6 +891,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)
@@ -828,6 +950,18 @@ func doCleanup() {
} }
} }
// Get files that will be modifie during this operation
operation.GetModifiedFiles()
// Executing pre-operation hooks
fmt.Println("Running pre-operation hooks...")
err = operation.RunPreHooks(verbose)
if err != nil {
log.Printf("Error: could not run pre-operation hooks: %s\n", err)
exitCode = 1
return
}
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -836,14 +970,16 @@ func doCleanup() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { if err != nil {
log.Printf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run post-operation hooks: %s\n", err)
exitCode = 1 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
} }
} }
@@ -897,7 +1033,8 @@ func updatePackages() {
yesAll, _ := currentFlagSet.GetBool("yes") yesAll, _ := currentFlagSet.GetBool("yes")
noSync, _ := currentFlagSet.GetBool("no-sync") noSync, _ := currentFlagSet.GetBool("no-sync")
allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades") allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades")
installOptional, _ := currentFlagSet.GetBool("optional") skipChecks, _ := currentFlagSet.GetBool("skip-checks")
compilationJobs, _ := currentFlagSet.GetInt("jobs")
// Check for required permissions // Check for required permissions
if os.Getuid() != 0 { if os.Getuid() != 0 {
@@ -915,6 +1052,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()
@@ -935,7 +1080,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, 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
@@ -946,6 +1091,9 @@ func updatePackages() {
return return
} }
// Set compilation job count
operation.CompilationJobs = compilationJobs
// Exit if operation contains no actions // Exit if operation contains no actions
if len(operation.Actions) == 0 { if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken") fmt.Println("No action needs to be taken")
@@ -977,6 +1125,9 @@ func updatePackages() {
return return
} }
// Get files that will be modifie during this operation
operation.GetModifiedFiles()
if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" { if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" {
// Show source package contents // Show source package contents
sourcePackagesShown, err := operation.ShowSourcePackageContent() sourcePackagesShown, err := operation.ShowSourcePackageContent()
@@ -999,6 +1150,15 @@ func updatePackages() {
// Get optional dependencies // Get optional dependencies
optionalDepends := operation.GetOptionalDependencies() optionalDepends := operation.GetOptionalDependencies()
// Executing pre-operation hooks
fmt.Println("Running pre-operation hooks...")
err = operation.RunPreHooks(verbose)
if err != nil {
log.Printf("Error: could not run pre-operation hooks: %s\n", err)
exitCode = 1
return
}
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -1007,17 +1167,19 @@ func updatePackages() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { if err != nil {
log.Printf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run post-operation hooks: %s\n", err)
exitCode = 1 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
// Show optional dependencies // Show optional dependencies
if !installOptional && len(optionalDepends) != 0 { if len(optionalDepends) != 0 {
// List optional dependencies // List optional dependencies
fmt.Println("The following optional dependenices have been discovered:") fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends { for dependant, depends := range optionalDepends {
@@ -1029,10 +1191,18 @@ func updatePackages() {
} }
} }
func getFileOwner() { func getPathOwners() {
// 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 {
@@ -1044,7 +1214,7 @@ func getFileOwner() {
// Ensure file exists // Ensure file exists
stat, err := os.Lstat(path) stat, err := os.Lstat(path)
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Printf("Error: file (%s) does not exist!\n", path) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
return return
} }
@@ -1057,57 +1227,21 @@ func getFileOwner() {
pathType = "Symlink" pathType = "Symlink"
} }
// Get absolte path to path pathOwners, err := bpmlib.GetPathOwners(path, rootDir)
absPath, err := filepath.Abs(path)
if err != nil { if err != nil {
log.Printf("Error: could not get absolute path of file (%s)\n", path) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
return return
} }
// Get path relative to rootDir
if !strings.HasPrefix(absPath, rootDir) {
log.Printf("Error: could not get path of file (%s) relative to root path", absPath)
exitCode = 1
return
}
absPath, err = filepath.Rel(rootDir, absPath)
if err != nil {
log.Printf("Error: could not get path of file (%s) relative to root path", absPath)
exitCode = 1
return
}
// Trim leading and trailing slashes
absPath = strings.TrimLeft(absPath, "/")
absPath = strings.TrimRight(absPath, "/")
// Get installed packages
pkgs, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil {
log.Printf("Error: could not get installed packages: %s\n", err.Error())
exitCode = 1
return
}
// Add packages that own path to list
var pkgList []string
for _, pkg := range pkgs {
if slices.ContainsFunc(bpmlib.GetPackage(pkg, rootDir).PkgFiles, func(entry *bpmlib.PackageFileEntry) bool {
return entry.Path == absPath
}) {
pkgList = append(pkgList, pkg)
}
}
// Print packages // Print packages
if len(pkgList) == 0 { if len(pathOwners) == 0 {
fmt.Printf("%s (%s) is not owned by any packages!\n", absPath, pathType) fmt.Printf("%s (%s) is not owned by any packages!\n", path, pathType)
exitCode = 1 exitCode = 1
return return
} else { } else {
fmt.Printf("%s (%s) is owned by the following packages:\n", absPath, pathType) fmt.Printf("%s (%s) is owned by the following packages:\n", path, pathType)
for _, pkg := range pkgList { for _, pkg := range pathOwners {
fmt.Println("- " + pkg) fmt.Println("- " + pkg)
} }
} }
@@ -1124,6 +1258,15 @@ func compilePackage() {
skipChecks, _ := currentFlagSet.GetBool("skip-checks") skipChecks, _ := currentFlagSet.GetBool("skip-checks")
outputDirectory, _ := currentFlagSet.GetString("output-directory") outputDirectory, _ := currentFlagSet.GetString("output-directory")
outputFd, _ := currentFlagSet.GetInt("output-fd") outputFd, _ := currentFlagSet.GetInt("output-fd")
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()
@@ -1133,7 +1276,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
@@ -1163,12 +1306,12 @@ func compilePackage() {
return return
} }
// Get direct runtime and make dependencies // Get common, make and check dependencies
totalDepends := make([]string, 0) totalDepends := make([]string, 0)
for _, depend := range bpmpkg.PkgInfo.GetDependencies(true, false) { totalDepends = append(totalDepends, bpmpkg.PkgInfo.Depends...)
if !slices.Contains(totalDepends, depend.PkgName) { totalDepends = append(totalDepends, bpmpkg.PkgInfo.MakeDepends...)
totalDepends = append(totalDepends, depend.PkgName) if !skipChecks {
} totalDepends = append(totalDepends, bpmpkg.PkgInfo.CheckDepends...)
} }
// Get unmet dependencies // Get unmet dependencies
@@ -1182,7 +1325,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:]...)
} }
} }
@@ -1198,7 +1341,7 @@ func compilePackage() {
} }
// Run 'bpm install' using the set privilege escalator command // Run 'bpm install' using the set privilege escalator command
args := []string{executable, "install", "--installation-reason=make-dependency"} args := []string{executable, "install", "--runtime=false", "--installation-reason=make-dependency"}
args = append(args, unmetDepends...) args = append(args, unmetDepends...)
cmd := exec.Command(bpmlib.CompilationBPMConfig.PrivilegeEscalatorCmd, args...) cmd := exec.Command(bpmlib.CompilationBPMConfig.PrivilegeEscalatorCmd, args...)
if yesAll { if yesAll {
@@ -1317,7 +1460,7 @@ func compilePackage() {
return return
} }
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks, keepCompilationFiles, verbose) outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, compilationJobs, skipChecks, keepCompilationFiles, verbose)
if err != nil { if err != nil {
// Remove unused packages // Remove unused packages
cleanupFunc() cleanupFunc()
@@ -1359,10 +1502,129 @@ func compareVersions() {
fmt.Println(bpmlib.CompareVersions(v1, v2)) fmt.Println(bpmlib.CompareVersions(v1, v2))
} }
func manageKeyring() {
// Get flags
rootDir, _ := currentFlagSet.GetString("root")
yesAll, _ := currentFlagSet.GetBool("yes")
initKeyring, _ := currentFlagSet.GetBool("init")
populateKeyring, _ := currentFlagSet.GetBool("populate")
addKeys, _ := currentFlagSet.GetBool("add")
removeKeys, _ := currentFlagSet.GetBool("remove")
listKeys, _ := currentFlagSet.GetBool("list")
// Check for required permissions
if os.Getuid() != 0 {
log.Printf("Error: this subcommand needs to be run with superuser permissions")
exitCode = 1
return
}
if initKeyring {
err := bpmlib.InitializeKeyring(rootDir)
if err != nil {
log.Printf("Error: could not populate keyring: %s", err)
exitCode = 1
return
}
fmt.Println("Keyring initialized successfully!")
} else if populateKeyring {
if !bpmlib.IsKeyringInitialized(rootDir) {
log.Printf("Error: keyring needs to be initialized first")
exitCode = 1
return
}
err := bpmlib.PopulateKeyring(rootDir)
if err != nil {
log.Printf("Error: could not populate keyring: %s", err)
exitCode = 1
return
}
fmt.Println("Keyring populated successfully!")
} else if addKeys {
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
if currentFlagSet.NArg() == 0 {
log.Printf("Error: no keys specified")
exitCode = 1
return
}
if !bpmlib.IsKeyringInitialized(rootDir) {
log.Printf("Error: keyring needs to be initialized first")
exitCode = 1
return
}
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--import")
cmd.Args = append(cmd.Args, currentFlagSet.Args()...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
log.Printf("Error: could not add keys: %s", err)
exitCode = 1
return
}
} else if removeKeys {
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
if currentFlagSet.NArg() == 0 {
log.Printf("Error: no keys specified")
exitCode = 1
return
}
if !bpmlib.IsKeyringInitialized(rootDir) {
log.Printf("Error: keyring needs to be initialized first")
exitCode = 1
return
}
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--delete-secret-and-public-keys")
if yesAll {
cmd.Args = append(cmd.Args, "--batch", "--yes")
}
cmd.Args = append(cmd.Args, currentFlagSet.Args()...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
log.Printf("Error: could not remove keys: %s", err)
exitCode = 1
return
}
} else if listKeys {
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
if !bpmlib.IsKeyringInitialized(rootDir) {
log.Printf("Error: keyring needs to be initialized first")
exitCode = 1
return
}
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-keys")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
log.Printf("Error: could not list keys: %s", err)
exitCode = 1
return
}
} else {
currentFlagSet.Usage()
}
}
func printUsage() { func printUsage() {
fmt.Printf("Usage: %s <subcommand> [options]\n", os.Args[0]) fmt.Printf("Usage: %s <subcommand> [options]\n", os.Args[0])
fmt.Println("Description: Manage system packages") fmt.Println("Description: Manage system packages")
fmt.Println("Subcommands:") fmt.Println("Main subcommands:")
fmt.Println(" q, query Show information on the specified packages") fmt.Println(" q, query Show information on the specified packages")
fmt.Println(" l, list List packages") fmt.Println(" l, list List packages")
fmt.Println(" s, search Search for packages in remote databases") fmt.Println(" s, search Search for packages in remote databases")
@@ -1372,8 +1634,13 @@ func printUsage() {
fmt.Println(" y, sync Sync all databases") fmt.Println(" y, sync Sync all databases")
fmt.Println(" u, update Update installed packages") fmt.Println(" u, update Update installed packages")
fmt.Println(" o, owner Show what packages own the specified paths") fmt.Println(" o, owner Show what packages own the specified paths")
fmt.Println("Developer subcommands:")
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")
fmt.Println("Maintenance subcommands:")
fmt.Println(" keyring Manage the BPM keyring")
fmt.Println(" upgrade-persistent-data Upgrade persistent data directory to the latest format")
} }
func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) { func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) {
+63 -1
View File
@@ -11,6 +11,7 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"runtime"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@@ -23,7 +24,12 @@ import (
var rootCompilationUID = "65534" var rootCompilationUID = "65534"
var rootCompilationGID = "65534" var rootCompilationGID = "65534"
func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, keepCompilationFiles, verbose bool) (outputBpmPackages map[string]string, err error) { func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJobs int, skipChecks, keepCompilationFiles, verbose bool) (outputBpmPackages map[string]string, err error) {
// Set compilation jobs
if compilationJobs <= 0 || compilationJobs > runtime.NumCPU() {
compilationJobs = runtime.NumCPU()
}
// Initialize map // Initialize map
outputBpmPackages = make(map[string]string) outputBpmPackages = make(map[string]string)
@@ -139,8 +145,27 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
env = append(env, "BPM_PKG_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision)) env = append(env, "BPM_PKG_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision))
env = append(env, "BPM_PKG_URL="+bpmpkg.PkgInfo.Url) env = append(env, "BPM_PKG_URL="+bpmpkg.PkgInfo.Url)
env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch) env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch)
env = append(env, "BPM_JOBS="+strconv.Itoa(compilationJobs))
env = append(env, CompilationBPMConfig.CompilationEnvironment...) env = append(env, CompilationBPMConfig.CompilationEnvironment...)
// Set common flags used for limiting job count
makeflags := ""
env = slices.DeleteFunc(env, func(s string) bool {
if strings.HasPrefix(s, "MAKEFLAGS=") {
makeflags = s + " "
return true
} else if strings.HasPrefix(s, "CMAKE_BUILD_PARALLEL_LEVEL=") {
return true
} else if strings.HasPrefix(s, "CARGO_BUILD_JOBS=") {
return true
}
return false
})
env = append(env, makeflags+"-j"+strconv.Itoa(compilationJobs))
env = append(env, "CMAKE_BUILD_PARALLEL_LEVEL="+strconv.Itoa(compilationJobs))
env = append(env, "CARGO_BUILD_JOBS="+strconv.Itoa(compilationJobs))
// Execute prepare and build functions in source.sh script // Execute prepare and build functions in source.sh script
cmd := exec.Command("bash", "-c", cmd := exec.Command("bash", "-c",
"set -a\n"+ // Source and export functions and variables in source.sh script "set -a\n"+ // Source and export functions and variables in source.sh script
@@ -431,6 +456,24 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
} }
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 {
@@ -509,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 {
@@ -532,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 {
@@ -576,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)
} }
+6 -3
View File
@@ -8,19 +8,22 @@ import (
type MainBPMConfigStruct struct { type MainBPMConfigStruct struct {
IgnorePackages []string `yaml:"ignore_packages"` IgnorePackages []string `yaml:"ignore_packages"`
IgnorePaths []string `yaml:"ignore_paths"`
ShowSourcePackageContents string `yaml:"show_source_package_contents"` ShowSourcePackageContents string `yaml:"show_source_package_contents"`
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"` CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
Databases []configDatabase `yaml:"databases"` Databases []configDatabase `yaml:"databases"`
} }
type configDatabase struct { type configDatabase struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Source string `yaml:"source"` Source string `yaml:"source"`
Disabled *bool `yaml:"disabled"` VerificationLevel string `yaml:"verification_level"`
Disabled *bool `yaml:"disabled"`
} }
type CompilationBPMConfigStruct struct { type CompilationBPMConfigStruct struct {
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"` PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
CompilationJobs int `yaml:"compilation_jobs"`
CompilationEnvironment []string `yaml:"compilation_env"` CompilationEnvironment []string `yaml:"compilation_env"`
} }
+252 -61
View File
@@ -17,12 +17,21 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type VerificationLevel int
const (
VerificationLevelNone VerificationLevel = iota
VerificationLevelAll
VerificationLevelTrusted
)
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 VerificationLevel VerificationLevel
Source string
} }
type BPMDatabaseEntry struct { type BPMDatabaseEntry struct {
@@ -59,8 +68,18 @@ 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
switch db.VerificationLevel {
case "0", "none":
database.VerificationLevel = VerificationLevelNone
case "1", "all":
database.VerificationLevel = VerificationLevelAll
case "2", "trusted":
database.VerificationLevel = VerificationLevelTrusted
default:
database.VerificationLevel = VerificationLevelAll
}
database.Source = db.Source database.Source = db.Source
for entryName, entry := range database.Entries { for entryName, entry := range database.Entries {
@@ -105,13 +124,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 +250,16 @@ 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 slices.SortFunc(providers, func(a, b *BPMDatabaseEntry) int {
return strings.Compare(a.Info.Name, b.Info.Name)
})
return providers
} }
func (db *BPMDatabase) FetchPackage(pkg string) (string, error) { func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
@@ -256,20 +276,76 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
} }
// Download package from url // Download package from url
err = downloadFile("Downloading "+entry.Info.Name, u, path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), 0644) filepath := path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath))
err = downloadFile("Downloading "+entry.Info.Name, u, filepath, 0644)
if err != nil { if err != nil {
return "", err return "", err
} }
return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil // Download and verify signature if required
if db.VerificationLevel != VerificationLevelNone {
err = downloadFile("", u+".sig", filepath+".sig", 0644)
if err != nil {
return "", err
}
err := VerifySignature(filepath, filepath+".sig", db.VerificationLevel == VerificationLevelTrusted, "/")
if err != nil {
return "", fmt.Errorf("Could not verify signature for %s: %s", filepath, err)
}
}
return filepath, nil
} }
func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) { func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
dependantsMap := make(map[string][]string) dependantsMap := make(map[string][]string)
// Loop through all entries
for _, db := range BPMDatabases { for _, db := range BPMDatabases {
for _, e := range db.Entries { for _, e := range db.Entries {
if slices.Contains(e.Info.Depends, entry.Info.Name) { // Skip iteration if comparing the same packages
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name) if e.Info.Name == entry.Info.Name {
continue
}
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
continue
}
// Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
continue
}
// Loop through each virtual package
for _, vpkg := range entry.Info.Provides {
// Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
break
}
// Add installed package to list if its runtime dependencies contain a provided virtual package
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
break
}
} }
} }
} }
@@ -297,7 +373,15 @@ 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 {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == 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,63 +406,157 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
return dependants return dependants
} }
func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableSize bool) string { func (entry *BPMDatabaseEntry) GetEntryMakeDependants() (dependants []string) {
ret := make([]string, 0) dependantsMap := make(map[string][]string)
appendArray := func(label string, array []string, sort bool) { for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.ContainsFunc(e.Info.MakeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
}
}
// Get keys
keySlice := slices.Collect(maps.Keys(dependantsMap))
slices.Sort(keySlice)
// Add all dependant entries to slice in alphabetical order
for _, entryName := range keySlice {
dbs := dependantsMap[entryName]
if len(dbs) > 1 {
for _, db := range dbs {
dependants = append(dependants, db+"/"+entryName)
}
} else {
dependants = append(dependants, entryName)
}
}
return dependants
}
func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool) string {
builder := strings.Builder{}
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")
}
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("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)
builderWriteArray("Make dependant packages", entry.GetEntryMakeDependants(), 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:
@@ -390,17 +568,30 @@ 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")
} }
// Download size
downloadSize := entry.DownloadSize
var downloadSizeStr string
if showBytes {
downloadSizeStr = strconv.FormatInt(downloadSize, 10)
} else {
downloadSizeStr = BytesToHumanReadable(downloadSize)
}
builder.WriteString("Download size: " + downloadSizeStr + "\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())
} }
+169 -172
View File
@@ -1,181 +1,11 @@
package bpmlib package bpmlib
import ( import (
"fmt"
"slices" "slices"
"strings"
) )
type pkgInstallationReason struct { func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultipleProviders bool) (dependants []string) {
PkgName string
InstallationReason InstallationReason
}
func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalDepends bool) []pkgInstallationReason {
allDepends := make([]pkgInstallationReason, 0)
for _, depend := range pkgInfo.Depends {
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend
}) {
allDepends = append(allDepends, pkgInstallationReason{
PkgName: depend,
InstallationReason: InstallationReasonDependency,
})
}
}
if includeOptionalDepends {
for _, depend := range pkgInfo.OptionalDepends {
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend
}) {
allDepends = append(allDepends, pkgInstallationReason{
PkgName: depend,
InstallationReason: InstallationReasonManual,
})
}
}
}
if includeMakeDepends {
for _, depend := range pkgInfo.MakeDepends {
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend
}) {
allDepends = append(allDepends, pkgInstallationReason{
PkgName: depend,
InstallationReason: InstallationReasonMakeDependency,
})
}
}
}
// Skip ignored packages
allDepends = slices.DeleteFunc(allDepends, func(depend pkgInstallationReason) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, depend.PkgName)
})
return allDepends
}
func (pkgInfo *PackageInfo) GetDependenciesRecursive(includeMakeDepends bool, rootDir string) (resolved []string) {
// Initialize slices
resolved = make([]string, 0)
unresolved := make([]string, 0)
// Call unexported function
pkgInfo.getDependenciesRecursive(&resolved, &unresolved, includeMakeDepends, rootDir)
return resolved
}
func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresolved *[]string, includeMakeDepends bool, rootDir string) {
// Add current package name to unresolved slice
*unresolved = append(*unresolved, pkgInfo.Name)
// Loop through all dependencies
for _, pkgIR := range pkgInfo.GetDependencies(includeMakeDepends, false) {
depend := pkgIR.PkgName
if isVirtual, p := IsVirtualPackage(depend, rootDir); isVirtual {
depend = p
}
if !slices.Contains(*resolved, depend) {
// Add current dependency to resolved slice when circular dependency is detected
if slices.Contains(*unresolved, depend) {
if !slices.Contains(*resolved, depend) {
*resolved = append(*resolved, depend)
}
continue
}
dependInfo := GetPackageInfo(depend, rootDir)
if dependInfo != nil {
dependInfo.getDependenciesRecursive(resolved, unresolved, includeMakeDepends, rootDir)
}
}
}
if !slices.Contains(*resolved, pkgInfo.Name) {
*resolved = append(*resolved, pkgInfo.Name)
}
*unresolved = stringSliceRemove(*unresolved, pkgInfo.Name)
}
func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) (resolved []pkgInstallationReason, unresolved []string) {
// Initialize slices
resolved = make([]pkgInstallationReason, 0)
unresolved = make([]string, 0)
// Call unexported function
resolvePackageDependenciesFromDatabase(&resolved, &unresolved, pkgInfo, checkMake, checkOptional, ignoreInstalled, verbose, rootDir)
// Remove main package from unresolved slice
unresolved = stringSliceRemove(unresolved, pkgInfo.Name)
return resolved, unresolved
}
func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, unresolved *[]string, pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) {
// Add current package name to unresolved slice
*unresolved = append(*unresolved, pkgInfo.Name)
// Loop through all dependencies
for _, pkgIR := range pkgInfo.GetDependencies(pkgInfo.Type == "source", checkOptional) {
// Skip dependency if it has already been resolved
if slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
return p.PkgName == pkgIR.PkgName
}) {
continue
}
// Add current dependency to resolved slice when circular dependency is detected
if slices.Contains(*unresolved, pkgIR.PkgName) {
if verbose {
fmt.Printf("Circular dependency was detected (%s -> %s). Installing %s first\n", pkgInfo.Name, pkgIR.PkgName, pkgIR.PkgName)
}
*resolved = append(*resolved, pkgInstallationReason{
PkgName: pkgIR.PkgName,
InstallationReason: pkgIR.InstallationReason,
})
continue
}
// Skip dependency if it is already installed or provided
if ignoreInstalled && IsPackageProvided(pkgIR.PkgName, rootDir) {
continue
}
// Get database entry for dependency
var err error
var entry *BPMDatabaseEntry
entry, _, err = GetDatabaseEntry(pkgIR.PkgName)
if err != nil {
if entry = ResolveVirtualPackage(pkgIR.PkgName); entry == nil {
if !slices.Contains(*unresolved, pkgIR.PkgName) {
*unresolved = append(*unresolved, pkgIR.PkgName)
}
continue
}
}
// Resolve the dependencies of this dependency
resolvePackageDependenciesFromDatabase(resolved, unresolved, entry.Info, checkMake, false, ignoreInstalled, verbose, rootDir)
// Move dependency from the unresolved slice to the resolved slice
if !slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
return p.PkgName == entry.Info.Name
}) {
*resolved = append(*resolved, pkgInstallationReason{
PkgName: entry.Info.Name,
InstallationReason: pkgIR.InstallationReason,
})
}
*unresolved = stringSliceRemove(*unresolved, entry.Info.Name)
}
}
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
// Get installed package names // Get installed package names
pkgs, ok := localPackageInformation[rootDir] pkgs, ok := localPackageInformation[rootDir]
if !ok { if !ok {
@@ -191,6 +21,16 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its dependencies include pkgName // Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
}
// Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name return n == pkgInfo.Name
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
@@ -199,8 +39,22 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Loop through each virtual package // Loop through each virtual package
for _, vpkg := range pkgInfo.Provides { for _, vpkg := range pkgInfo.Provides {
if skipMultipleProviders && len(GetVirtualPackageInfo(vpkg, rootDir)) > 1 {
continue
}
// Add installed package to list if its dependencies contain a provided virtual package // Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
}
// Add installed package to list if its runtime dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg return n == vpkg
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
@@ -228,6 +82,12 @@ 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 {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name return n == pkgInfo.Name
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
@@ -238,6 +98,12 @@ 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 {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg return n == vpkg
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
@@ -248,3 +114,134 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
return dependants return dependants
} }
type ResolvedPackage struct {
DatabaseEntry *BPMDatabaseEntry
InstallationReason InstallationReason
}
func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[string]string, includeRuntimeDepends bool, rootDir string) (resolved []ResolvedPackage, unresolved []string) {
visited := make([]string, 0)
var dfs func(resolvedPkg *PackageInfo)
dfs = func(pkgInfo *PackageInfo) {
checkDependencies := func(dependencies []string, installationReason InstallationReason) {
for _, depend := range dependencies {
// Split dependency name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
// Ignore if package is already installed
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
} else if providers := GetVirtualPackageInfo(dependName, rootDir); len(providers) > 0 {
continue
}
// Find database entry for dependency
var dependEntry *BPMDatabaseEntry
if resolvedVpkg, ok := resolvedVirtualPackages[dependName]; ok {
dependEntry, _, _ = GetDatabaseEntry(resolvedVpkg)
} else if entry, _, _ := GetDatabaseEntry(dependName); entry != nil {
dependEntry = entry
} else if providers := GetDatabaseVirtualPackageEntry(dependName); len(providers) > 0 {
dependEntry = providers[0]
}
if dependEntry == nil {
unresolved = append(unresolved, depend)
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
unresolved = append(unresolved, depend)
continue
}
// Skip ignored packages in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
if !slices.Contains(visited, dependEntry.Info.Name) {
dfs(dependEntry.Info)
resolved = append(resolved, ResolvedPackage{DatabaseEntry: dependEntry, InstallationReason: installationReason})
}
}
}
visited = append(visited, pkgInfo.Name)
checkDependencies(pkgInfo.Depends, InstallationReasonDependency)
if includeRuntimeDepends {
checkDependencies(pkgInfo.RuntimeDepends, InstallationReasonDependency)
}
if pkgInfo.Type == "source" {
checkDependencies(pkgInfo.MakeDepends, InstallationReasonMakeDependency)
checkDependencies(pkgInfo.CheckDepends, InstallationReasonMakeDependency)
}
}
dfs(pkgInfo)
return resolved, unresolved
}
func SplitPkgNameAndVersion(pkg string) (string, string, string) {
if strings.Contains(pkg, ">=") {
pkgSplit := strings.SplitN(pkg, ">=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">=", pkgVersion
} else if strings.Contains(pkg, ">") {
pkgSplit := strings.SplitN(pkg, ">", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">", pkgVersion
} else if strings.Contains(pkg, "<=") {
pkgSplit := strings.SplitN(pkg, "<=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<=", pkgVersion
} else if strings.Contains(pkg, "<") {
pkgSplit := strings.SplitN(pkg, "<", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<", pkgVersion
} else if strings.Contains(pkg, "=") {
pkgSplit := strings.SplitN(pkg, "=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "=", pkgVersion
}
return pkg, "", ""
}
func EvaluateDependency(pkg, matchVersion string) bool {
_, comparisonSymbol, pkgVersion := SplitPkgNameAndVersion(pkg)
switch comparisonSymbol {
case ">=":
return CompareVersions(matchVersion, pkgVersion) >= 0
case ">":
return CompareVersions(matchVersion, pkgVersion) > 0
case "<=":
return CompareVersions(matchVersion, pkgVersion) <= 0
case "<":
return CompareVersions(matchVersion, pkgVersion) < 0
case "=":
if cutPkgVersion, ok := strings.CutSuffix(pkgVersion, "*"); ok {
return strings.HasPrefix(matchVersion, cutPkgVersion)
} else {
return CompareVersions(matchVersion, pkgVersion) == 0
}
default:
return true
}
}
+4
View File
@@ -2,6 +2,7 @@ package bpmlib
import ( import (
"fmt" "fmt"
"slices"
"strings" "strings"
) )
@@ -10,6 +11,7 @@ type PackageNotFoundErr struct {
} }
func (e PackageNotFoundErr) Error() string { func (e PackageNotFoundErr) Error() string {
slices.Sort(e.packages)
return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ") return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ")
} }
@@ -18,6 +20,7 @@ type DependencyNotFoundErr struct {
} }
func (e DependencyNotFoundErr) Error() string { func (e DependencyNotFoundErr) Error() string {
slices.Sort(e.dependencies)
return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ") return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ")
} }
@@ -27,6 +30,7 @@ type PackageConflictErr struct {
} }
func (e PackageConflictErr) Error() string { func (e PackageConflictErr) Error() string {
slices.Sort(e.conflicts)
return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", ")) return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", "))
} }
+135 -53
View File
@@ -11,21 +11,14 @@ import (
"strings" "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 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, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) { func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallPackages bool, installRuntimeDependencies, 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), ModifiedFiles: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
@@ -44,7 +37,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.IsSplitPackage() { if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.IsSplitPackage() {
for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages { for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages {
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() { if !reinstallPackages && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() {
continue continue
} }
@@ -52,13 +45,13 @@ 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
} }
} }
operation.AppendAction(&InstallPackageAction{ operation.Actions = append(operation.Actions, &InstallPackageAction{
File: pkg, File: pkg,
InstallationReason: installationReason, InstallationReason: installationReason,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
@@ -68,7 +61,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
continue continue
} }
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() { if !reinstallPackages && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
continue continue
} }
@@ -76,35 +69,44 @@ 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
} }
} }
operation.AppendAction(&InstallPackageAction{ operation.Actions = append(operation.Actions, &InstallPackageAction{
File: pkg, File: pkg,
InstallationReason: installationReason, InstallationReason: installationReason,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
}) })
} else { } else {
// Split package name and required version
pkgName, _, _ := SplitPkgNameAndVersion(pkg)
var entry *BPMDatabaseEntry var entry *BPMDatabaseEntry
if e, _, err := GetDatabaseEntry(pkg); err == nil { if e, _, err := GetDatabaseEntry(pkgName); err == nil {
entry = e entry = e
} else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual { } else if providers := GetVirtualPackageInfo(pkgName, 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(pkgName); len(providers) > 0 {
entry = e entry = providers[0]
} else { } else {
pkgsNotFound = append(pkgsNotFound, pkg) pkgsNotFound = append(pkgsNotFound, pkg)
continue continue
} }
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
if !EvaluateDependency(pkg, entry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
}
if !reinstallPackages && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
continue continue
} }
@@ -112,13 +114,13 @@ 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
} }
} }
operation.AppendAction(&FetchPackageAction{ operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: installationReason, InstallationReason: installationReason,
DatabaseEntry: entry, DatabaseEntry: entry,
}) })
@@ -131,10 +133,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
} }
// Resolve dependencies // Resolve dependencies
err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installOptionalDependencies, verbose) operation.ResolveDependencies(installRuntimeDependencies)
if err != nil {
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
}
if len(operation.UnresolvedDepends) != 0 { if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation { if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends} return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
@@ -190,7 +189,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
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), ModifiedFiles: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
@@ -201,13 +200,13 @@ 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
} }
operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg}) operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
} }
// Do package cleanup // Do package cleanup
@@ -223,12 +222,12 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Get packages and their dependants // Get packages and their dependants
packageDepndants := make(map[string][]string, 0) packageDepndants := make(map[string][]string, 0)
for _, action := range operation.Actions { for _, action := range operation.Actions {
// Skip package if ignored // Skip package if ignored in config
if slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) { if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
continue continue
} }
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir) dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir, true)
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
} }
@@ -244,7 +243,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Remove dependant packages if ignored // Remove dependant packages if ignored
for pkg, required := range packageDepndants { for pkg, required := range packageDepndants {
required = slices.DeleteFunc(required, func(pkgName string) bool { required = slices.DeleteFunc(required, func(pkgName string) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, pkgName) return rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
}) })
packageDepndants[pkg] = required packageDepndants[pkg] = required
} }
@@ -268,7 +267,7 @@ func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOpe
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), ModifiedFiles: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
@@ -365,7 +364,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, allowDowngrades, forceInstallation, runChecks, verbose bool) (operation *BPMOperation, err error) {
// Sync databases // Sync databases
if syncDatabase { if syncDatabase {
err := SyncDatabase(verbose) err := SyncDatabase(verbose)
@@ -396,14 +395,16 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
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), ModifiedFiles: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
// Search for packages // Search for packages
pkgsNotFound := make([]string, 0)
for _, pkg := range pkgs { for _, pkg := range pkgs {
if slices.Contains(MainBPMConfig.IgnorePackages, pkg) { if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
continue continue
} }
var entry *BPMDatabaseEntry var entry *BPMDatabaseEntry
@@ -420,25 +421,106 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
} else { } else {
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.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: GetInstallationReason(pkg, rootDir), InstallationReason: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(),
DatabaseEntry: entry, DatabaseEntry: entry,
}) })
} }
// Check for missing dependencies
for _, depend := range entry.Info.Depends {
// Split package name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
}
if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 {
continue
}
// Find database entry for missing dependency
dependEntry, _, err := GetDatabaseEntry(dependName)
if err != nil {
providers := GetDatabaseVirtualPackageEntry(dependName)
if len(providers) == 0 {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
dependEntry = providers[0]
}
// Skip dependency if action already exists
if ActionSliceIndex(operation.Actions, dependEntry.Info.Name) != -1 {
continue
}
// Skip dependency if ignored in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
// Fetch dependency
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: InstallationReasonDependency,
DatabaseEntry: dependEntry,
})
}
// Check for missing runtime dependencies
for _, depend := range entry.Info.RuntimeDepends {
// Split package name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
}
if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 {
continue
}
// Find database entry for missing dependency
dependEntry, _, err := GetDatabaseEntry(dependName)
if err != nil {
providers := GetDatabaseVirtualPackageEntry(dependName)
if len(providers) == 0 {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
dependEntry = providers[0]
}
// Skip dependency if ignored in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
// Fetch dependency
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: InstallationReasonDependency,
DatabaseEntry: dependEntry,
})
}
} }
} }
// Check for new dependencies in updated packages // Return error if not all packages are found
err = operation.ResolveDependencies(false, installOptionalDependencies, verbose) if len(pkgsNotFound) != 0 {
if err != nil { return nil, PackageNotFoundErr{pkgsNotFound}
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
}
if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
} else if verbose {
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
}
} }
// Replace obsolete packages // Replace obsolete packages
+216
View File
@@ -0,0 +1,216 @@
package bpmlib
import (
"fmt"
"io"
"os"
"os/exec"
"path"
"strings"
)
func InitializeKeyring(rootDir string) error {
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
// Create GPG directory
err := os.Mkdir(gpgHomedir, 0700)
if err != nil && !os.IsExist(err) {
return err
}
// Get number of secret keys
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-secret-keys", "--with-colons")
output, err := cmd.Output()
if err != nil {
return err
}
secretKeysLineCount := len(strings.Split(strings.TrimSpace(string(output)), "\n"))
// Create signing key
if secretKeysLineCount <= 1 {
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--batch", "--gen-key")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = strings.NewReader(`%echo Creating signing key...
Key-Type: RSA
Key-Length: 4096
Key-Usage: sign
Name-Real: BPM signing key
Name-Email: bpm@localhost
Expire-Date: 0
%no-protection
%commit
%echo Done`)
err = cmd.Run()
if err != nil {
return err
}
}
return nil
}
func IsKeyringInitialized(rootDir string) bool {
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
// Check if gpg directory exists
if stat, err := os.Stat(gpgHomedir); err != nil || !stat.IsDir() {
return false
}
// Get number of secret keys
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-secret-keys", "--with-colons")
output, err := cmd.Output()
if err != nil {
return false
}
secretKeysLineCount := len(strings.Split(strings.TrimSpace(string(output)), "\n"))
// Return false if no signing key has been created
if secretKeysLineCount <= 1 {
return false
}
return true
}
func PopulateKeyring(rootDir string) error {
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
keyringsDir := path.Join(rootDir, "/var/lib/bpm/keyrings")
dirEntries, err := os.ReadDir(keyringsDir)
if err != nil && !os.IsNotExist(err) {
return err
}
// Remove removed keys
for _, entry := range dirEntries {
if entry.IsDir() {
continue
}
if !strings.HasSuffix(entry.Name(), ".revoked") {
continue
}
data, err := os.ReadFile(path.Join(keyringsDir, entry.Name()))
if err != nil {
return err
}
// Loop over all key IDs
for entry := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") {
// Ensure key ID exists
err := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-keys", entry).Run()
if err != nil {
continue
}
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--batch", "--yes", "--delete-secret-and-public-keys", entry)
err = cmd.Run()
if err != nil {
return err
}
}
}
// Import all keyrings
for _, entry := range dirEntries {
if entry.IsDir() {
continue
}
if !strings.HasSuffix(entry.Name(), ".pgp") && !strings.HasSuffix(entry.Name(), ".asc") {
continue
}
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--import", path.Join(keyringsDir, entry.Name()))
err := cmd.Run()
if err != nil {
return err
}
}
// Trust keys
for _, entry := range dirEntries {
if entry.IsDir() {
continue
}
if !strings.HasSuffix(entry.Name(), ".trustdb") {
continue
}
data, err := os.ReadFile(path.Join(keyringsDir, entry.Name()))
if err != nil {
return err
}
// Loop over all key IDs
for entry := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") {
keyID := strings.Split(entry, ":")[0]
// Ensure key ID exists
err := exec.Command("gpg", "--homedir="+gpgHomedir, "--list-keys", keyID).Run()
if err != nil {
continue
}
// Sign key
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--command-fd=0", "--batch", "--lsign-key", keyID)
cmd.Stdin = strings.NewReader("y\ny\n")
err = cmd.Run()
if err != nil {
return err
}
}
// Import owner trust database
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--import-ownertrust", path.Join(keyringsDir, entry.Name()))
err = cmd.Run()
if err != nil {
return err
}
}
return nil
}
func VerifySignature(filename, signature string, requireTrusted bool, rootDir string) error {
gpgHomedir := path.Join(rootDir, "/var/lib/bpm/gpg")
if _, err := os.Stat(gpgHomedir); err != nil {
return err
}
cmd := exec.Command("gpg", "--homedir="+gpgHomedir, "--status-fd=3", "--verify", signature, filename)
pipeReader, pipeWriter, err := os.Pipe()
if err != nil {
return err
}
defer pipeReader.Close()
defer pipeWriter.Close()
cmd.ExtraFiles = append(cmd.ExtraFiles, pipeWriter)
err = cmd.Run()
if err != nil {
return err
}
pipeWriter.Close()
if requireTrusted {
data, err := io.ReadAll(pipeReader)
if err != nil {
return err
}
dataStr := string(data)
if !strings.Contains(dataStr, "[GNUPG:] TRUST_FULLY") && !strings.Contains(dataStr, "[GNUPG:] TRUST_ULTIMATE") {
return fmt.Errorf("signature verified but not trusted")
}
}
return err
}
+46 -67
View File
@@ -1,26 +1,27 @@
package bpmlib package bpmlib
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"gopkg.in/yaml.v3"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"slices" "slices"
"strings" "strings"
"syscall" "syscall"
"gopkg.in/yaml.v3"
) )
type BPMHook struct { type BPMHook struct {
SourcePath string SourcePath string
SourceContent string SourceContent string
TriggerOperations []string `yaml:"trigger_operations"` TriggerActions []string `yaml:"trigger_actions"`
TargetType string `yaml:"target_type"` TriggerPreOperation bool `yaml:"trigger_pre_operation"`
Targets []string `yaml:"targets"` Targets []string `yaml:"targets"`
Depends []string `yaml:"depends"` Run string `yaml:"run"`
Run string `yaml:"run"` PassTargets bool `yaml:"pass_targets"`
} }
// createHook returns a BPMHook instance based on the content of the given string // createHook returns a BPMHook instance based on the content of the given string
@@ -33,13 +34,11 @@ func createHook(sourcePath string) (*BPMHook, error) {
// Create base hook structure // Create base hook structure
hook := &BPMHook{ hook := &BPMHook{
SourcePath: sourcePath, SourcePath: sourcePath,
SourceContent: string(bytes), SourceContent: string(bytes),
TriggerOperations: nil, TriggerActions: nil,
TargetType: "", Targets: nil,
Targets: nil, Run: "",
Depends: nil,
Run: "",
} }
// Unmarshal yaml string // Unmarshal yaml string
@@ -61,19 +60,15 @@ func (hook *BPMHook) IsValid() error {
ValidOperations := []string{"install", "upgrade", "remove"} ValidOperations := []string{"install", "upgrade", "remove"}
// Return error if any trigger operation is not valid or none are given // Return error if any trigger operation is not valid or none are given
if len(hook.TriggerOperations) == 0 { if len(hook.TriggerActions) == 0 {
return errors.New("no trigger operations specified") return errors.New("no trigger operations specified")
} }
for _, operation := range hook.TriggerOperations { for _, operation := range hook.TriggerActions {
if !slices.Contains(ValidOperations, operation) { if !slices.Contains(ValidOperations, operation) {
return errors.New("trigger operation '" + operation + "' is not valid") return errors.New("trigger operation '" + operation + "' is not valid")
} }
} }
if hook.TargetType != "package" && hook.TargetType != "path" {
return errors.New("target type '" + hook.TargetType + "' is not valid")
}
if len(hook.Run) == 0 { if len(hook.Run) == 0 {
return errors.New("command to run is empty") return errors.New("command to run is empty")
} }
@@ -83,55 +78,30 @@ func (hook *BPMHook) IsValid() error {
} }
// Execute hook if all conditions are met // Execute hook if all conditions are met
func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, rootDir string) error { func (hook *BPMHook) Execute(modifiedFiles map[string]string, preOperation bool, verbose bool, rootDir string) error {
// Check if package dependencies are met
installedPackages, err := GetInstalledPackages(rootDir)
if err != nil {
return err
}
for _, depend := range hook.Depends {
if !slices.Contains(installedPackages, depend) {
return nil
}
}
// Get modified files slice
modifiedFiles := make([]*PackageFileEntry, 0)
for pkg := range packageChanges {
if GetPackage(pkg, rootDir) != nil {
modifiedFiles = append(modifiedFiles, GetPackage(pkg, rootDir).PkgFiles...)
}
}
// Check if any targets are met // Check if any targets are met
targetMet := false targetsMet := make([]string, 0)
for _, target := range hook.Targets { for _, target := range hook.Targets {
if targetMet { for modifiedFile, action := range modifiedFiles {
break // Check if this hook is triggered by this file's action
} if !slices.Contains(hook.TriggerActions, action) {
if hook.TargetType == "package" { continue
for change, operation := range packageChanges {
if target == change && slices.Contains(hook.TriggerOperations, operation) {
targetMet = true
break
}
} }
} else {
glob, err := filepath.Glob(path.Join(rootDir, target)) // Check if file has already been checked
if err != nil { if slices.Contains(targetsMet, modifiedFile) {
return err continue
} }
for _, change := range modifiedFiles {
if slices.Contains(glob, path.Join(rootDir, change.Path)) { if matched, _ := filepath.Match(target, modifiedFile); !matched {
targetMet = true continue
break
}
} }
targetsMet = append(targetsMet, modifiedFile)
} }
} }
if !targetMet {
if len(targetsMet) == 0 {
return nil return nil
} }
@@ -140,16 +110,25 @@ func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, roo
cmd := exec.Command(splitCommand[0], splitCommand[1:]...) cmd := exec.Command(splitCommand[0], splitCommand[1:]...)
// Setup subprocess environment // Setup subprocess environment
cmd.Dir = "/" cmd.Dir = "/"
// Pass targets
if hook.PassTargets {
buffer := bytes.Buffer{}
buffer.WriteString(strings.Join(targetsMet, "\n") + "\n")
cmd.Stdin = &buffer
}
// Run hook in chroot if using the -R flag // Run hook in chroot if using the -R flag
if rootDir != "/" { if rootDir != "/" {
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir} cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir}
} }
if verbose { if !verbose {
fmt.Printf("Running hook (%s) with run command: %s\n", hook.SourcePath, strings.Join(splitCommand, " ")) fmt.Printf("Running hook (%s)\n", filepath.Base(hook.SourcePath))
} else {
fmt.Printf("Running hook (%s) with run command: %s\n", filepath.Base(hook.SourcePath), strings.Join(splitCommand, " "))
} }
err = cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return err return err
} }
+208 -44
View File
@@ -4,23 +4,46 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath"
"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 +74,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
} }
@@ -75,9 +104,44 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
return ret, nil return ret, nil
} }
func GetPathOwners(path, rootDir string) (ret []string, err error) {
// Get absolte path to path
path, err = filepath.Abs(path)
if err != nil {
return
}
path, err = filepath.Rel(rootDir, path)
if err != nil {
return
}
// Trim leading and trailing slashes
path = strings.TrimLeft(path, "/")
path = strings.TrimRight(path, "/")
// Get installed packages
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return
}
// Add packages that own path to list
for _, pkg := range pkgs {
pkgFiles := getPackageFiles(pkg, rootDir)
if slices.ContainsFunc(pkgFiles, func(entry *PackageFileEntry) bool {
return entry.Path == path
}) {
ret = append(ret, pkg)
}
}
return ret, nil
}
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 +152,22 @@ 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 { providers := installedVirtualPackages[rootDir][vpkg]
pkgs, err := GetInstalledPackages(rootDir) slices.SortFunc(providers, func(a, b *PackageInfo) int {
if err != nil { return strings.Compare(a.Name, b.Name)
return false })
}
for _, p := range pkgs { return providers
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 +182,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 +274,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
}
+1 -1
View File
@@ -39,7 +39,7 @@ func downloadFile(barText, u, filepath string, perm os.FileMode) error {
defer file.Close() defer file.Close()
// Create progress bar // Create progress bar
bar := createProgressBar(resp.ContentLength, barText, false) bar := createProgressBar(resp.ContentLength, barText, barText == "")
defer bar.Close() defer bar.Close()
// Copy data // Copy data
+233 -143
View File
@@ -14,79 +14,15 @@ import (
type BPMOperation struct { type BPMOperation struct {
Actions []OperationAction Actions []OperationAction
UnresolvedDepends []string UnresolvedDepends []string
Changes map[string]string ModifiedFiles map[string]string
CompilationJobs int
RunChecks bool
RootDir string RootDir string
compiledPackages map[string]string compiledPackages map[string]string
hasFetchedPackages bool hasFetchedPackages bool
} }
func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
return true
}
} else if action.GetActionType() == "fetch" {
if action.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg {
return true
}
} else if action.GetActionType() == "remove" {
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
return true
}
}
}
return false
}
func (operation *BPMOperation) AppendAction(action OperationAction) {
operation.InsertActionAt(len(operation.Actions), action)
}
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
if len(operation.Actions) == index { // nil or empty slice or after last element
operation.Actions = append(operation.Actions, action)
} else {
operation.Actions = append(operation.Actions[:index+1], operation.Actions[index:]...) // index < len(a)
operation.Actions[index] = action
}
if action.GetActionType() == "install" {
pkgInfo := action.(*InstallPackageAction).BpmPackage.PkgInfo
if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) {
operation.Changes[pkgInfo.Name] = "install"
} else {
operation.Changes[pkgInfo.Name] = "upgrade"
}
} else if action.GetActionType() == "fetch" {
pkgInfo := action.(*FetchPackageAction).DatabaseEntry.Info
if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) {
operation.Changes[pkgInfo.Name] = "install"
} else {
operation.Changes[pkgInfo.Name] = "upgrade"
}
} else if action.GetActionType() == "remove" {
operation.Changes[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = "remove"
}
}
func (operation *BPMOperation) RemoveAction(pkg, actionType string) {
operation.Actions = slices.DeleteFunc(operation.Actions, func(a OperationAction) bool {
if a.GetActionType() != actionType {
return false
}
if a.GetActionType() == "install" {
return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg
} else if a.GetActionType() == "fetch" {
return a.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg
} else if a.GetActionType() == "remove" {
return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg
}
return false
})
}
func (operation *BPMOperation) GetTotalDownloadSize() int64 { func (operation *BPMOperation) GetTotalDownloadSize() int64 {
var ret int64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
@@ -129,8 +65,9 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
return ret return ret
} }
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installOptionalDependencies, verbose bool) error { func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends bool) {
pos := 0 // Discover resolved virtual packages
resolvedVirtualPackages := make(map[string]string)
for _, value := range slices.Clone(operation.Actions) { for _, value := range slices.Clone(operation.Actions) {
var pkgInfo *PackageInfo var pkgInfo *PackageInfo
if value.GetActionType() == "install" { if value.GetActionType() == "install" {
@@ -140,58 +77,63 @@ func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, instal
action := value.(*FetchPackageAction) action := value.(*FetchPackageAction)
pkgInfo = action.DatabaseEntry.Info pkgInfo = action.DatabaseEntry.Info
} else { } else {
pos++
continue continue
} }
resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir) for _, vpkg := range pkgInfo.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = pkgInfo.Name
}
}
}
// Discover all dependencies
newActions := make([]OperationAction, 0)
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
pkgInfo = action.BpmPackage.PkgInfo
} else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction)
pkgInfo = action.DatabaseEntry.Info
} else {
continue
}
resolved, unresolved := ResolveDependencies(pkgInfo, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir)
// Append unresolved dependencies
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...) operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
operation.UnresolvedDepends = removeDuplicates(operation.UnresolvedDepends)
for _, resolvedPkg := range resolved { for _, resolvedPkg := range resolved {
if !operation.ActionsContainPackage(resolvedPkg.PkgName) && resolvedPkg.PkgName != pkgInfo.Name { if ActionSliceIndex(newActions, resolvedPkg.DatabaseEntry.Info.Name) == -1 { // Dependency not in actions slice
if !reinstallDependencies && IsPackageInstalled(resolvedPkg.PkgName, operation.RootDir) { var action OperationAction = &FetchPackageAction{
continue
}
entry, _, err := GetDatabaseEntry(resolvedPkg.PkgName)
if err != nil {
return errors.New("could not get database entry for package (" + resolvedPkg.PkgName + ")")
}
operation.InsertActionAt(pos, &FetchPackageAction{
InstallationReason: resolvedPkg.InstallationReason, InstallationReason: resolvedPkg.InstallationReason,
DatabaseEntry: entry, DatabaseEntry: resolvedPkg.DatabaseEntry,
}) }
pos++ newActions = append(newActions, action)
for _, vpkg := range resolvedPkg.DatabaseEntry.Info.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = resolvedPkg.DatabaseEntry.Info.Name
}
}
// Check if can move original action
if i := ActionSliceIndex(operation.Actions, resolvedPkg.DatabaseEntry.Info.Name); i != -1 {
newActions[len(newActions)-1] = operation.Actions[i]
}
} }
} }
pos++
}
return nil if ActionSliceIndex(newActions, pkgInfo.Name) == -1 {
} newActions = append(newActions, value)
func (operation *BPMOperation) RemoveNeededPackages() error {
removeActions := make(map[string]*RemovePackageAction)
for _, action := range slices.Clone(operation.Actions) {
if action.GetActionType() == "remove" {
removeActions[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = action.(*RemovePackageAction)
} }
} }
for pkg, action := range removeActions { operation.Actions = newActions
dependants := action.BpmPackage.PkgInfo.GetPackageDependants(operation.RootDir)
dependants = slices.DeleteFunc(dependants, func(d string) bool {
if _, ok := removeActions[d]; ok {
return true
}
return false
})
if len(dependants) != 0 {
operation.RemoveAction(pkg, action.GetActionType())
}
}
return nil
} }
func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error { func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
@@ -202,11 +144,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
@@ -217,34 +159,70 @@ 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(!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 && v.Type == "source" {
depends = append(depends, v.MakeDepends...)
depends = append(depends, v.CheckDepends...)
}
// Loop through all dependencies
for _, depend := range depends {
// Remove required version
depend, _, _ = SplitPkgNameAndVersion(depend)
// 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 + ")")
@@ -271,10 +249,11 @@ func (operation *BPMOperation) ReplaceObsoletePackages() {
} }
for _, r := range pkgInfo.Replaces { for _, r := range pkgInfo.Replaces {
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && !operation.ActionsContainPackage(bpmpkg.PkgInfo.Name) { if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && ActionSliceIndex(operation.Actions, bpmpkg.PkgInfo.Name) == -1 {
operation.InsertActionAt(0, &RemovePackageAction{ var action OperationAction = &RemovePackageAction{
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
}) }
operation.Actions = slices.Insert(operation.Actions, 0, action)
} }
} }
} }
@@ -483,24 +462,40 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
} }
for _, depend := range pkgInfo.OptionalDepends { for _, depend := range pkgInfo.OptionalDepends {
// Get optional dependency name
dependSplit := strings.SplitN(depend, ":", 2)
dependName, _, _ := SplitPkgNameAndVersion(dependSplit[0])
// Skip if dependency is already installed // Skip if dependency is already installed
if IsPackageInstalled(depend, operation.RootDir) { if IsPackageInstalled(dependName, 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 {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == dependName
}) {
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], dependName)
}
} }
} }
return return
} }
func (operation *BPMOperation) RunHooks(verbose bool) error { func (operation *BPMOperation) RunPreHooks(verbose bool) error {
// Return if hooks directory does not exist // Return if hooks directory does not exist
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() { if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
return nil return nil
@@ -520,7 +515,46 @@ func (operation *BPMOperation) RunHooks(verbose bool) error {
log.Printf("Error while reading hook (%s): %s", entry.Name(), err) log.Printf("Error while reading hook (%s): %s", entry.Name(), err)
} }
err = hook.Execute(operation.Changes, verbose, operation.RootDir) if !hook.TriggerPreOperation {
continue
}
err = hook.Execute(operation.ModifiedFiles, false, verbose, operation.RootDir)
if err != nil {
log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err)
continue
}
}
}
return nil
}
func (operation *BPMOperation) RunPostHooks(verbose bool) error {
// Return if hooks directory does not exist
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
return nil
}
// Get directory entries in hooks directory
dirEntries, err := os.ReadDir(path.Join(operation.RootDir, "var/lib/bpm/hooks"))
if err != nil {
return err
}
// Find all hooks, validate and execute them
for _, entry := range dirEntries {
if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), ".bpmhook") {
hook, err := createHook(path.Join(operation.RootDir, "var/lib/bpm/hooks", entry.Name()))
if err != nil {
log.Printf("Error while reading hook (%s): %s", entry.Name(), err)
}
if hook.TriggerPreOperation {
continue
}
err = hook.Execute(operation.ModifiedFiles, false, verbose, operation.RootDir)
if err != nil { if err != nil {
log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err) log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err)
continue continue
@@ -604,9 +638,34 @@ func (operation *BPMOperation) FetchPackages() (err error) {
} }
operation.hasFetchedPackages = true operation.hasFetchedPackages = true
return nil return nil
} }
func (operation *BPMOperation) GetModifiedFiles() {
// Get modified files
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
installAction := action.(*InstallPackageAction)
isUpgrade := IsPackageInstalled(installAction.BpmPackage.PkgInfo.Name, operation.RootDir)
for _, pkgFile := range installAction.BpmPackage.PkgFiles {
operation.ModifiedFiles[pkgFile.Path] = "install"
if isUpgrade {
operation.ModifiedFiles[pkgFile.Path] = "upgrade"
}
}
}
if action.GetActionType() == "remove" {
removeAction := action.(*RemovePackageAction)
for _, pkgFile := range removeAction.BpmPackage.PkgFiles {
operation.ModifiedFiles[pkgFile.Path] = "remove"
}
}
}
}
func (operation *BPMOperation) Execute(verbose, force bool) (err error) { func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Fetch packages // Fetch packages
if !operation.hasFetchedPackages { if !operation.hasFetchedPackages {
@@ -670,7 +729,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, 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)
} }
@@ -690,22 +749,15 @@ 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!")
return nil return nil
} }
@@ -741,3 +793,41 @@ type RemovePackageAction struct {
func (action *RemovePackageAction) GetActionType() string { func (action *RemovePackageAction) GetActionType() string {
return "remove" return "remove"
} }
func ActionSliceIndex(actions []OperationAction, pkg string) int {
for i, action := range actions {
if action.GetActionType() == "install" {
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
return i
}
} else if action.GetActionType() == "fetch" {
if action.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg {
return i
}
} else if action.GetActionType() == "remove" {
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
return i
}
}
}
return -1
}
func ActionSliceRemove(actions []OperationAction, pkg, actionType string) []OperationAction {
actions = slices.DeleteFunc(actions, func(a OperationAction) bool {
if a.GetActionType() != actionType {
return false
}
if a.GetActionType() == "install" {
return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg
} else if a.GetActionType() == "fetch" {
return a.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg
} else if a.GetActionType() == "remove" {
return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg
}
return false
})
return actions
}
+245 -96
View File
@@ -10,19 +10,22 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"regexp" "regexp"
"slices" "slices"
"sort" "sort"
"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 {
@@ -32,13 +35,16 @@ type PackageInfo struct {
Revision int `yaml:"revision,omitempty"` Revision int `yaml:"revision,omitempty"`
Url string `yaml:"url,omitempty"` Url string `yaml:"url,omitempty"`
License string `yaml:"license,omitempty"` License string `yaml:"license,omitempty"`
Maintainers []string `yaml:"maintainers,omitempty"`
Arch string `yaml:"architecture,omitempty"` Arch string `yaml:"architecture,omitempty"`
OutputArch string `yaml:"output_architecture,omitempty"` OutputArch string `yaml:"output_architecture,omitempty"`
Type string `yaml:"type,omitempty"` Type string `yaml:"type,omitempty"`
Keep []string `yaml:"keep,omitempty"` Keep []string `yaml:"keep,omitempty"`
Depends []string `yaml:"depends,omitempty"` Depends []string `yaml:"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"`
@@ -72,6 +78,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 {
@@ -120,35 +132,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) {
@@ -457,6 +451,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
OutputArch: GetArch(), OutputArch: GetArch(),
Keep: make([]string, 0), Keep: make([]string, 0),
Depends: make([]string, 0), Depends: make([]string, 0),
RuntimeDepends: make([]string, 0),
MakeDepends: make([]string, 0), MakeDepends: make([]string, 0),
OptionalDepends: make([]string, 0), OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0), Conflicts: make([]string, 0),
@@ -537,56 +532,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")
}
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("Make Dependencies", pkgInfo.MakeDepends)
}
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, false), 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:
@@ -598,10 +659,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 {
@@ -638,6 +699,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
extractFilename := path.Join(rootDir, header.Name) extractFilename := path.Join(rootDir, header.Name)
switch header.Typeflag { switch header.Typeflag {
case tar.TypeDir: case tar.TypeDir:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping Directory: %s (Path was ignored)\n", extractFilename)
}
continue
}
if _, err := os.Stat(extractFilename); err == nil { if _, err := os.Stat(extractFilename); err == nil {
if verbose { if verbose {
fmt.Printf("Skipping Directory: %s (Directory already exists)\n", extractFilename) fmt.Printf("Skipping Directory: %s (Directory already exists)\n", extractFilename)
@@ -665,6 +737,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
} }
bar.Add64(header.Size) bar.Add64(header.Size)
case tar.TypeReg: case tar.TypeReg:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping File: %s (Path was ignored)\n", extractFilename)
}
continue
}
skip := false skip := false
if _, err := os.Stat(extractFilename); err == nil { if _, err := os.Stat(extractFilename); err == nil {
for _, k := range bpmpkg.PkgInfo.Keep { for _, k := range bpmpkg.PkgInfo.Keep {
@@ -722,6 +805,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
} }
bar.Add64(header.Size) bar.Add64(header.Size)
case tar.TypeSymlink: case tar.TypeSymlink:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping Symlink: %s (Path was ignored)\n", extractFilename)
}
continue
}
err := os.Remove(extractFilename) err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
@@ -737,6 +831,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
} }
bar.Add64(header.Size) bar.Add64(header.Size)
case tar.TypeLink: case tar.TypeLink:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping Hard Link: %s (Path was ignored)\n", extractFilename)
}
continue
}
if verbose { if verbose {
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/"))) fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/")))
} }
@@ -765,7 +870,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
} }
@@ -812,26 +917,39 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
fmt.Printf("Removing old files for package (%s)...\n", bpmpkg.PkgInfo.Name) fmt.Printf("Removing old files for package (%s)...\n", bpmpkg.PkgInfo.Name)
} }
for _, entry := range fileEntries { for _, entry := range fileEntries {
file := path.Join(rootDir, entry.Path) finalPath := path.Join(rootDir, entry.Path)
stat, err := os.Lstat(file)
stat, err := os.Lstat(finalPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} } else if err != nil {
if err != nil {
return err return err
} }
if len(files[entry.Path]) != 0 {
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, entry.Path)
return matched
}); rootDir == "/" && ok {
if verbose { if verbose {
fmt.Println("Skipping path: " + file + " (Path is managed by multiple packages)") fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
} }
continue continue
} }
if len(files[entry.Path]) != 0 {
if verbose {
fmt.Println("Skipping path: " + finalPath + " (Path is managed by multiple packages)")
}
continue
}
shouldContinue := false shouldContinue := false
for _, value := range bpmpkg.PkgInfo.Keep { for _, value := range bpmpkg.PkgInfo.Keep {
if strings.HasSuffix(value, "/") { if strings.HasSuffix(value, "/") {
if strings.HasPrefix(entry.Path, value) || entry.Path == strings.TrimSuffix(value, "/") { if strings.HasPrefix(entry.Path, value) || entry.Path == strings.TrimSuffix(value, "/") {
if verbose { if verbose {
fmt.Println("Skipping path: " + file + " (Path is set to be kept during reinstalls/updates)") fmt.Println("Skipping path: " + finalPath + " (Path is set to be kept during reinstalls/updates)")
} }
shouldContinue = true shouldContinue = true
continue continue
@@ -839,7 +957,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
} else { } else {
if entry.Path == value { if entry.Path == value {
if verbose { if verbose {
fmt.Println("Skipping path: " + file + " (Path is set to be kept during reinstalls/updates)") fmt.Println("Skipping path: " + finalPath + " (Path is set to be kept during reinstalls/updates)")
} }
shouldContinue = true shouldContinue = true
continue continue
@@ -851,37 +969,37 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
} }
if stat.Mode()&os.ModeSymlink != 0 { if stat.Mode()&os.ModeSymlink != 0 {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
continue continue
} }
if stat.IsDir() { if stat.IsDir() {
dir, err := os.ReadDir(file) dir, err := os.ReadDir(finalPath)
if err != nil { if err != nil {
return err return err
} }
if len(dir) != 0 { if len(dir) != 0 {
if verbose { if verbose {
fmt.Println("Skipping non-empty directory: " + file) fmt.Println("Skipping non-empty directory: " + finalPath)
} }
continue continue
} }
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err = os.Remove(file) err = os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
@@ -949,6 +1067,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 {
@@ -988,8 +1116,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
} }
@@ -1033,31 +1169,44 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
// Removing package files // Removing package files
for _, entry := range fileEntries { for _, entry := range fileEntries {
bar.Add64(entry.SizeInBytes) bar.Add64(entry.SizeInBytes)
file := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(file) finalPath := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(finalPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} } else if err != nil {
if err != nil {
return err return err
} }
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, entry.Path)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
}
continue
}
if len(files[entry.Path]) != 0 { if len(files[entry.Path]) != 0 {
if verbose { if verbose {
fmt.Println("Skipping path: " + file + "(Path is managed by multiple packages)") fmt.Println("Skipping path: " + finalPath + "(Path is managed by multiple packages)")
} }
continue continue
} }
if lstat.Mode()&os.ModeSymlink != 0 { if lstat.Mode()&os.ModeSymlink != 0 {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
continue continue
} }
stat, err := os.Stat(file) stat, err := os.Stat(finalPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} }
@@ -1065,28 +1214,28 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
return err return err
} }
if stat.IsDir() { if stat.IsDir() {
dir, err := os.ReadDir(file) dir, err := os.ReadDir(finalPath)
if err != nil { if err != nil {
return err return err
} }
if len(dir) != 0 { if len(dir) != 0 {
if verbose { if verbose {
fmt.Println("Skipping non-empty directory: " + file) fmt.Println("Skipping non-empty directory: " + finalPath)
} }
continue continue
} }
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err = os.Remove(file) err = os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
@@ -1109,7 +1258,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 {