mirror of
https://github.com/EnumeratedDev/bpm.git
synced 2026-09-16 10:36:12 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc8959c2f8
|
||
|
|
9122ffb9ca
|
||
|
|
a4e20b7d88
|
||
|
|
a03c9ad9ce
|
||
|
|
3f205ec412
|
||
|
|
2f9487affe
|
||
|
|
596d8753a7
|
||
|
|
2de4eea7a4
|
||
|
|
5d0312856d
|
||
|
|
248359c02f
|
||
|
|
1d81690bff
|
||
|
|
618916cd22
|
||
|
|
8e4177c899
|
||
|
|
3f23580d16
|
||
|
|
95dee0560a
|
||
|
|
45721cfc43
|
||
|
|
2aceb9efe8
|
||
|
|
bc3df83936
|
||
|
|
f18c837bc2
|
||
|
|
76ad8e77c5
|
||
|
|
9ae2d3f893
|
||
|
|
89c0c0892f
|
||
|
|
ad89650dc4
|
||
|
|
3a7adb27ab
|
||
|
|
f6aa01339e
|
||
|
|
a29de2e52d
|
||
|
|
619335bbe6
|
||
|
|
8579c688d7
|
||
|
|
48cec35f0c
|
||
|
|
e19d64bf04
|
||
|
|
ee80640153
|
||
|
|
e4d94e378f
|
||
|
|
b37360ed82
|
||
|
|
ba0d288e45
|
||
|
|
c1fa9d9474
|
||
|
|
0742c81f2f
|
||
|
|
dd20574470
|
||
|
|
b3dc6e3e34
|
||
|
|
0a06808303
|
||
|
|
9f8a20c671
|
||
|
|
2072e3856f
|
||
|
|
72f574209f
|
||
|
|
9c2d4595c7
|
||
|
|
72f17f8954
|
||
|
|
fd646a820d
|
+386
-119
@@ -13,6 +13,7 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/EnumeratedDev/bpm/src/bpmlib"
|
||||
|
||||
@@ -54,7 +55,7 @@ func main() {
|
||||
currentFlagSet = flag.NewFlagSet("query", flag.ExitOnError)
|
||||
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
|
||||
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:])
|
||||
|
||||
showPackageInfo()
|
||||
@@ -70,7 +71,7 @@ func main() {
|
||||
currentFlagSet.Bool("make-depends", false, "Show packages installed as make dependencies")
|
||||
currentFlagSet.String("sort", "", "Sort listed packages by 'name' or 'size")
|
||||
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:])
|
||||
|
||||
showPackageList()
|
||||
@@ -87,10 +88,11 @@ func main() {
|
||||
currentFlagSet.BoolP("verbose", "v", false, "Show additional information about the current operation")
|
||||
currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package installation")
|
||||
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.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:])
|
||||
|
||||
installPackages()
|
||||
@@ -139,7 +141,8 @@ func main() {
|
||||
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
|
||||
currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases")
|
||||
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:])
|
||||
|
||||
updatePackages()
|
||||
@@ -149,7 +152,7 @@ func main() {
|
||||
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:])
|
||||
|
||||
getFileOwner()
|
||||
getPathOwners()
|
||||
case "c", "compile":
|
||||
// Setup flags and help
|
||||
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("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.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:])
|
||||
|
||||
compilePackage()
|
||||
@@ -170,6 +174,31 @@ func main() {
|
||||
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compare two version numbers", os.Args[2:])
|
||||
|
||||
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:
|
||||
printUsage()
|
||||
exitCode = 1
|
||||
@@ -184,7 +213,15 @@ func showPackageInfo() {
|
||||
// Get flags
|
||||
rootDir, _ := currentFlagSet.GetString("root")
|
||||
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
|
||||
packages := currentFlagSet.Args()
|
||||
@@ -194,7 +231,7 @@ func showPackageInfo() {
|
||||
}
|
||||
|
||||
// Read local databases
|
||||
err := bpmlib.ReadLocalDatabaseFiles()
|
||||
err = bpmlib.ReadLocalDatabaseFiles()
|
||||
if err != nil {
|
||||
log.Printf("Error: could not read local databases: %s", err)
|
||||
exitCode = 1
|
||||
@@ -207,7 +244,9 @@ func showPackageInfo() {
|
||||
var entry *bpmlib.BPMDatabaseEntry
|
||||
entry, _, err = bpmlib.GetDatabaseEntry(pkg)
|
||||
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)
|
||||
exitCode = 1
|
||||
return
|
||||
@@ -217,7 +256,7 @@ func showPackageInfo() {
|
||||
if n != 0 {
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize))
|
||||
fmt.Println(entry.CreateReadableInfo(rootDir, showBytes))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -233,8 +272,8 @@ func showPackageInfo() {
|
||||
}
|
||||
isFile = true
|
||||
} else {
|
||||
if isVirtual, p := bpmlib.IsVirtualPackage(pkg, rootDir); isVirtual {
|
||||
bpmpkg = bpmlib.GetPackage(p, rootDir)
|
||||
if providers := bpmlib.GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 {
|
||||
bpmpkg = bpmlib.GetPackage(providers[0].Name, rootDir)
|
||||
} else {
|
||||
bpmpkg = bpmlib.GetPackage(pkg, rootDir)
|
||||
}
|
||||
@@ -258,12 +297,20 @@ func showPackageInfo() {
|
||||
}
|
||||
fmt.Println(bpmpkg.PkgInfo.CreateReadableInfo(rootDir))
|
||||
if bpmpkg.PkgInfo.Type == "binary" {
|
||||
if !showHumanReadableSize {
|
||||
if showBytes {
|
||||
fmt.Printf("Installed size: %d\n", bpmpkg.GetInstalledSize())
|
||||
} else {
|
||||
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")
|
||||
sortPackages, _ := currentFlagSet.GetString("sort")
|
||||
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") {
|
||||
showManual = true
|
||||
@@ -286,8 +333,16 @@ func showPackageList() {
|
||||
showMakeDepends = true
|
||||
}
|
||||
|
||||
// Initialize installed packages map
|
||||
err := bpmlib.InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s", err)
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
// Read local databases
|
||||
err := bpmlib.ReadLocalDatabaseFiles()
|
||||
err = bpmlib.ReadLocalDatabaseFiles()
|
||||
if err != nil {
|
||||
log.Printf("Error: could not read local databases: %s", err)
|
||||
exitCode = 1
|
||||
@@ -301,20 +356,21 @@ func showPackageList() {
|
||||
return
|
||||
}
|
||||
|
||||
installedPackages := make([]struct {
|
||||
type installedPackage struct {
|
||||
pkgInfo bpmlib.PackageInfo
|
||||
installedSize int64
|
||||
}, len(installedPackageNames))
|
||||
localInfo bpmlib.PackageLocalInfo
|
||||
}
|
||||
|
||||
installedPackages := make([]installedPackage, len(installedPackageNames))
|
||||
for i, pkgName := range installedPackageNames {
|
||||
pkgInfo := *bpmlib.GetPackageInfo(pkgName, rootDir)
|
||||
installedSize := bpmlib.GetPackage(pkgName, rootDir).GetInstalledSize()
|
||||
bpmpkg := bpmlib.GetPackage(pkgName, rootDir)
|
||||
|
||||
installedPackages[i] = struct {
|
||||
pkgInfo bpmlib.PackageInfo
|
||||
installedSize int64
|
||||
}{
|
||||
installedPackages[i] = installedPackage{
|
||||
pkgInfo: pkgInfo,
|
||||
installedSize: installedSize,
|
||||
installedSize: bpmpkg.GetInstalledSize(),
|
||||
localInfo: bpmpkg.LocalInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,16 +381,27 @@ func showPackageList() {
|
||||
|
||||
switch sortPackages {
|
||||
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":
|
||||
slices.SortFunc(installedPackages, func(a, b struct {
|
||||
pkgInfo bpmlib.PackageInfo
|
||||
installedSize int64
|
||||
}) int {
|
||||
slices.SortFunc(installedPackages, func(a, b installedPackage) int {
|
||||
return int(b.installedSize - a.installedSize)
|
||||
})
|
||||
slices.SortFunc(databaseEntries, func(a, b *bpmlib.BPMDatabaseEntry) int {
|
||||
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:
|
||||
log.Printf("Error: cannot sort by '%s'", sortPackages)
|
||||
exitCode = 1
|
||||
@@ -359,7 +426,7 @@ func showPackageList() {
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
continue
|
||||
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
|
||||
@@ -383,7 +450,7 @@ func showPackageList() {
|
||||
if n != 0 {
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize))
|
||||
fmt.Println(entry.CreateReadableInfo(rootDir, showBytes))
|
||||
}
|
||||
} else {
|
||||
if len(installedPackages) == 0 {
|
||||
@@ -391,7 +458,7 @@ func showPackageList() {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
continue
|
||||
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
|
||||
@@ -408,12 +475,20 @@ func showPackageList() {
|
||||
|
||||
fmt.Println(pkg.pkgInfo.CreateReadableInfo(rootDir))
|
||||
if pkg.pkgInfo.Type == "binary" {
|
||||
if !showHumanReadableSize {
|
||||
if showBytes {
|
||||
fmt.Printf("Installed size: %d\n", pkg.installedSize)
|
||||
} else {
|
||||
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")
|
||||
force, _ := currentFlagSet.GetBool("force")
|
||||
yesAll, _ := currentFlagSet.GetBool("yes")
|
||||
installOptional, _ := currentFlagSet.GetBool("optional")
|
||||
installRuntime, _ := currentFlagSet.GetBool("runtime")
|
||||
installationReason, _ := currentFlagSet.GetString("installation-reason")
|
||||
reinstall, _ := currentFlagSet.GetBool("reinstall")
|
||||
reinstallAll, _ := currentFlagSet.GetBool("reinstall-all")
|
||||
reinstallPackages, _ := currentFlagSet.GetBool("reinstall")
|
||||
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
|
||||
compilationJobs, _ := currentFlagSet.GetInt("jobs")
|
||||
|
||||
// Get packages
|
||||
packages := currentFlagSet.Args()
|
||||
@@ -515,16 +591,6 @@ func installPackages() {
|
||||
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
|
||||
fileLock, err := bpmlib.LockBPM(rootDir)
|
||||
if err != nil {
|
||||
@@ -534,6 +600,14 @@ func installPackages() {
|
||||
}
|
||||
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
|
||||
err = bpmlib.ReadLocalDatabaseFiles()
|
||||
if err != nil {
|
||||
@@ -543,7 +617,7 @@ func installPackages() {
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
log.Printf("Error: %s", err)
|
||||
exitCode = 1
|
||||
@@ -554,6 +628,9 @@ func installPackages() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set compilation job count
|
||||
operation.CompilationJobs = compilationJobs
|
||||
|
||||
// Exit if operation contains no actions
|
||||
if len(operation.Actions) == 0 {
|
||||
fmt.Println("No action needs to be taken")
|
||||
@@ -585,6 +662,9 @@ func installPackages() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get files that will be modifie during this operation
|
||||
operation.GetModifiedFiles()
|
||||
|
||||
if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" || bpmlib.MainBPMConfig.ShowSourcePackageContents == "install-only" {
|
||||
// Show source package contents
|
||||
sourcePackagesShown, err := operation.ShowSourcePackageContent()
|
||||
@@ -607,6 +687,15 @@ func installPackages() {
|
||||
// Get optional dependencies
|
||||
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
|
||||
err = operation.Execute(verbose, force)
|
||||
if err != nil {
|
||||
@@ -615,17 +704,19 @@ func installPackages() {
|
||||
return
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
// Executing post-operation hooks
|
||||
fmt.Println("Running post-operation hooks...")
|
||||
err = operation.RunPostHooks(verbose)
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Operation complete!")
|
||||
|
||||
// Show optional dependencies
|
||||
if !installOptional && len(optionalDepends) != 0 {
|
||||
if len(optionalDepends) != 0 {
|
||||
// List optional dependencies
|
||||
fmt.Println("The following optional dependenices have been discovered:")
|
||||
for dependant, depends := range optionalDepends {
|
||||
@@ -664,6 +755,14 @@ func removePackages() {
|
||||
}
|
||||
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
|
||||
err = bpmlib.ReadLocalDatabaseFiles()
|
||||
if err != nil {
|
||||
@@ -680,6 +779,7 @@ func removePackages() {
|
||||
return
|
||||
} else if errors.As(err, &bpmlib.PackageRemovalDependencyErr{}) {
|
||||
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, ", "))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
err = operation.Execute(verbose, force)
|
||||
if err != nil {
|
||||
@@ -723,14 +835,16 @@ func removePackages() {
|
||||
return
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
// Executing post-operation hooks
|
||||
fmt.Println("Running post-operation hooks...")
|
||||
err = operation.RunPostHooks(verbose)
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Operation complete!")
|
||||
}
|
||||
|
||||
func doCleanup() {
|
||||
@@ -777,6 +891,14 @@ func doCleanup() {
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
err = operation.Execute(verbose, force)
|
||||
if err != nil {
|
||||
@@ -836,14 +970,16 @@ func doCleanup() {
|
||||
return
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
// Executing post-operation hooks
|
||||
fmt.Println("Running post-operation hooks...")
|
||||
err = operation.RunPostHooks(verbose)
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Operation complete!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,7 +1033,8 @@ func updatePackages() {
|
||||
yesAll, _ := currentFlagSet.GetBool("yes")
|
||||
noSync, _ := currentFlagSet.GetBool("no-sync")
|
||||
allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades")
|
||||
installOptional, _ := currentFlagSet.GetBool("optional")
|
||||
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
|
||||
compilationJobs, _ := currentFlagSet.GetInt("jobs")
|
||||
|
||||
// Check for required permissions
|
||||
if os.Getuid() != 0 {
|
||||
@@ -915,6 +1052,14 @@ func updatePackages() {
|
||||
}
|
||||
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
|
||||
if noSync {
|
||||
err := bpmlib.ReadLocalDatabaseFiles()
|
||||
@@ -935,7 +1080,7 @@ func updatePackages() {
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
log.Printf("Error: %s", err)
|
||||
exitCode = 1
|
||||
@@ -946,6 +1091,9 @@ func updatePackages() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set compilation job count
|
||||
operation.CompilationJobs = compilationJobs
|
||||
|
||||
// Exit if operation contains no actions
|
||||
if len(operation.Actions) == 0 {
|
||||
fmt.Println("No action needs to be taken")
|
||||
@@ -977,6 +1125,9 @@ func updatePackages() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get files that will be modifie during this operation
|
||||
operation.GetModifiedFiles()
|
||||
|
||||
if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" {
|
||||
// Show source package contents
|
||||
sourcePackagesShown, err := operation.ShowSourcePackageContent()
|
||||
@@ -999,6 +1150,15 @@ func updatePackages() {
|
||||
// Get optional dependencies
|
||||
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
|
||||
err = operation.Execute(verbose, force)
|
||||
if err != nil {
|
||||
@@ -1007,17 +1167,19 @@ func updatePackages() {
|
||||
return
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
// Executing post-operation hooks
|
||||
fmt.Println("Running post-operation hooks...")
|
||||
err = operation.RunPostHooks(verbose)
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Operation complete!")
|
||||
|
||||
// Show optional dependencies
|
||||
if !installOptional && len(optionalDepends) != 0 {
|
||||
if len(optionalDepends) != 0 {
|
||||
// List optional dependencies
|
||||
fmt.Println("The following optional dependenices have been discovered:")
|
||||
for dependant, depends := range optionalDepends {
|
||||
@@ -1029,10 +1191,18 @@ func updatePackages() {
|
||||
}
|
||||
}
|
||||
|
||||
func getFileOwner() {
|
||||
func getPathOwners() {
|
||||
// Get flags
|
||||
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
|
||||
files := currentFlagSet.Args()
|
||||
if len(files) == 0 {
|
||||
@@ -1044,7 +1214,7 @@ func getFileOwner() {
|
||||
// Ensure file exists
|
||||
stat, err := os.Lstat(path)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("Error: file (%s) does not exist!\n", path)
|
||||
log.Printf("Error: %s", err)
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
@@ -1057,57 +1227,21 @@ func getFileOwner() {
|
||||
pathType = "Symlink"
|
||||
}
|
||||
|
||||
// Get absolte path to path
|
||||
absPath, err := filepath.Abs(path)
|
||||
pathOwners, err := bpmlib.GetPathOwners(path, rootDir)
|
||||
if err != nil {
|
||||
log.Printf("Error: could not get absolute path of file (%s)\n", path)
|
||||
log.Printf("Error: %s", err)
|
||||
exitCode = 1
|
||||
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
|
||||
if len(pkgList) == 0 {
|
||||
fmt.Printf("%s (%s) is not owned by any packages!\n", absPath, pathType)
|
||||
if len(pathOwners) == 0 {
|
||||
fmt.Printf("%s (%s) is not owned by any packages!\n", path, pathType)
|
||||
exitCode = 1
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("%s (%s) is owned by the following packages:\n", absPath, pathType)
|
||||
for _, pkg := range pkgList {
|
||||
fmt.Printf("%s (%s) is owned by the following packages:\n", path, pathType)
|
||||
for _, pkg := range pathOwners {
|
||||
fmt.Println("- " + pkg)
|
||||
}
|
||||
}
|
||||
@@ -1124,6 +1258,15 @@ func compilePackage() {
|
||||
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
|
||||
outputDirectory, _ := currentFlagSet.GetString("output-directory")
|
||||
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
|
||||
sourcePackages := currentFlagSet.Args()
|
||||
@@ -1133,7 +1276,7 @@ func compilePackage() {
|
||||
}
|
||||
|
||||
// Read local databases
|
||||
err := bpmlib.ReadLocalDatabaseFiles()
|
||||
err = bpmlib.ReadLocalDatabaseFiles()
|
||||
if err != nil {
|
||||
log.Printf("Error: could not read local databases: %s", err)
|
||||
exitCode = 1
|
||||
@@ -1163,12 +1306,12 @@ func compilePackage() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get direct runtime and make dependencies
|
||||
// Get common, make and check dependencies
|
||||
totalDepends := make([]string, 0)
|
||||
for _, depend := range bpmpkg.PkgInfo.GetDependencies(true, false) {
|
||||
if !slices.Contains(totalDepends, depend.PkgName) {
|
||||
totalDepends = append(totalDepends, depend.PkgName)
|
||||
}
|
||||
totalDepends = append(totalDepends, bpmpkg.PkgInfo.Depends...)
|
||||
totalDepends = append(totalDepends, bpmpkg.PkgInfo.MakeDepends...)
|
||||
if !skipChecks {
|
||||
totalDepends = append(totalDepends, bpmpkg.PkgInfo.CheckDepends...)
|
||||
}
|
||||
|
||||
// Get unmet dependencies
|
||||
@@ -1182,7 +1325,7 @@ func compilePackage() {
|
||||
for i := len(unmetDepends) - 1; i >= 0; i-- {
|
||||
if slices.Contains(installedPackages, unmetDepends[i]) {
|
||||
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:]...)
|
||||
}
|
||||
}
|
||||
@@ -1198,7 +1341,7 @@ func compilePackage() {
|
||||
}
|
||||
|
||||
// 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...)
|
||||
cmd := exec.Command(bpmlib.CompilationBPMConfig.PrivilegeEscalatorCmd, args...)
|
||||
if yesAll {
|
||||
@@ -1317,7 +1460,7 @@ func compilePackage() {
|
||||
return
|
||||
}
|
||||
|
||||
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks, keepCompilationFiles, verbose)
|
||||
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, compilationJobs, skipChecks, keepCompilationFiles, verbose)
|
||||
if err != nil {
|
||||
// Remove unused packages
|
||||
cleanupFunc()
|
||||
@@ -1359,10 +1502,129 @@ func compareVersions() {
|
||||
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() {
|
||||
fmt.Printf("Usage: %s <subcommand> [options]\n", os.Args[0])
|
||||
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(" l, list List packages")
|
||||
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(" u, update Update installed packages")
|
||||
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(" 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) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -23,7 +24,12 @@ import (
|
||||
var rootCompilationUID = "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
|
||||
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_URL="+bpmpkg.PkgInfo.Url)
|
||||
env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch)
|
||||
env = append(env, "BPM_JOBS="+strconv.Itoa(compilationJobs))
|
||||
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
|
||||
cmd := exec.Command("bash", "-c",
|
||||
"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 {
|
||||
// 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 {
|
||||
// Replace variables
|
||||
replaceVars := func(s string) string {
|
||||
@@ -509,12 +552,22 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
|
||||
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")) {
|
||||
cmd := exec.Command("tar", "xf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
|
||||
if verbose {
|
||||
cmd.Args[1] = "xvf"
|
||||
}
|
||||
cmd.Dir = tempDirectory
|
||||
if os.Getuid() == 0 {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
}
|
||||
if extractTo != "" {
|
||||
err := os.MkdirAll(extractTo, 0755)
|
||||
if err != nil {
|
||||
@@ -532,6 +585,11 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
|
||||
}
|
||||
} else if !download.NoExtract && strings.HasSuffix(filepath, ".zip") {
|
||||
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 != "" {
|
||||
err := os.MkdirAll(extractTo, 0755)
|
||||
if err != nil {
|
||||
@@ -576,6 +634,10 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
|
||||
|
||||
cmd := exec.Command("git", "clone", "--depth=1", downloadUrl)
|
||||
cmd.Dir = tempDirectory
|
||||
if os.Getuid() == 0 {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
}
|
||||
if gitBranch != "" {
|
||||
cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
type MainBPMConfigStruct struct {
|
||||
IgnorePackages []string `yaml:"ignore_packages"`
|
||||
IgnorePaths []string `yaml:"ignore_paths"`
|
||||
ShowSourcePackageContents string `yaml:"show_source_package_contents"`
|
||||
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
|
||||
Databases []configDatabase `yaml:"databases"`
|
||||
@@ -16,11 +17,13 @@ type MainBPMConfigStruct struct {
|
||||
type configDatabase struct {
|
||||
Name string `yaml:"name"`
|
||||
Source string `yaml:"source"`
|
||||
VerificationLevel string `yaml:"verification_level"`
|
||||
Disabled *bool `yaml:"disabled"`
|
||||
}
|
||||
|
||||
type CompilationBPMConfigStruct struct {
|
||||
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
|
||||
CompilationJobs int `yaml:"compilation_jobs"`
|
||||
CompilationEnvironment []string `yaml:"compilation_env"`
|
||||
}
|
||||
|
||||
|
||||
+246
-55
@@ -17,11 +17,20 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type VerificationLevel int
|
||||
|
||||
const (
|
||||
VerificationLevelNone VerificationLevel = iota
|
||||
VerificationLevelAll
|
||||
VerificationLevelTrusted
|
||||
)
|
||||
|
||||
type BPMDatabase struct {
|
||||
DatabaseVersion int `yaml:"database_version"`
|
||||
Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
|
||||
VirtualPackages map[string][]string
|
||||
VirtualPackages map[string][]*BPMDatabaseEntry
|
||||
Name string
|
||||
VerificationLevel VerificationLevel
|
||||
Source string
|
||||
}
|
||||
|
||||
@@ -59,8 +68,18 @@ func (db *configDatabase) ReadLocalDatabase() error {
|
||||
}
|
||||
|
||||
// Initialize struct values
|
||||
database.VirtualPackages = make(map[string][]string)
|
||||
database.VirtualPackages = make(map[string][]*BPMDatabaseEntry)
|
||||
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
|
||||
|
||||
for entryName, entry := range database.Entries {
|
||||
@@ -105,13 +124,13 @@ func (db *configDatabase) ReadLocalDatabase() error {
|
||||
|
||||
// Add virtual packages to database
|
||||
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 {
|
||||
// Add virtual packages to database
|
||||
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
|
||||
}
|
||||
|
||||
func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry {
|
||||
func GetDatabaseVirtualPackageEntry(vpkg string) (providers []*BPMDatabaseEntry) {
|
||||
for _, db := range BPMDatabases {
|
||||
if v, ok := db.VirtualPackages[vpkg]; ok {
|
||||
slices.Sort(v)
|
||||
return db.Entries[v[0]]
|
||||
}
|
||||
providers = append(providers, db.VirtualPackages[vpkg]...)
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -256,20 +276,76 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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) {
|
||||
dependantsMap := make(map[string][]string)
|
||||
|
||||
// Loop through all entries
|
||||
for _, db := range BPMDatabases {
|
||||
for _, e := range db.Entries {
|
||||
if slices.Contains(e.Info.Depends, entry.Info.Name) {
|
||||
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
|
||||
// Skip iteration if comparing the same packages
|
||||
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)
|
||||
for _, db := range BPMDatabases {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -322,63 +406,157 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
|
||||
return dependants
|
||||
}
|
||||
|
||||
func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableSize bool) string {
|
||||
ret := make([]string, 0)
|
||||
appendArray := func(label string, array []string, sort bool) {
|
||||
func (entry *BPMDatabaseEntry) GetEntryMakeDependants() (dependants []string) {
|
||||
dependantsMap := make(map[string][]string)
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
if sort {
|
||||
// Sort array
|
||||
if sort {
|
||||
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
|
||||
}
|
||||
|
||||
ret = append(ret, "Name: "+entry.Info.Name)
|
||||
ret = append(ret, "Database: "+entry.Database.Name)
|
||||
ret = append(ret, "Description: "+entry.Info.Description)
|
||||
ret = append(ret, "Version: "+entry.Info.GetFullVersion())
|
||||
if entry.Info.Url != "" {
|
||||
ret = append(ret, "URL: "+entry.Info.Url)
|
||||
// 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 + ", ")
|
||||
}
|
||||
if entry.Info.License != "" {
|
||||
ret = append(ret, "License: "+entry.Info.License)
|
||||
}
|
||||
ret = append(ret, "Architecture: "+entry.Info.Arch)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Main information
|
||||
builder.WriteString("Name: " + entry.Info.Name + "\n")
|
||||
builder.WriteString("Database: " + entry.Database.Name + "\n")
|
||||
builder.WriteString("Description: " + entry.Info.Description + "\n")
|
||||
builder.WriteString("Version: " + entry.Info.GetFullVersion() + "\n")
|
||||
builderWriteStringNotEmpty("URL", entry.Info.Url)
|
||||
builderWriteStringNotEmpty("License", entry.Info.License)
|
||||
builderWriteArray("Maintainers", entry.Info.Maintainers, false)
|
||||
builder.WriteString("Architecture: " + entry.Info.Arch + "\n")
|
||||
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)
|
||||
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)
|
||||
builder.WriteString("Type: " + entry.Info.Type + "\n")
|
||||
|
||||
// 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 {
|
||||
splitPkgs := make([]string, len(entry.Info.SplitPackages))
|
||||
for i, splitPkgInfo := range entry.Info.SplitPackages {
|
||||
splitPkgs[i] = splitPkgInfo.Name
|
||||
}
|
||||
appendArray("Split Packages", splitPkgs, true)
|
||||
builderWriteArray("Split packages", splitPkgs, true)
|
||||
}
|
||||
|
||||
// Installation reason
|
||||
if rootDir != "" && IsPackageInstalled(entry.Info.Name, rootDir) {
|
||||
installationReason := GetInstallationReason(entry.Info.Name, rootDir)
|
||||
installationReason := GetPackage(entry.Info.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
var installationReasonString string
|
||||
switch installationReason {
|
||||
case InstallationReasonManual:
|
||||
@@ -390,17 +568,30 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableS
|
||||
default:
|
||||
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" {
|
||||
installedSize := entry.InstalledSize
|
||||
var installedSizeStr string
|
||||
if humanReadableSize {
|
||||
installedSizeStr = BytesToHumanReadable(installedSize)
|
||||
} else {
|
||||
if showBytes {
|
||||
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
@@ -1,181 +1,11 @@
|
||||
package bpmlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type pkgInstallationReason struct {
|
||||
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) {
|
||||
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultipleProviders bool) (dependants []string) {
|
||||
// Get installed package names
|
||||
pkgs, ok := localPackageInformation[rootDir]
|
||||
if !ok {
|
||||
@@ -191,6 +21,16 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
|
||||
|
||||
// Add installed package to list if its dependencies include pkgName
|
||||
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
|
||||
}) {
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
@@ -199,8 +39,22 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
|
||||
|
||||
// Loop through each virtual package
|
||||
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
|
||||
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
|
||||
}) {
|
||||
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
|
||||
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
|
||||
}) {
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
@@ -238,6 +98,12 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
|
||||
for _, vpkg := range pkgInfo.Provides {
|
||||
// Add installed package to list if its optional dependencies contain a provided virtual package
|
||||
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
|
||||
}) {
|
||||
dependants = append(dependants, installedPkg.Name)
|
||||
@@ -248,3 +114,134 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package bpmlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -10,6 +11,7 @@ type PackageNotFoundErr struct {
|
||||
}
|
||||
|
||||
func (e PackageNotFoundErr) Error() string {
|
||||
slices.Sort(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 {
|
||||
slices.Sort(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 {
|
||||
slices.Sort(e.conflicts)
|
||||
return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", "))
|
||||
|
||||
}
|
||||
|
||||
+127
-45
@@ -11,21 +11,14 @@ import (
|
||||
"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
|
||||
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
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RunChecks: runChecks,
|
||||
RootDir: rootDir,
|
||||
compiledPackages: make(map[string]string),
|
||||
}
|
||||
@@ -44,7 +37,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
|
||||
if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.IsSplitPackage() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -52,7 +45,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
installationReason := forceInstallationReason
|
||||
if installationReason == InstallationReasonUnknown {
|
||||
if IsPackageInstalled(splitPkg.Name, rootDir) {
|
||||
installationReason = GetInstallationReason(splitPkg.Name, rootDir)
|
||||
installationReason = GetPackage(splitPkg.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
} else {
|
||||
installationReason = InstallationReasonManual
|
||||
}
|
||||
@@ -68,7 +61,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -76,7 +69,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
installationReason := forceInstallationReason
|
||||
if installationReason == InstallationReasonUnknown {
|
||||
if IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
|
||||
installationReason = GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir)
|
||||
installationReason = GetPackage(bpmpkg.PkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
} else {
|
||||
installationReason = InstallationReasonManual
|
||||
}
|
||||
@@ -88,23 +81,32 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
BpmPackage: bpmpkg,
|
||||
})
|
||||
} else {
|
||||
// Split package name and required version
|
||||
pkgName, _, _ := SplitPkgNameAndVersion(pkg)
|
||||
|
||||
var entry *BPMDatabaseEntry
|
||||
|
||||
if e, _, err := GetDatabaseEntry(pkg); err == nil {
|
||||
if e, _, err := GetDatabaseEntry(pkgName); err == nil {
|
||||
entry = e
|
||||
} else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual {
|
||||
entry, _, err = GetDatabaseEntry(p)
|
||||
} else if providers := GetVirtualPackageInfo(pkgName, rootDir); len(providers) > 0 {
|
||||
entry, _, err = GetDatabaseEntry(providers[0].Name)
|
||||
if err != nil {
|
||||
pkgsNotFound = append(pkgsNotFound, pkg)
|
||||
continue
|
||||
}
|
||||
} else if e := ResolveVirtualPackage(pkg); e != nil {
|
||||
entry = e
|
||||
} else if providers := GetDatabaseVirtualPackageEntry(pkgName); len(providers) > 0 {
|
||||
entry = providers[0]
|
||||
} else {
|
||||
pkgsNotFound = append(pkgsNotFound, pkg)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -112,7 +114,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
installationReason := forceInstallationReason
|
||||
if installationReason == InstallationReasonUnknown {
|
||||
if IsPackageInstalled(entry.Info.Name, rootDir) {
|
||||
installationReason = GetInstallationReason(entry.Info.Name, rootDir)
|
||||
installationReason = GetPackage(entry.Info.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
} else {
|
||||
installationReason = InstallationReasonManual
|
||||
}
|
||||
@@ -131,10 +133,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
|
||||
}
|
||||
|
||||
// Resolve dependencies
|
||||
err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installOptionalDependencies, verbose)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
|
||||
}
|
||||
operation.ResolveDependencies(installRuntimeDependencies)
|
||||
if len(operation.UnresolvedDepends) != 0 {
|
||||
if !forceInstallation {
|
||||
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
|
||||
@@ -190,7 +189,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
compiledPackages: make(map[string]string),
|
||||
}
|
||||
@@ -201,8 +200,8 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
|
||||
// Search for packages
|
||||
for _, pkg := range packages {
|
||||
bpmpkg := GetPackage(pkg, rootDir)
|
||||
if isVirutal, vpkg := IsVirtualPackage(pkg, rootDir); isVirutal {
|
||||
bpmpkg = GetPackage(vpkg, rootDir)
|
||||
if providers := GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 {
|
||||
bpmpkg = GetPackage(providers[0].Name, rootDir)
|
||||
}
|
||||
if bpmpkg == nil {
|
||||
continue
|
||||
@@ -223,12 +222,12 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
|
||||
// Get packages and their dependants
|
||||
packageDepndants := make(map[string][]string, 0)
|
||||
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) {
|
||||
continue
|
||||
}
|
||||
|
||||
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir)
|
||||
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir, true)
|
||||
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
|
||||
}
|
||||
|
||||
@@ -268,7 +267,7 @@ func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOpe
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
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
|
||||
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
|
||||
if syncDatabase {
|
||||
err := SyncDatabase(verbose)
|
||||
@@ -396,12 +395,14 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
|
||||
operation = &BPMOperation{
|
||||
Actions: make([]OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
ModifiedFiles: make(map[string]string),
|
||||
RunChecks: runChecks,
|
||||
RootDir: rootDir,
|
||||
compiledPackages: make(map[string]string),
|
||||
}
|
||||
|
||||
// Search for packages
|
||||
pkgsNotFound := make([]string, 0)
|
||||
for _, pkg := range pkgs {
|
||||
if slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
|
||||
continue
|
||||
@@ -421,24 +422,105 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
|
||||
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
|
||||
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
|
||||
operation.AppendAction(&FetchPackageAction{
|
||||
InstallationReason: GetInstallationReason(pkg, rootDir),
|
||||
InstallationReason: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(),
|
||||
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 operation.ActionsContainPackage(dependEntry.Info.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip dependency if ignored in config
|
||||
if 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.AppendAction(&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 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.AppendAction(&FetchPackageAction{
|
||||
InstallationReason: InstallationReasonDependency,
|
||||
DatabaseEntry: dependEntry,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for new dependencies in updated packages
|
||||
err = operation.ResolveDependencies(false, installOptionalDependencies, verbose)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
|
||||
}
|
||||
if len(operation.UnresolvedDepends) != 0 {
|
||||
if !forceInstallation {
|
||||
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
|
||||
} else if verbose {
|
||||
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
|
||||
}
|
||||
// Return error if not all packages are found
|
||||
if len(pkgsNotFound) != 0 {
|
||||
return nil, PackageNotFoundErr{pkgsNotFound}
|
||||
}
|
||||
|
||||
// Replace obsolete packages
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+40
-61
@@ -1,26 +1,27 @@
|
||||
package bpmlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type BPMHook struct {
|
||||
SourcePath string
|
||||
SourceContent string
|
||||
TriggerOperations []string `yaml:"trigger_operations"`
|
||||
TargetType string `yaml:"target_type"`
|
||||
TriggerActions []string `yaml:"trigger_actions"`
|
||||
TriggerPreOperation bool `yaml:"trigger_pre_operation"`
|
||||
Targets []string `yaml:"targets"`
|
||||
Depends []string `yaml:"depends"`
|
||||
Run string `yaml:"run"`
|
||||
PassTargets bool `yaml:"pass_targets"`
|
||||
}
|
||||
|
||||
// createHook returns a BPMHook instance based on the content of the given string
|
||||
@@ -35,10 +36,8 @@ func createHook(sourcePath string) (*BPMHook, error) {
|
||||
hook := &BPMHook{
|
||||
SourcePath: sourcePath,
|
||||
SourceContent: string(bytes),
|
||||
TriggerOperations: nil,
|
||||
TargetType: "",
|
||||
TriggerActions: nil,
|
||||
Targets: nil,
|
||||
Depends: nil,
|
||||
Run: "",
|
||||
}
|
||||
|
||||
@@ -61,19 +60,15 @@ func (hook *BPMHook) IsValid() error {
|
||||
ValidOperations := []string{"install", "upgrade", "remove"}
|
||||
|
||||
// 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")
|
||||
}
|
||||
for _, operation := range hook.TriggerOperations {
|
||||
for _, operation := range hook.TriggerActions {
|
||||
if !slices.Contains(ValidOperations, operation) {
|
||||
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 {
|
||||
return errors.New("command to run is empty")
|
||||
}
|
||||
@@ -83,55 +78,30 @@ func (hook *BPMHook) IsValid() error {
|
||||
}
|
||||
|
||||
// Execute hook if all conditions are met
|
||||
func (hook *BPMHook) Execute(packageChanges map[string]string, 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...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (hook *BPMHook) Execute(modifiedFiles map[string]string, preOperation bool, verbose bool, rootDir string) error {
|
||||
// Check if any targets are met
|
||||
targetMet := false
|
||||
targetsMet := make([]string, 0)
|
||||
for _, target := range hook.Targets {
|
||||
if targetMet {
|
||||
break
|
||||
for modifiedFile, action := range modifiedFiles {
|
||||
// Check if this hook is triggered by this file's action
|
||||
if !slices.Contains(hook.TriggerActions, action) {
|
||||
continue
|
||||
}
|
||||
if hook.TargetType == "package" {
|
||||
for change, operation := range packageChanges {
|
||||
if target == change && slices.Contains(hook.TriggerOperations, operation) {
|
||||
targetMet = true
|
||||
break
|
||||
|
||||
// Check if file has already been checked
|
||||
if slices.Contains(targetsMet, modifiedFile) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matched, _ := filepath.Match(target, modifiedFile); !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
targetsMet = append(targetsMet, modifiedFile)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glob, err := filepath.Glob(path.Join(rootDir, target))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, change := range modifiedFiles {
|
||||
if slices.Contains(glob, path.Join(rootDir, change.Path)) {
|
||||
targetMet = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !targetMet {
|
||||
|
||||
if len(targetsMet) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -140,16 +110,25 @@ func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, roo
|
||||
cmd := exec.Command(splitCommand[0], splitCommand[1:]...)
|
||||
// Setup subprocess environment
|
||||
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
|
||||
if rootDir != "/" {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
fmt.Printf("Running hook (%s) with run command: %s\n", hook.SourcePath, strings.Join(splitCommand, " "))
|
||||
if !verbose {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,23 +4,46 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"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
|
||||
if _, ok := localPackageInformation[rootDir]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
tempPackageInformation := make(map[string]*PackageInfo)
|
||||
tempInstalledVirtualPackages := make(map[string][]*PackageInfo)
|
||||
|
||||
// Get path to installed package information directory
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
// Get paths
|
||||
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
|
||||
items, err := os.ReadDir(installedDir)
|
||||
@@ -51,15 +74,21 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
|
||||
|
||||
// Add package to slice
|
||||
tempPackageInformation[info.Name] = info
|
||||
|
||||
// Add virtual packages
|
||||
for _, vpkg := range info.Provides {
|
||||
tempInstalledVirtualPackages[vpkg] = append(tempInstalledVirtualPackages[vpkg], info)
|
||||
}
|
||||
}
|
||||
|
||||
localPackageInformation[rootDir] = tempPackageInformation
|
||||
installedVirtualPackages[rootDir] = tempInstalledVirtualPackages
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetInstalledPackages(rootDir string) (ret []string, err error) {
|
||||
// Initialize local package information
|
||||
err = initializeLocalPackageInformation(rootDir)
|
||||
err = InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,9 +104,44 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
|
||||
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 {
|
||||
// Initialize local package information
|
||||
err := initializeLocalPackageInformation(rootDir)
|
||||
err := InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -88,48 +152,22 @@ func IsPackageInstalled(pkg, rootDir string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func IsVirtualPackage(pkg, rootDir string) (bool, string) {
|
||||
pkgs, err := GetInstalledPackages(rootDir)
|
||||
func GetVirtualPackageInfo(vpkg, rootDir string) []*PackageInfo {
|
||||
err := InitializeLocalPackageInformation(rootDir)
|
||||
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 {
|
||||
pkgs, err := GetInstalledPackages(rootDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
if p == pkg {
|
||||
return true
|
||||
}
|
||||
i := GetPackageInfo(p, rootDir)
|
||||
if i == nil {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(i.Provides, pkg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
providers := installedVirtualPackages[rootDir][vpkg]
|
||||
slices.SortFunc(providers, func(a, b *PackageInfo) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
|
||||
err := initializeLocalPackageInformation(rootDir)
|
||||
err := InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -144,10 +182,12 @@ func GetPackage(pkg, rootDir string) *BPMPackage {
|
||||
}
|
||||
|
||||
files := getPackageFiles(pkgInfo.Name, rootDir)
|
||||
localInfo := getPackageLocalInfo(pkgInfo.Name, rootDir)
|
||||
|
||||
return &BPMPackage{
|
||||
PkgInfo: pkgInfo,
|
||||
PkgFiles: files,
|
||||
LocalInfo: localInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,3 +274,127 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
|
||||
|
||||
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
@@ -39,7 +39,7 @@ func downloadFile(barText, u, filepath string, perm os.FileMode) error {
|
||||
defer file.Close()
|
||||
|
||||
// Create progress bar
|
||||
bar := createProgressBar(resp.ContentLength, barText, false)
|
||||
bar := createProgressBar(resp.ContentLength, barText, barText == "")
|
||||
defer bar.Close()
|
||||
|
||||
// Copy data
|
||||
|
||||
+179
-88
@@ -14,7 +14,9 @@ import (
|
||||
type BPMOperation struct {
|
||||
Actions []OperationAction
|
||||
UnresolvedDepends []string
|
||||
Changes map[string]string
|
||||
ModifiedFiles map[string]string
|
||||
CompilationJobs int
|
||||
RunChecks bool
|
||||
RootDir string
|
||||
|
||||
compiledPackages map[string]string
|
||||
@@ -51,24 +53,6 @@ func (operation *BPMOperation) InsertActionAt(index int, action OperationAction)
|
||||
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) {
|
||||
@@ -129,7 +113,29 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installOptionalDependencies, verbose bool) error {
|
||||
func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends bool) {
|
||||
// Discover resolved virtual packages
|
||||
resolvedVirtualPackages := make(map[string]string)
|
||||
for _, value := range slices.Clone(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
|
||||
}
|
||||
|
||||
for _, vpkg := range pkgInfo.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = pkgInfo.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discover all dependencies
|
||||
pos := 0
|
||||
for _, value := range slices.Clone(operation.Actions) {
|
||||
var pkgInfo *PackageInfo
|
||||
@@ -140,58 +146,34 @@ func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, instal
|
||||
action := value.(*FetchPackageAction)
|
||||
pkgInfo = action.DatabaseEntry.Info
|
||||
} else {
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
|
||||
resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
|
||||
resolved, unresolved := ResolveDependencies(pkgInfo, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir)
|
||||
|
||||
// Append unresolved dependencies
|
||||
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
|
||||
operation.UnresolvedDepends = removeDuplicates(operation.UnresolvedDepends)
|
||||
|
||||
for _, resolvedPkg := range resolved {
|
||||
if !operation.ActionsContainPackage(resolvedPkg.PkgName) && resolvedPkg.PkgName != pkgInfo.Name {
|
||||
if !reinstallDependencies && IsPackageInstalled(resolvedPkg.PkgName, operation.RootDir) {
|
||||
continue
|
||||
}
|
||||
entry, _, err := GetDatabaseEntry(resolvedPkg.PkgName)
|
||||
if err != nil {
|
||||
return errors.New("could not get database entry for package (" + resolvedPkg.PkgName + ")")
|
||||
}
|
||||
if !operation.ActionsContainPackage(resolvedPkg.DatabaseEntry.Info.Name) && resolvedPkg.DatabaseEntry.Info.Name != pkgInfo.Name {
|
||||
operation.InsertActionAt(pos, &FetchPackageAction{
|
||||
InstallationReason: resolvedPkg.InstallationReason,
|
||||
DatabaseEntry: entry,
|
||||
DatabaseEntry: resolvedPkg.DatabaseEntry,
|
||||
})
|
||||
|
||||
for _, vpkg := range resolvedPkg.DatabaseEntry.Info.Provides {
|
||||
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
|
||||
resolvedVirtualPackages[vpkg] = resolvedPkg.DatabaseEntry.Info.Name
|
||||
}
|
||||
}
|
||||
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
pos++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
@@ -202,11 +184,11 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
|
||||
}
|
||||
installedPackages := make([]*PackageInfo, len(installedPackageNames))
|
||||
for i, value := range installedPackageNames {
|
||||
bpmpkg := GetPackage(value, operation.RootDir)
|
||||
if bpmpkg == nil {
|
||||
pkgInfo := GetPackageInfo(value, operation.RootDir)
|
||||
if pkgInfo == nil {
|
||||
return errors.New("could not find installed package (" + value + ")")
|
||||
}
|
||||
installedPackages[i] = bpmpkg.PkgInfo
|
||||
installedPackages[i] = pkgInfo
|
||||
}
|
||||
|
||||
// Get packages to remove
|
||||
@@ -217,34 +199,70 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Get manually installed packages, resolve all their dependencies and add them to the keepPackages slice
|
||||
keepPackages := make([]string, 0)
|
||||
for _, pkg := range slices.Clone(installedPackages) {
|
||||
if GetInstallationReason(pkg.Name, operation.RootDir) != InstallationReasonManual {
|
||||
// Run BFS on all manually installed packages
|
||||
visited := make([]string, 0)
|
||||
for _, pkg := range installedPackages {
|
||||
if getPackageLocalInfo(pkg.Name, operation.RootDir).GetInstallationReason() != InstallationReasonManual {
|
||||
continue
|
||||
}
|
||||
|
||||
// Do not resolve dependencies or add package to keepPackages slice if package removal action exists for it
|
||||
if _, ok := removeActions[pkg.Name]; ok {
|
||||
queue := make([]*PackageInfo, 0)
|
||||
|
||||
queue = append(queue, pkg)
|
||||
|
||||
for len(queue) > 0 {
|
||||
v := queue[len(queue)-1]
|
||||
queue = queue[:len(queue)-1]
|
||||
|
||||
// Skip package if it's to be removed
|
||||
if _, ok := removeActions[v.Name]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
keepPackages = append(keepPackages, pkg.Name)
|
||||
resolved := pkg.GetDependenciesRecursive(!cleanupMakeDepends, operation.RootDir)
|
||||
for _, value := range resolved {
|
||||
if !slices.Contains(keepPackages, value) && !slices.Contains(MainBPMConfig.IgnorePackages, value) {
|
||||
keepPackages = append(keepPackages, value)
|
||||
// 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 {
|
||||
// Do not add package removal action if there already is one
|
||||
if _, ok := removeActions[pkg]; ok {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(keepPackages, pkg) {
|
||||
|
||||
if !slices.Contains(visited, pkg) {
|
||||
bpmpkg := GetPackage(pkg, operation.RootDir)
|
||||
if bpmpkg == nil {
|
||||
return errors.New("Error: could not find installed package (" + pkg + ")")
|
||||
@@ -483,24 +501,40 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
|
||||
}
|
||||
|
||||
for _, depend := range pkgInfo.OptionalDepends {
|
||||
// Get optional dependency name
|
||||
dependSplit := strings.SplitN(depend, ":", 2)
|
||||
dependName, _, _ := SplitPkgNameAndVersion(dependSplit[0])
|
||||
|
||||
// Skip if dependency is already installed
|
||||
if IsPackageInstalled(depend, operation.RootDir) {
|
||||
if IsPackageInstalled(dependName, operation.RootDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RunHooks(verbose bool) error {
|
||||
func (operation *BPMOperation) RunPreHooks(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
|
||||
@@ -520,7 +554,46 @@ func (operation *BPMOperation) RunHooks(verbose bool) error {
|
||||
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 {
|
||||
log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err)
|
||||
continue
|
||||
@@ -604,9 +677,34 @@ func (operation *BPMOperation) FetchPackages() (err error) {
|
||||
}
|
||||
|
||||
operation.hasFetchedPackages = true
|
||||
|
||||
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) {
|
||||
// Fetch packages
|
||||
if !operation.hasFetchedPackages {
|
||||
@@ -670,7 +768,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
|
||||
|
||||
// Compile source package if not compiled already
|
||||
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 {
|
||||
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
|
||||
}
|
||||
@@ -690,22 +788,15 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
|
||||
}
|
||||
|
||||
if value.InstallationReason != InstallationReasonManual {
|
||||
err = installPackage(fileToInstall, operation.RootDir, verbose, true)
|
||||
err = installPackage(fileToInstall, value.InstallationReason, operation.RootDir, verbose, true)
|
||||
} else {
|
||||
err = installPackage(fileToInstall, operation.RootDir, verbose, force)
|
||||
err = installPackage(fileToInstall, value.InstallationReason, operation.RootDir, verbose, force)
|
||||
}
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
+239
-90
@@ -10,12 +10,14 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -23,6 +25,7 @@ import (
|
||||
type BPMPackage struct {
|
||||
PkgInfo *PackageInfo
|
||||
PkgFiles []*PackageFileEntry
|
||||
LocalInfo PackageLocalInfo
|
||||
}
|
||||
|
||||
type PackageInfo struct {
|
||||
@@ -32,13 +35,16 @@ type PackageInfo struct {
|
||||
Revision int `yaml:"revision,omitempty"`
|
||||
Url string `yaml:"url,omitempty"`
|
||||
License string `yaml:"license,omitempty"`
|
||||
Maintainers []string `yaml:"maintainers,omitempty"`
|
||||
Arch string `yaml:"architecture,omitempty"`
|
||||
OutputArch string `yaml:"output_architecture,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Keep []string `yaml:"keep,omitempty"`
|
||||
Depends []string `yaml:"depends,omitempty"`
|
||||
RuntimeDepends []string `yaml:"runtime_depends,omitempty"`
|
||||
OptionalDepends []string `yaml:"optional_depends,omitempty"`
|
||||
MakeDepends []string `yaml:"make_depends,omitempty"`
|
||||
CheckDepends []string `yaml:"check_depends,omitempty"`
|
||||
Conflicts []string `yaml:"conflicts,omitempty"`
|
||||
Replaces []string `yaml:"replaces,omitempty"`
|
||||
Provides []string `yaml:"provides,omitempty"`
|
||||
@@ -72,6 +78,12 @@ type PackageFileEntry struct {
|
||||
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 {
|
||||
var totalSize int64 = 0
|
||||
for _, entry := range pkg.PkgFiles {
|
||||
@@ -120,35 +132,17 @@ const (
|
||||
InstallationReasonUnknown InstallationReason = "unknown"
|
||||
)
|
||||
|
||||
func GetInstallationReason(pkg, rootDir string) InstallationReason {
|
||||
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
|
||||
pkgDir := path.Join(installedDir, pkg)
|
||||
if stat, err := os.Stat(path.Join(pkgDir, "installation_reason")); err != nil || stat.IsDir() {
|
||||
func (localInfo PackageLocalInfo) GetInstallationReason() InstallationReason {
|
||||
switch localInfo.InstallationReason {
|
||||
case "manual":
|
||||
return InstallationReasonManual
|
||||
}
|
||||
b, err := os.ReadFile(path.Join(pkgDir, "installation_reason"))
|
||||
if err != nil {
|
||||
return InstallationReasonUnknown
|
||||
}
|
||||
reason := strings.TrimSpace(string(b))
|
||||
if reason == "manual" {
|
||||
return InstallationReasonManual
|
||||
} else if reason == "dependency" {
|
||||
case "dependency":
|
||||
return InstallationReasonDependency
|
||||
} else if reason == "make_dependency" {
|
||||
case "make_dependency":
|
||||
return InstallationReasonMakeDependency
|
||||
}
|
||||
default:
|
||||
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) {
|
||||
@@ -457,6 +451,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
|
||||
OutputArch: GetArch(),
|
||||
Keep: make([]string, 0),
|
||||
Depends: make([]string, 0),
|
||||
RuntimeDepends: make([]string, 0),
|
||||
MakeDepends: make([]string, 0),
|
||||
OptionalDepends: make([]string, 0),
|
||||
Conflicts: make([]string, 0),
|
||||
@@ -537,56 +532,122 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
|
||||
}
|
||||
|
||||
func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
|
||||
ret := make([]string, 0)
|
||||
appendArray := func(label string, array []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 {
|
||||
return
|
||||
}
|
||||
|
||||
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
|
||||
// Sort array
|
||||
if sort {
|
||||
slices.Sort(array)
|
||||
}
|
||||
|
||||
ret = append(ret, "Name: "+pkgInfo.Name)
|
||||
ret = append(ret, "Description: "+pkgInfo.Description)
|
||||
ret = append(ret, "Version: "+pkgInfo.GetFullVersion())
|
||||
if pkgInfo.Url != "" {
|
||||
ret = append(ret, "URL: "+pkgInfo.Url)
|
||||
builder.WriteString(label + " (" + strconv.Itoa(len(array)) + "):\n")
|
||||
for _, val := range array {
|
||||
builder.WriteString(" - " + val + "\n")
|
||||
}
|
||||
if pkgInfo.License != "" {
|
||||
ret = append(ret, "License: "+pkgInfo.License)
|
||||
}
|
||||
ret = append(ret, "Architecture: "+pkgInfo.Arch)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// Main information
|
||||
builder.WriteString("Name: " + pkgInfo.Name + "\n")
|
||||
builder.WriteString("Description: " + pkgInfo.Description + "\n")
|
||||
builder.WriteString("Version: " + pkgInfo.GetFullVersion() + "\n")
|
||||
builderWriteStringNotEmpty("URL", pkgInfo.Url)
|
||||
builderWriteStringNotEmpty("License", pkgInfo.License)
|
||||
builderWriteArray("Maintainers", pkgInfo.Maintainers, false)
|
||||
builder.WriteString("Architecture: " + pkgInfo.Arch + "\n")
|
||||
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)
|
||||
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)
|
||||
builder.WriteString("Type: " + pkgInfo.Type + "\n")
|
||||
|
||||
// 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 {
|
||||
splitPkgs := make([]string, len(pkgInfo.SplitPackages))
|
||||
for i, splitPkgInfo := range pkgInfo.SplitPackages {
|
||||
splitPkgs[i] = splitPkgInfo.Name
|
||||
}
|
||||
appendArray("Split Packages", splitPkgs)
|
||||
builderWriteArray("Split packages", splitPkgs, true)
|
||||
}
|
||||
|
||||
// Installation reason
|
||||
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
|
||||
installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
|
||||
installationReason := GetPackage(pkgInfo.Name, rootDir).LocalInfo.GetInstallationReason()
|
||||
var installationReasonString string
|
||||
switch installationReason {
|
||||
case InstallationReasonManual:
|
||||
@@ -598,10 +659,10 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
|
||||
default:
|
||||
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 {
|
||||
@@ -638,6 +699,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
extractFilename := path.Join(rootDir, header.Name)
|
||||
switch header.Typeflag {
|
||||
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
|
||||
}); ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping Directory: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := os.Stat(extractFilename); err == nil {
|
||||
if verbose {
|
||||
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)
|
||||
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
|
||||
}); ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping File: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
skip := false
|
||||
if _, err := os.Stat(extractFilename); err == nil {
|
||||
for _, k := range bpmpkg.PkgInfo.Keep {
|
||||
@@ -722,6 +805,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
}
|
||||
bar.Add64(header.Size)
|
||||
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
|
||||
}); ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping Symlink: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err := os.Remove(extractFilename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
@@ -737,6 +831,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
|
||||
}
|
||||
bar.Add64(header.Size)
|
||||
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
|
||||
}); ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping Hard Link: %s (Path was ignored)\n", extractFilename)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if verbose {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
for _, entry := range fileEntries {
|
||||
file := path.Join(rootDir, entry.Path)
|
||||
stat, err := os.Lstat(file)
|
||||
finalPath := path.Join(rootDir, entry.Path)
|
||||
|
||||
stat, err := os.Lstat(finalPath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
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
|
||||
}); ok {
|
||||
if verbose {
|
||||
fmt.Println("Skipping path: " + file + " (Path is managed by multiple packages)")
|
||||
fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(files[entry.Path]) != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Skipping path: " + finalPath + " (Path is managed by multiple packages)")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
shouldContinue := false
|
||||
for _, value := range bpmpkg.PkgInfo.Keep {
|
||||
if strings.HasSuffix(value, "/") {
|
||||
if strings.HasPrefix(entry.Path, value) || entry.Path == strings.TrimSuffix(value, "/") {
|
||||
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
|
||||
continue
|
||||
@@ -839,7 +957,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
} else {
|
||||
if entry.Path == value {
|
||||
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
|
||||
continue
|
||||
@@ -851,37 +969,37 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
}
|
||||
if stat.Mode()&os.ModeSymlink != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if stat.IsDir() {
|
||||
dir, err := os.ReadDir(file)
|
||||
dir, err := os.ReadDir(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dir) != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Skipping non-empty directory: " + file)
|
||||
fmt.Println("Skipping non-empty directory: " + finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err = os.Remove(file)
|
||||
err = os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -949,6 +1067,16 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
|
||||
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
|
||||
packageScripts, err := ReadPackageScripts(filename)
|
||||
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
|
||||
err = initializeLocalPackageInformation(rootDir)
|
||||
err = InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1033,31 +1169,44 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
|
||||
// Removing package files
|
||||
for _, entry := range fileEntries {
|
||||
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) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
} else if err != nil {
|
||||
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
|
||||
}); ok {
|
||||
if verbose {
|
||||
fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(files[entry.Path]) != 0 {
|
||||
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
|
||||
}
|
||||
if lstat.Mode()&os.ModeSymlink != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
stat, err := os.Stat(file)
|
||||
stat, err := os.Stat(finalPath)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
@@ -1065,28 +1214,28 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
dir, err := os.ReadDir(file)
|
||||
dir, err := os.ReadDir(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dir) != 0 {
|
||||
if verbose {
|
||||
fmt.Println("Skipping non-empty directory: " + file)
|
||||
fmt.Println("Skipping non-empty directory: " + finalPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err = os.Remove(file)
|
||||
err = os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if verbose {
|
||||
fmt.Println("Removing: " + file)
|
||||
fmt.Println("Removing: " + finalPath)
|
||||
}
|
||||
err := os.Remove(file)
|
||||
err := os.Remove(finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1109,7 +1258,7 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
|
||||
}
|
||||
|
||||
// Ensure local package information has been initialized for rootDir
|
||||
err = initializeLocalPackageInformation(rootDir)
|
||||
err = InitializeLocalPackageInformation(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+4
-4
@@ -19,14 +19,14 @@ type BPMLock struct {
|
||||
}
|
||||
|
||||
func LockBPM(rootDir string) (*BPMLock, error) {
|
||||
// Create parent directories if they don't already exist
|
||||
err := os.MkdirAll(path.Join(rootDir, "/var/lib/bpm"), 0755)
|
||||
// Create cache directory if it doesn't already exist
|
||||
err := os.MkdirAll(path.Join(rootDir, "/var/cache/bpm"), 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func LockBPM(rootDir string) (*BPMLock, error) {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user