23 Commits
Author SHA1 Message Date
EnumDev ad89650dc4 Sort packages by name correctly in 'list' subcommand 2026-02-05 18:08:34 +02:00
EnumDev 3a7adb27ab Replace 'human-readable' flag with 'show-bytes' 2026-02-05 18:02:02 +02:00
EnumDev f6aa01339e Show array length in readable info 2026-02-05 17:57:11 +02:00
EnumDev a29de2e52d Show virtual package providers in readable info 2026-02-05 17:52:40 +02:00
EnumDev 619335bbe6 Replace ResolveVirtualPackage function with GetDatabaseVirtualPackageEntry 2026-02-05 14:54:08 +02:00
EnumDev 8579c688d7 Use BFS for finding unused dependencies 2026-02-05 14:22:16 +02:00
EnumDev 48cec35f0c Cache installed virtual packages 2026-02-05 13:21:50 +02:00
EnumDev e19d64bf04 Replace an instance of GetPackage with GetPackageInfo 2026-02-04 14:50:25 +02:00
EnumDev ee80640153 Move installation reason to package local info 2026-02-04 14:48:52 +02:00
EnumDev e4d94e378f Move bpm locks to var/cache/bpm 2026-02-04 13:03:52 +02:00
EnumDev b37360ed82 Add package installation and last update dates 2026-02-03 21:25:33 +02:00
EnumDev ba0d288e45 Add check dependencies 2026-01-28 18:50:25 +02:00
EnumDev c1fa9d9474 Allow skipping tests during compilation in 'install' and 'update' subcommands 2026-01-28 13:02:37 +02:00
EnumDev 0742c81f2f Fix file ownership issues with package compilation as the root user 2026-01-28 12:33:26 +02:00
EnumDev dd20574470 More improvements for the readable info functions 2026-01-27 12:47:25 +02:00
EnumDev b3dc6e3e34 Add optional dependency descriptions 2026-01-26 13:25:25 +02:00
EnumDev 0a06808303 Improve readable info functions 2026-01-25 19:55:09 +02:00
EnumDev 9f8a20c671 Show arrays in multiple lines in readable info 2026-01-25 19:23:39 +02:00
EnumDev 2072e3856f Add 'maintainers' field to pkg.info 2026-01-25 17:23:53 +02:00
EnumDev 72f574209f Keep track of resolved virtual packages during dependency resolution 2026-01-22 12:35:08 +02:00
EnumDev 9c2d4595c7 Add 'jobs' flag to the 'compile' and 'install' subcommands 2026-01-19 17:42:47 +02:00
EnumDev 72f17f8954 Rework runtime dependency system 2026-01-06 19:35:42 +02:00
EnumDev fd646a820d Add 'runtime_depends' field to package info 2026-01-04 14:40:55 +02:00
10 changed files with 815 additions and 255 deletions
+146 -23
View File
@@ -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,13 @@ 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.Bool("runtime", true, "Install all runtime dependencies")
currentFlagSet.BoolP("optional", "o", false, "Install all optional 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()
@@ -140,6 +144,8 @@ func main() {
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()
@@ -162,6 +168,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 +177,18 @@ func main() {
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Compare two version numbers", os.Args[2:])
compareVersions()
case "upgrade-persistent-data":
currentFlagSet = flag.NewFlagSet("upgrade-persistent-data", flag.ExitOnError)
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Upgrade BPM's persistent data directory contents", os.Args[2:])
rootDir, _ := currentFlagSet.GetString("root")
err = bpmlib.UpgradePersistentData(rootDir)
if err != nil {
log.Printf("Error: could not upgrade persistent data directory: %s", err)
exitCode = 1
return
}
default:
printUsage()
exitCode = 1
@@ -184,7 +203,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 +221,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 +234,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 +246,7 @@ func showPackageInfo() {
if n != 0 {
fmt.Println()
}
fmt.Println(entry.CreateReadableInfo(rootDir, showHumanReadableSize))
fmt.Println(entry.CreateReadableInfo(rootDir, showBytes))
return
}
@@ -233,8 +262,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 +287,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 +315,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 +323,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
@@ -325,6 +370,15 @@ func showPackageList() {
switch sortPackages {
case "", "name":
slices.SortFunc(installedPackages, func(a, b struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}) int {
return strings.Compare(a.pkgInfo.Name, b.pkgInfo.Name)
})
slices.SortFunc(databaseEntries, func(a, b *bpmlib.BPMDatabaseEntry) int {
return strings.Compare(a.Info.Name, b.Info.Name)
})
case "size":
slices.SortFunc(installedPackages, func(a, b struct {
pkgInfo bpmlib.PackageInfo
@@ -359,7 +413,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 +437,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 +445,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 +462,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 +542,13 @@ func installPackages() {
verbose, _ := currentFlagSet.GetBool("verbose")
force, _ := currentFlagSet.GetBool("force")
yesAll, _ := currentFlagSet.GetBool("yes")
installRuntime, _ := currentFlagSet.GetBool("runtime")
installOptional, _ := currentFlagSet.GetBool("optional")
installationReason, _ := currentFlagSet.GetString("installation-reason")
reinstall, _ := currentFlagSet.GetBool("reinstall")
reinstallAll, _ := currentFlagSet.GetBool("reinstall-all")
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
compilationJobs, _ := currentFlagSet.GetInt("jobs")
// Get packages
packages := currentFlagSet.Args()
@@ -534,6 +599,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 +616,7 @@ func installPackages() {
}
// Create installation operation
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, installOptional, force, verbose, packages...)
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, installRuntime, installOptional, force, !skipChecks, verbose, packages...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err)
exitCode = 1
@@ -554,6 +627,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")
@@ -664,6 +740,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 {
@@ -777,6 +861,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)
@@ -898,6 +990,8 @@ func updatePackages() {
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 +1009,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 +1037,7 @@ func updatePackages() {
}
// Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, installOptional, force, verbose)
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, installOptional, force, !skipChecks, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err)
exitCode = 1
@@ -946,6 +1048,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")
@@ -1033,6 +1138,14 @@ func getFileOwner() {
// 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 {
@@ -1124,6 +1237,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 +1255,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,9 +1285,9 @@ func compilePackage() {
return
}
// Get direct runtime and make dependencies
// Get direct common and make dependencies
totalDepends := make([]string, 0)
for _, depend := range bpmpkg.PkgInfo.GetDependencies(true, false) {
for _, depend := range bpmpkg.PkgInfo.GetDependencies(true, !skipChecks, false, false) {
if !slices.Contains(totalDepends, depend.PkgName) {
totalDepends = append(totalDepends, depend.PkgName)
}
@@ -1182,7 +1304,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 +1320,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 +1439,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()
@@ -1374,6 +1496,7 @@ func printUsage() {
fmt.Println(" o, owner Show what packages own the specified paths")
fmt.Println(" c, compile Compile source packages and convert them to binary ones")
fmt.Println(" p, vercmp Compare package version numbers")
}
func setupFlagsAndHelp(flagset *flag.FlagSet, usage, desc string, args []string) {
+63 -1
View File
@@ -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)
}
+1
View File
@@ -21,6 +21,7 @@ type configDatabase struct {
type CompilationBPMConfigStruct struct {
PrivilegeEscalatorCmd string `yaml:"privilege_escalator_cmd"`
CompilationJobs int `yaml:"compilation_jobs"`
CompilationEnvironment []string `yaml:"compilation_env"`
}
+114 -51
View File
@@ -20,7 +20,7 @@ import (
type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"`
Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
VirtualPackages map[string][]string
VirtualPackages map[string][]*BPMDatabaseEntry
Name string
Source string
}
@@ -59,7 +59,7 @@ func (db *configDatabase) ReadLocalDatabase() error {
}
// Initialize struct values
database.VirtualPackages = make(map[string][]string)
database.VirtualPackages = make(map[string][]*BPMDatabaseEntry)
database.Name = db.Name
database.Source = db.Source
@@ -105,13 +105,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 +231,12 @@ 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
return providers
}
func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
@@ -297,7 +294,9 @@ 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 {
return strings.SplitN(n, ":", 2)[0] == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
}
@@ -322,63 +321,124 @@ 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) 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)
// 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 +450,20 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableS
default:
installationReasonString = "Unknown"
}
ret = append(ret, "Installation Reason: "+installationReasonString)
builder.WriteString("Installation reason: " + installationReasonString + "\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())
}
+88 -18
View File
@@ -3,6 +3,7 @@ package bpmlib
import (
"fmt"
"slices"
"strings"
)
type pkgInstallationReason struct {
@@ -10,7 +11,7 @@ type pkgInstallationReason struct {
InstallationReason InstallationReason
}
func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalDepends bool) []pkgInstallationReason {
func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeCheckDepends, includeRuntimeDepends, includeOptionalDepends bool) []pkgInstallationReason {
allDepends := make([]pkgInstallationReason, 0)
for _, depend := range pkgInfo.Depends {
@@ -25,6 +26,7 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalD
}
if includeOptionalDepends {
for _, depend := range pkgInfo.OptionalDepends {
depend = strings.SplitN(depend, ":", 2)[0]
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend
}) {
@@ -35,6 +37,18 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalD
}
}
}
if includeRuntimeDepends {
for _, depend := range pkgInfo.RuntimeDepends {
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend
}) {
allDepends = append(allDepends, pkgInstallationReason{
PkgName: depend,
InstallationReason: InstallationReasonDependency,
})
}
}
}
if includeMakeDepends {
for _, depend := range pkgInfo.MakeDepends {
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
@@ -47,6 +61,18 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalD
}
}
}
if includeCheckDepends {
for _, depend := range pkgInfo.CheckDepends {
if !slices.ContainsFunc(allDepends, func(p pkgInstallationReason) bool {
return p.PkgName == depend
}) {
allDepends = append(allDepends, pkgInstallationReason{
PkgName: depend,
InstallationReason: InstallationReasonMakeDependency,
})
}
}
}
// Skip ignored packages
allDepends = slices.DeleteFunc(allDepends, func(depend pkgInstallationReason) bool {
@@ -56,27 +82,27 @@ func (pkgInfo *PackageInfo) GetDependencies(includeMakeDepends, includeOptionalD
return allDepends
}
func (pkgInfo *PackageInfo) GetDependenciesRecursive(includeMakeDepends bool, rootDir string) (resolved []string) {
func (pkgInfo *PackageInfo) GetDependenciesRecursive(includeRuntimeDepends, includeCheckDepends, 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)
pkgInfo.getDependenciesRecursive(&resolved, &unresolved, includeRuntimeDepends, includeMakeDepends, includeCheckDepends, rootDir)
return resolved
}
func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresolved *[]string, includeMakeDepends bool, rootDir string) {
func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresolved *[]string, includeRuntimeDepends, includeMakeDepends, includeCheckDepends bool, rootDir string) {
// Add current package name to unresolved slice
*unresolved = append(*unresolved, pkgInfo.Name)
// Loop through all dependencies
for _, pkgIR := range pkgInfo.GetDependencies(includeMakeDepends, false) {
for _, pkgIR := range pkgInfo.GetDependencies(includeMakeDepends, includeCheckDepends, includeRuntimeDepends, false) {
depend := pkgIR.PkgName
if isVirtual, p := IsVirtualPackage(depend, rootDir); isVirtual {
depend = p
if providers := GetVirtualPackageInfo(depend, rootDir); len(providers) > 0 {
depend = providers[0].Name
}
if !slices.Contains(*resolved, depend) {
@@ -91,7 +117,7 @@ func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresol
dependInfo := GetPackageInfo(depend, rootDir)
if dependInfo != nil {
dependInfo.getDependenciesRecursive(resolved, unresolved, includeMakeDepends, rootDir)
dependInfo.getDependenciesRecursive(resolved, unresolved, includeRuntimeDepends, includeMakeDepends, includeCheckDepends, rootDir)
}
}
}
@@ -101,13 +127,16 @@ func (pkgInfo *PackageInfo) getDependenciesRecursive(resolved *[]string, unresol
*unresolved = stringSliceRemove(*unresolved, pkgInfo.Name)
}
func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) (resolved []pkgInstallationReason, unresolved []string) {
// Initialize slices
func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, resolvedVirtualPkgs map[string]string, checkMake, checkCheck, checkRuntime, checkOptional, ignoreInstalled, verbose bool, rootDir string) (resolved []pkgInstallationReason, unresolved []string) {
// Initialize slices and maps
resolved = make([]pkgInstallationReason, 0)
unresolved = make([]string, 0)
if resolvedVirtualPkgs == nil {
resolvedVirtualPkgs = make(map[string]string)
}
// Call unexported function
resolvePackageDependenciesFromDatabase(&resolved, &unresolved, pkgInfo, checkMake, checkOptional, ignoreInstalled, verbose, rootDir)
resolvePackageDependenciesFromDatabase(&resolved, &unresolved, resolvedVirtualPkgs, pkgInfo, checkMake, checkCheck, checkRuntime, checkOptional, ignoreInstalled, verbose, rootDir)
// Remove main package from unresolved slice
unresolved = stringSliceRemove(unresolved, pkgInfo.Name)
@@ -115,12 +144,18 @@ func ResolveAllPackageDependenciesFromDatabases(pkgInfo *PackageInfo, checkMake,
return resolved, unresolved
}
func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, unresolved *[]string, pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled, verbose bool, rootDir string) {
func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, unresolved *[]string, resolvedVirtualPkgs map[string]string, pkgInfo *PackageInfo, checkMake, checkCheck, checkRuntime, checkOptional, ignoreInstalled, verbose bool, rootDir string) {
// Add current package name to unresolved slice
*unresolved = append(*unresolved, pkgInfo.Name)
for _, vpkg := range pkgInfo.Provides {
if _, ok := resolvedVirtualPkgs[vpkg]; !ok {
resolvedVirtualPkgs[vpkg] = pkgInfo.Name
}
}
// Loop through all dependencies
for _, pkgIR := range pkgInfo.GetDependencies(pkgInfo.Type == "source", checkOptional) {
for _, pkgIR := range pkgInfo.GetDependencies(pkgInfo.Type == "source", pkgInfo.Type == "source" && checkCheck, checkRuntime, checkOptional) {
// Skip dependency if it has already been resolved
if slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
return p.PkgName == pkgIR.PkgName
@@ -142,7 +177,7 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
}
// Skip dependency if it is already installed or provided
if ignoreInstalled && IsPackageProvided(pkgIR.PkgName, rootDir) {
if providers := GetVirtualPackageInfo(pkgIR.PkgName, rootDir); ignoreInstalled && len(providers) > 0 {
continue
}
@@ -151,7 +186,26 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
var entry *BPMDatabaseEntry
entry, _, err = GetDatabaseEntry(pkgIR.PkgName)
if err != nil {
if entry = ResolveVirtualPackage(pkgIR.PkgName); entry == nil {
if resolvedVirtualPkg, ok := resolvedVirtualPkgs[pkgIR.PkgName]; ok {
// Virtual package already resolved
// Move dependency from the unresolved slice to the resolved slice
if !slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
return p.PkgName == resolvedVirtualPkg
}) {
*resolved = append(*resolved, pkgInstallationReason{
PkgName: resolvedVirtualPkg,
InstallationReason: pkgIR.InstallationReason,
})
}
*unresolved = stringSliceRemove(*unresolved, resolvedVirtualPkg)
continue
} else if providers := GetDatabaseVirtualPackageEntry(pkgIR.PkgName); len(providers) > 0 {
// Virtual package found in database
entry = providers[0]
} else {
// Virtual package not found
if !slices.Contains(*unresolved, pkgIR.PkgName) {
*unresolved = append(*unresolved, pkgIR.PkgName)
}
@@ -160,7 +214,7 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
}
// Resolve the dependencies of this dependency
resolvePackageDependenciesFromDatabase(resolved, unresolved, entry.Info, checkMake, false, ignoreInstalled, verbose, rootDir)
resolvePackageDependenciesFromDatabase(resolved, unresolved, resolvedVirtualPkgs, entry.Info, checkMake, checkCheck, checkRuntime, false, ignoreInstalled, verbose, rootDir)
// Move dependency from the unresolved slice to the resolved slice
if !slices.ContainsFunc(*resolved, func(p pkgInstallationReason) bool {
@@ -197,6 +251,14 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
continue
}
// Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
}
// Loop through each virtual package
for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its dependencies contain a provided virtual package
@@ -206,6 +268,14 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
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 {
return n == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
}
}
}
@@ -228,7 +298,7 @@ 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 {
return n == pkgInfo.Name
return strings.SplitN(n, ":", 2)[0] == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
@@ -238,7 +308,7 @@ 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 {
return n == vpkg
return strings.SplitN(n, ":", 2)[0] == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
+16 -14
View File
@@ -20,12 +20,13 @@ const (
)
// InstallPackages installs the specified packages into the given root directory by fetching them from databases or directly from local bpm archives
func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallMethod ReinstallMethod, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) {
func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallMethod ReinstallMethod, installRuntimeDependencies, installOptionalDependencies, forceInstallation, runChecks bool, verbose bool, packages ...string) (operation *BPMOperation, err error) {
// Setup operation struct
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir,
compiledPackages: make(map[string]string),
}
@@ -52,7 +53,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
}
@@ -76,7 +77,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
}
@@ -92,14 +93,14 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
if e, _, err := GetDatabaseEntry(pkg); err == nil {
entry = e
} else if isVirtual, p := IsVirtualPackage(pkg, rootDir); isVirtual {
entry, _, err = GetDatabaseEntry(p)
} else if providers := GetVirtualPackageInfo(pkg, 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(pkg); len(providers) > 0 {
entry = providers[0]
} else {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
@@ -112,7 +113,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,7 +132,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
// Resolve dependencies
err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installOptionalDependencies, verbose)
err = operation.ResolveDependencies(reinstallMethod == ReinstallMethodAll, installRuntimeDependencies, installOptionalDependencies, verbose)
if err != nil {
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
}
@@ -201,8 +202,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
@@ -365,7 +366,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 bool, allowDowngrades bool, installOptionalDependencies, forceInstallation, runChecks, verbose bool) (operation *BPMOperation, err error) {
// Sync databases
if syncDatabase {
err := SyncDatabase(verbose)
@@ -397,6 +398,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir,
compiledPackages: make(map[string]string),
}
@@ -421,7 +423,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
operation.AppendAction(&FetchPackageAction{
InstallationReason: GetInstallationReason(pkg, rootDir),
InstallationReason: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(),
DatabaseEntry: entry,
})
}
@@ -429,7 +431,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
}
// Check for new dependencies in updated packages
err = operation.ResolveDependencies(false, installOptionalDependencies, verbose)
err = operation.ResolveDependencies(false, true, installOptionalDependencies, verbose)
if err != nil {
return nil, fmt.Errorf("could not resolve dependencies: %s", err)
}
+165 -42
View File
@@ -7,20 +7,42 @@ import (
"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 +73,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
}
@@ -77,7 +105,7 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
func IsPackageInstalled(pkg, rootDir string) bool {
// Initialize local package information
err := initializeLocalPackageInformation(rootDir)
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return false
}
@@ -88,48 +116,17 @@ 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
return installedVirtualPackages[rootDir][vpkg]
}
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
err := initializeLocalPackageInformation(rootDir)
err := InitializeLocalPackageInformation(rootDir)
if err != nil {
return nil
}
@@ -144,10 +141,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 +233,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
}
+68 -30
View File
@@ -15,6 +15,8 @@ type BPMOperation struct {
Actions []OperationAction
UnresolvedDepends []string
Changes map[string]string
CompilationJobs int
RunChecks bool
RootDir string
compiledPackages map[string]string
@@ -129,8 +131,9 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
return ret
}
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installOptionalDependencies, verbose bool) error {
func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, installRuntimeDependencies, installOptionalDependencies, verbose bool) error {
pos := 0
resolvedVirtualPkgs := make(map[string]string, 0)
for _, value := range slices.Clone(operation.Actions) {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
@@ -144,7 +147,7 @@ func (operation *BPMOperation) ResolveDependencies(reinstallDependencies, instal
continue
}
resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, pkgInfo.Type == "source", installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
resolved, unresolved := ResolveAllPackageDependenciesFromDatabases(pkgInfo, resolvedVirtualPkgs, pkgInfo.Type == "source", pkgInfo.Type == "source" && operation.RunChecks, installRuntimeDependencies, installOptionalDependencies, !reinstallDependencies, verbose, operation.RootDir)
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
@@ -202,11 +205,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 +220,67 @@ 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 {
depends = append(depends, v.MakeDepends...)
depends = append(depends, v.CheckDepends...)
}
// Loop through all dependencies
for _, depend := range depends {
// Resolve dependency
var dependPkgInfo *PackageInfo
if providers := GetVirtualPackageInfo(depend, operation.RootDir); len(providers) > 0 {
dependPkgInfo = providers[0]
} else {
dependPkgInfo = GetPackageInfo(depend, operation.RootDir)
}
if dependPkgInfo == nil {
continue
}
// Mark dependency as visited and add it to the queue
if !slices.Contains(visited, dependPkgInfo.Name) {
visited = append(visited, dependPkgInfo.Name)
queue = append(queue, dependPkgInfo)
}
}
}
}
// Get all installed packages that are not in the keepPackages slice and add them to the BPM operation
// Remove all packages that were not discovered after running BFS
for _, pkg := range installedPackageNames {
// 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,17 +519,25 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
}
for _, depend := range pkgInfo.OptionalDepends {
dependSplit := strings.SplitN(depend, ":", 2)
// Skip if dependency is already installed
if IsPackageInstalled(depend, operation.RootDir) {
if IsPackageInstalled(dependSplit[0], 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 {
return strings.SplitN(n, ":", 2)[0] == dependSplit[0]
}) {
continue
}
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], depend)
if len(dependSplit) == 2 {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", dependSplit[0], dependSplit[1]))
} else {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependSplit[0])
}
}
}
@@ -670,7 +714,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,19 +734,13 @@ 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!")
+138 -60
View File
@@ -16,6 +16,7 @@ import (
"strconv"
"strings"
"syscall"
"time"
"gopkg.in/yaml.v3"
)
@@ -23,6 +24,7 @@ import (
type BPMPackage struct {
PkgInfo *PackageInfo
PkgFiles []*PackageFileEntry
LocalInfo PackageLocalInfo
}
type PackageInfo struct {
@@ -32,13 +34,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 +77,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 +131,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 +450,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 +531,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), 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 +658,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 {
@@ -765,7 +825,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
}
@@ -949,6 +1009,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 +1058,16 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
}
}
// Write persistent data version number
if _, err := os.Stat(path.Join(rootDir, "var/lib/bpm/.version")); err != nil {
err = os.WriteFile(path.Join(rootDir, "var/lib/bpm/.version"), []byte(strconv.Itoa(persistentDataVersion)), 0644)
if err != nil {
return err
}
}
// Ensure local package information has been initialized for rootDir
err = initializeLocalPackageInformation(rootDir)
err = InitializeLocalPackageInformation(rootDir)
if err != nil {
return err
}
@@ -1109,7 +1187,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
View File
@@ -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 {