16 Commits
Author SHA1 Message Date
EnumDev f23537ba7d Fix typo 2026-01-03 13:46:43 +02:00
EnumDev 6e9f16837c Fix split packages with the same name as the main package being removed from the database 2026-01-02 20:08:10 +02:00
EnumDev 271e87cb77 Exit with exit code 1 if path is not owned by any package 2026-01-02 18:54:09 +02:00
EnumDev 25ccc208d7 Change module names 2026-01-01 10:20:33 +02:00
EnumDev 9c7e20d27d Remove need for config module 2026-01-01 10:15:23 +02:00
EnumDev 6206fdbf43 Add 'BPM_PKG_URL' variable in package compilation 2025-12-31 09:10:36 +02:00
EnumDev 9e48d97dc8 Add 'allow-downgrades' flag to 'update' subcommand 2025-12-28 18:30:17 +02:00
EnumDev 08a4e24889 Add confirmation prompt for syncing in 'update' subcomamnd 2025-12-26 13:05:25 +02:00
EnumDev 4a32df6335 Fix possible panic when updating packages 2025-12-26 13:02:58 +02:00
EnumDev 548b2476bd Reduce memory usage for the 'list' subcommand 2025-12-26 12:34:08 +02:00
EnumDev a660039be1 Use package info instead of BPMPackage for the local package information map 2025-12-26 11:41:07 +02:00
EnumDev 44c2678ef9 Show optional dependencies after install or update operation 2025-12-17 15:36:08 +02:00
EnumDev 2a7c6ce5d9 Count size in signed 64-bit integers 2025-12-17 13:43:23 +02:00
EnumDev f98760d1dd Rearrange function order in utils.go 2025-12-17 12:18:07 +02:00
EnumDev 2ba78f16db Remove duplicate strings in packages slice from install and remove operations 2025-12-17 12:16:45 +02:00
EnumDev 04a71b573b Improve conflict checking 2025-12-17 12:07:44 +02:00
13 changed files with 360 additions and 231 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ ROOT_COMPILATION_GID ?= 65534
build:
mkdir -p build
cd src/bpm; $(GO) build $(GOFLAGS) -ldflags "-w -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpm/config.BpmVersion=$(VERSION)' -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationUID=$(ROOT_COMPILATION_UID)' -X 'git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib.rootCompilationGID=$(ROOT_COMPILATION_GID)'" -o ../../build/bpm git.enumerated.dev/bubble-package-manager/bpm/src/bpm
cd src/bpm; $(GO) build $(GOFLAGS) -ldflags "-w -X 'main.BpmVersion=$(VERSION)' -X 'github.com/EnumeratedDev/bpm/src/bpmlib.rootCompilationUID=$(ROOT_COMPILATION_UID)' -X 'github.com/EnumeratedDev/bpm/src/bpmlib.rootCompilationGID=$(ROOT_COMPILATION_GID)'" -o ../../build/bpm bpm
install: build/bpm config/
# Create directory
-3
View File
@@ -1,3 +0,0 @@
package config
var BpmVersion = "dev"
+3 -3
View File
@@ -1,14 +1,14 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpm
module bpm
go 1.24.0
require (
git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
github.com/EnumeratedDev/bpm/src/bpmlib v0.0.0
github.com/lithammer/fuzzysearch v1.1.8
github.com/spf13/pflag v1.0.10
)
replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib
replace github.com/EnumeratedDev/bpm/src/bpmlib => ../bpmlib
require (
github.com/drone/envsubst v1.0.3 // indirect
+90 -19
View File
@@ -14,8 +14,7 @@ import (
"sort"
"strings"
"git.enumerated.dev/bubble-package-manager/bpm/src/bpm/config"
"git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib"
"github.com/EnumeratedDev/bpm/src/bpmlib"
"github.com/lithammer/fuzzysearch/fuzzy"
flag "github.com/spf13/pflag"
@@ -26,6 +25,8 @@ import (
/* A simple-to-use package manager */
/* ------------------------------------------------------- */
var BpmVersion = "dev"
var currentFlagSet *flag.FlagSet
var exitCode = 0
@@ -47,7 +48,7 @@ func main() {
switch subcommand {
case "v", "version":
fmt.Println("Bubble Package Manager (BPM)")
fmt.Println("Version: " + config.BpmVersion)
fmt.Println("Version: " + BpmVersion)
case "q", "query":
// Setup flags and help
currentFlagSet = flag.NewFlagSet("query", flag.ExitOnError)
@@ -137,6 +138,7 @@ func main() {
currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package update")
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")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Update installed packages", os.Args[2:])
@@ -254,7 +256,14 @@ func showPackageInfo() {
}
fmt.Println("File: " + abs)
}
fmt.Println(bpmpkg.CreateReadableInfo(rootDir, showHumanReadableSize))
fmt.Println(bpmpkg.PkgInfo.CreateReadableInfo(rootDir))
if bpmpkg.PkgInfo.Type == "binary" {
if !showHumanReadableSize {
fmt.Printf("Installed size: %d\n", bpmpkg.GetInstalledSize())
} else {
fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(bpmpkg.GetInstalledSize()))
}
}
}
}
@@ -292,9 +301,21 @@ func showPackageList() {
return
}
installedPackages := make([]*bpmlib.BPMPackage, len(installedPackageNames))
installedPackages := make([]struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}, len(installedPackageNames))
for i, pkgName := range installedPackageNames {
installedPackages[i] = bpmlib.GetPackage(pkgName, rootDir)
pkgInfo := *bpmlib.GetPackageInfo(pkgName, rootDir)
installedSize := bpmlib.GetPackage(pkgName, rootDir).GetInstalledSize()
installedPackages[i] = struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}{
pkgInfo: pkgInfo,
installedSize: installedSize,
}
}
databaseEntries := make([]*bpmlib.BPMDatabaseEntry, 0)
@@ -305,8 +326,11 @@ func showPackageList() {
switch sortPackages {
case "", "name":
case "size":
slices.SortFunc(installedPackages, func(a, b *bpmlib.BPMPackage) int {
return int(b.GetInstalledSize() - a.GetInstalledSize())
slices.SortFunc(installedPackages, func(a, b struct {
pkgInfo bpmlib.PackageInfo
installedSize int64
}) int {
return int(b.installedSize - a.installedSize)
})
slices.SortFunc(databaseEntries, func(a, b *bpmlib.BPMDatabaseEntry) int {
return int(b.InstalledSize - a.InstalledSize)
@@ -335,7 +359,7 @@ func showPackageList() {
}
} else {
for _, pkg := range installedPackages {
installationReason := bpmlib.GetInstallationReason(pkg.PkgInfo.Name, rootDir)
installationReason := bpmlib.GetInstallationReason(pkg.pkgInfo.Name, rootDir)
if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
@@ -346,7 +370,7 @@ func showPackageList() {
continue
}
fmt.Println(pkg.PkgInfo.Name)
fmt.Println(pkg.pkgInfo.Name)
}
}
} else {
@@ -366,13 +390,8 @@ func showPackageList() {
fmt.Println("No packages have been installed")
return
}
for n, bpmpkg := range installedPackages {
if bpmpkg == nil {
fmt.Printf("Package (%s) could not be found\n", installedPackageNames[n])
continue
}
installationReason := bpmlib.GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir)
for n, pkg := range installedPackages {
installationReason := bpmlib.GetInstallationReason(pkg.pkgInfo.Name, rootDir)
if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
@@ -386,7 +405,15 @@ func showPackageList() {
if n != 0 {
fmt.Println()
}
fmt.Println(bpmpkg.CreateReadableInfo(rootDir, showHumanReadableSize))
fmt.Println(pkg.pkgInfo.CreateReadableInfo(rootDir))
if pkg.pkgInfo.Type == "binary" {
if !showHumanReadableSize {
fmt.Printf("Installed size: %d\n", pkg.installedSize)
} else {
fmt.Printf("Installed size: %s\n", bpmlib.BytesToHumanReadable(pkg.installedSize))
}
}
}
}
}
@@ -577,6 +604,9 @@ func installPackages() {
}
}
// Get optional dependencies
optionalDepends := operation.GetOptionalDependencies()
// Execute operation
err = operation.Execute(verbose, force)
if err != nil {
@@ -593,6 +623,18 @@ func installPackages() {
exitCode = 1
return
}
// Show optional dependencies
if !installOptional && len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends {
fmt.Printf("%s: \n", dependant)
for _, depend := range depends {
fmt.Printf(" - %s\n", depend)
}
}
}
}
func removePackages() {
@@ -854,6 +896,7 @@ func updatePackages() {
force, _ := currentFlagSet.GetBool("force")
yesAll, _ := currentFlagSet.GetBool("yes")
noSync, _ := currentFlagSet.GetBool("no-sync")
allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades")
installOptional, _ := currentFlagSet.GetBool("optional")
// Check for required permissions
@@ -882,14 +925,25 @@ func updatePackages() {
}
}
// Confirmation Prompt
if !noSync && !yesAll {
if !showConfirmationPrompt("Do you wish to sync all databases?", false) {
fmt.Println("Cancelling package update...")
exitCode = 1
return
}
}
// Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, installOptional, force, verbose)
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, installOptional, force, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err)
exitCode = 1
return
} else if err != nil {
log.Printf("Error: could not setup operation: %s\n", err)
exitCode = 1
return
}
// Exit if operation contains no actions
@@ -942,6 +996,9 @@ func updatePackages() {
}
}
// Get optional dependencies
optionalDepends := operation.GetOptionalDependencies()
// Execute operation
err = operation.Execute(verbose, force)
if err != nil {
@@ -958,6 +1015,18 @@ func updatePackages() {
exitCode = 1
return
}
// Show optional dependencies
if !installOptional && len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends {
fmt.Printf("%s: \n", dependant)
for _, depend := range depends {
fmt.Printf(" - %s\n", depend)
}
}
}
}
func getFileOwner() {
@@ -1034,6 +1103,8 @@ func getFileOwner() {
// Print packages
if len(pkgList) == 0 {
fmt.Printf("%s (%s) is not owned by any packages!\n", absPath, pathType)
exitCode = 1
return
} else {
fmt.Printf("%s (%s) is owned by the following packages:\n", absPath, pathType)
for _, pkg := range pkgList {
+9 -4
View File
@@ -137,6 +137,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks, k
env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name)
env = append(env, "BPM_PKG_VERSION="+bpmpkg.PkgInfo.Version)
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, CompilationBPMConfig.CompilationEnvironment...)
@@ -434,10 +435,12 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
// Replace variables
replaceVars := func(s string) string {
switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME":
return pkgInfo.Name
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_URL":
return pkgInfo.Url
case "BPM_SOURCE":
return path.Join(tempDirectory, "source/")
default:
@@ -557,10 +560,12 @@ func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string, verbose bo
gitBranch := download.GitBranch
gitBranch, err := envsubst.Eval(gitBranch, func(s string) string {
switch s {
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_NAME":
return pkgInfo.Name
case "BPM_PKG_VERSION":
return pkgInfo.Version
case "BPM_PKG_URL":
return pkgInfo.Url
default:
return ""
}
+6 -13
View File
@@ -28,8 +28,8 @@ type BPMDatabase struct {
type BPMDatabaseEntry struct {
Info *PackageInfo `yaml:"info"`
Filepath string `yaml:"filepath"`
DownloadSize uint64 `yaml:"download_size"`
InstalledSize uint64 `yaml:"installed_size"`
DownloadSize int64 `yaml:"download_size"`
InstalledSize int64 `yaml:"installed_size"`
Database *BPMDatabase
}
@@ -63,11 +63,12 @@ func (db *configDatabase) ReadLocalDatabase() error {
database.Name = db.Name
database.Source = db.Source
entriesToRemove := make([]string, 0)
for entryName, entry := range database.Entries {
entry.Database = database
if entry.Info.IsSplitPackage() {
delete(database.Entries, entryName)
// Handle split packages
for _, splitPkg := range entry.Info.SplitPackages {
// Turn split package into json data
@@ -106,9 +107,6 @@ func (db *configDatabase) ReadLocalDatabase() error {
for _, p := range splitPkg.Provides {
database.VirtualPackages[p] = append(database.VirtualPackages[p], splitPkg.Name)
}
// Add current entry to list for removal
entriesToRemove = append(entriesToRemove, entryName)
}
} else {
// Add virtual packages to database
@@ -118,11 +116,6 @@ func (db *configDatabase) ReadLocalDatabase() error {
}
}
// Remove entries
for _, entryName := range entriesToRemove {
delete(database.Entries, entryName)
}
BPMDatabases[db.Name] = database
return nil
@@ -400,10 +393,10 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableS
ret = append(ret, "Installation Reason: "+installationReasonString)
}
if entry.Info.Type == "binary" {
installedSize := int64(entry.InstalledSize)
installedSize := entry.InstalledSize
var installedSizeStr string
if humanReadableSize {
installedSizeStr = bytesToHumanReadable(installedSize)
installedSizeStr = BytesToHumanReadable(installedSize)
} else {
installedSizeStr = strconv.FormatInt(installedSize, 10)
}
+16 -28
View File
@@ -177,39 +177,33 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
// Get installed package names
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
pkgs, ok := localPackageInformation[rootDir]
if !ok {
return nil
}
// Loop through all installed packages
for _, installedPkgName := range pkgs {
// Get installed BPM package
installedPkg := GetPackage(installedPkgName, rootDir)
if installedPkg == nil {
return nil
}
for _, installedPkg := range pkgs {
// Skip iteration if comparing the same packages
if installedPkg.PkgInfo.Name == pkgInfo.Name {
if installedPkg.Name == pkgInfo.Name {
continue
}
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkgName)
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
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
return n == vpkg
}) {
dependants = append(dependants, installedPkgName)
dependants = append(dependants, installedPkg.Name)
break
}
}
@@ -220,39 +214,33 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (dependants []string) {
// Get installed package names
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
pkgs, ok := localPackageInformation[rootDir]
if !ok {
return nil
}
// Loop through all installed packages
for _, installedPkgName := range pkgs {
// Get installed BPM package
installedPkg := GetPackage(installedPkgName, rootDir)
if installedPkg == nil {
return nil
}
for _, installedPkg := range pkgs {
// Skip iteration if comparing the same packages
if installedPkg.PkgInfo.Name == pkgInfo.Name {
if installedPkg.Name == pkgInfo.Name {
continue
}
// Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool {
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkgName)
dependants = append(dependants, installedPkg.Name)
continue
}
// Loop through each virtual package
for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its optional dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool {
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return n == vpkg
}) {
dependants = append(dependants, installedPkgName)
dependants = append(dependants, installedPkg.Name)
break
}
}
+25 -8
View File
@@ -21,7 +21,6 @@ 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) {
// Setup operation struct
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
@@ -31,6 +30,9 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
compiledPackages: make(map[string]string),
}
// Remove duplicates from packages
packages = removeDuplicates(packages)
// Resolve packages
pkgsNotFound := make([]string, 0)
for _, pkg := range packages {
@@ -145,12 +147,9 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
operation.ReplaceObsoletePackages()
// Check for conflicts
conflicts, err := operation.CheckForConflicts()
if err != nil {
return nil, fmt.Errorf("could not complete package conflict check: %s", err)
}
conflicts := operation.CheckForConflicts()
if len(conflicts) > 0 {
err = nil
err = fmt.Errorf("conflicts detected")
for pkg, conflict := range conflicts {
err = errors.Join(err, PackageConflictErr{pkg, conflict})
}
@@ -196,6 +195,9 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
compiledPackages: make(map[string]string),
}
// Remove duplicates from packages
packages = removeDuplicates(packages)
// Search for packages
for _, pkg := range packages {
bpmpkg := GetPackage(pkg, rootDir)
@@ -363,7 +365,7 @@ func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackag
}
// UpdatePackages fetches the newest versions of all installed packages from
func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) {
func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) {
// Sync databases
if syncDatabase {
err := SyncDatabase(verbose)
@@ -417,7 +419,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
return nil, fmt.Errorf("could not get package info for package (%s)", pkg)
} else {
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if comparison > 0 {
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
operation.AppendAction(&FetchPackageAction{
InstallationReason: GetInstallationReason(pkg, rootDir),
DatabaseEntry: entry,
@@ -441,6 +443,21 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
// Replace obsolete packages
operation.ReplaceObsoletePackages()
// Check for conflicts
conflicts := operation.CheckForConflicts()
if len(conflicts) > 0 {
err = fmt.Errorf("conflicts detected")
for pkg, conflict := range conflicts {
err = errors.Join(err, PackageConflictErr{pkg, conflict})
}
if !forceInstallation {
return nil, err
} else {
log.Printf("Warning: %s", err)
}
}
return operation, nil
}
+1 -1
View File
@@ -1,4 +1,4 @@
module git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib
module github.com/EnumeratedDev/bpm/src/bpmlib
go 1.24.0
+22 -28
View File
@@ -9,7 +9,7 @@ import (
"strings"
)
var localPackageInformation map[string]map[string]*BPMPackage = make(map[string]map[string]*BPMPackage)
var localPackageInformation map[string]map[string]*PackageInfo = make(map[string]map[string]*PackageInfo)
func initializeLocalPackageInformation(rootDir string) (err error) {
// Return if information is already initialized
@@ -17,7 +17,7 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
return nil
}
tempPackageInformation := make(map[string]*BPMPackage)
tempPackageInformation := make(map[string]*PackageInfo)
// Get path to installed package information directory
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
@@ -25,7 +25,7 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
// Get directory content
items, err := os.ReadDir(installedDir)
if os.IsNotExist(err) {
localPackageInformation[rootDir] = make(map[string]*BPMPackage)
localPackageInformation[rootDir] = make(map[string]*PackageInfo)
return nil
}
if err != nil {
@@ -49,14 +49,8 @@ func initializeLocalPackageInformation(rootDir string) (err error) {
return err
}
// Read package files
files := getPackageFiles(info.Name, rootDir)
// Add package to slice
tempPackageInformation[info.Name] = &BPMPackage{
PkgInfo: info,
PkgFiles: files,
}
tempPackageInformation[info.Name] = info
}
localPackageInformation[rootDir] = tempPackageInformation
@@ -71,8 +65,8 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
}
// Loop through each package and add it to slice
for _, bpmpkg := range localPackageInformation[rootDir] {
ret = append(ret, bpmpkg.PkgInfo.Name)
for _, pkgInfo := range localPackageInformation[rootDir] {
ret = append(ret, pkgInfo.Name)
}
// Sort packages
@@ -94,18 +88,6 @@ func IsPackageInstalled(pkg, rootDir string) bool {
return true
}
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
// Get BPM package
bpmpkg := GetPackage(pkg, rootDir)
// Return nil if not found
if bpmpkg == nil {
return nil
}
return bpmpkg.PkgInfo
}
func IsVirtualPackage(pkg, rootDir string) (bool, string) {
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
@@ -146,15 +128,27 @@ func IsPackageProvided(pkg, rootDir string) bool {
return false
}
func GetPackage(pkg, rootDir string) *BPMPackage {
func GetPackageInfo(pkg string, rootDir string) *PackageInfo {
err := initializeLocalPackageInformation(rootDir)
if err != nil {
return nil
}
bpmpkg := localPackageInformation[rootDir][pkg]
return localPackageInformation[rootDir][pkg]
}
return bpmpkg
func GetPackage(pkg, rootDir string) *BPMPackage {
pkgInfo := GetPackageInfo(pkg, rootDir)
if pkgInfo == nil {
return nil
}
files := getPackageFiles(pkgInfo.Name, rootDir)
return &BPMPackage{
PkgInfo: pkgInfo,
PkgFiles: files,
}
}
func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][]*BPMPackage, error) {
@@ -225,7 +219,7 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
if err != nil {
return nil
}
size, err := strconv.ParseUint(stringEntry[len(stringEntry)-1], 0, 64)
size, err := strconv.ParseInt(stringEntry[len(stringEntry)-1], 0, 64)
if err != nil {
return nil
}
+122 -47
View File
@@ -87,8 +87,8 @@ func (operation *BPMOperation) RemoveAction(pkg, actionType string) {
})
}
func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
var ret uint64 = 0
func (operation *BPMOperation) GetTotalDownloadSize() int64 {
var ret int64 = 0
for _, action := range operation.Actions {
if action.GetActionType() == "fetch" {
ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize
@@ -97,8 +97,8 @@ func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
return ret
}
func (operation *BPMOperation) GetTotalInstalledSize() uint64 {
var ret uint64 = 0
func (operation *BPMOperation) GetTotalInstalledSize() int64 {
var ret int64 = 0
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
@@ -113,14 +113,17 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
var ret int64 = 0
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
ret += int64(action.(*InstallPackageAction).BpmPackage.GetInstalledSize())
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
if IsPackageInstalled(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir) {
ret -= int64(GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize())
ret -= GetPackage(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir).GetInstalledSize()
}
} else if action.GetActionType() == "fetch" {
ret += int64(action.(*FetchPackageAction).DatabaseEntry.InstalledSize)
ret += action.(*FetchPackageAction).DatabaseEntry.InstalledSize
if IsPackageInstalled(action.(*FetchPackageAction).DatabaseEntry.Info.Name, rootDir) {
ret -= action.(*FetchPackageAction).DatabaseEntry.InstalledSize
}
} else if action.GetActionType() == "remove" {
ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize())
ret -= action.(*RemovePackageAction).BpmPackage.GetInstalledSize()
}
}
return ret
@@ -277,54 +280,92 @@ func (operation *BPMOperation) ReplaceObsoletePackages() {
}
}
func (operation *BPMOperation) CheckForConflicts() (map[string][]string, error) {
func (operation *BPMOperation) CheckForConflicts() map[string][]string {
conflicts := make(map[string][]string)
installedPackages, err := GetInstalledPackages(operation.RootDir)
if err != nil {
return nil, err
}
allPackages := make([]*PackageInfo, len(installedPackages))
for i, value := range installedPackages {
bpmpkg := GetPackage(value, operation.RootDir)
if bpmpkg == nil {
return nil, fmt.Errorf("could not find installed package (%s)", value)
// Get installed packages
installedPackages := localPackageInformation[operation.RootDir]
// Get packages to be removed
removedPackages := make([]string, 0)
for _, value := range slices.Clone(operation.Actions) {
if value.GetActionType() != "remove" {
continue
}
allPackages[i] = bpmpkg.PkgInfo
removedPackages = append(removedPackages, value.(*RemovePackageAction).BpmPackage.PkgInfo.Name)
}
// Add all new packages to the allPackages slice
// Check for conflicts
for _, value := range slices.Clone(operation.Actions) {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
pkgInfo := action.BpmPackage.PkgInfo
allPackages = append(allPackages, pkgInfo)
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
} else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction)
pkgInfo := action.DatabaseEntry.Info
allPackages = append(allPackages, pkgInfo)
} else if value.GetActionType() == "remove" {
action := value.(*RemovePackageAction)
pkgInfo := action.BpmPackage.PkgInfo
for i := len(allPackages) - 1; i >= 0; i-- {
info := allPackages[i]
if info.Name == pkgInfo.Name {
allPackages = append(allPackages[:i], allPackages[i+1:]...)
pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
} else {
continue
}
// Check for conflicts with installed packages
for _, installedPkg := range installedPackages {
// Skip if package is to be removed
if slices.Contains(removedPackages, installedPkg.Name) {
continue
}
// Skip if same package
if pkgInfo.Name == installedPkg.Name {
continue
}
// Check for new package conflicts
if slices.Contains(pkgInfo.Conflicts, installedPkg.Name) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], installedPkg.Name)
}
for _, vpkg := range installedPkg.Provides {
if slices.Contains(pkgInfo.Conflicts, vpkg) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], vpkg+" ("+installedPkg.Name+")")
}
}
// Check for installed package conflicts
for _, vpkg := range pkgInfo.Provides {
if slices.Contains(installedPkg.Conflicts, vpkg) {
conflicts[installedPkg.Name] = append(conflicts[installedPkg.Name], vpkg+" ("+pkgInfo.Name+")")
}
}
}
// Check for conflicts with other new packages
for _, value := range slices.Clone(operation.Actions) {
var pkgInfo2 *PackageInfo
if value.GetActionType() == "install" {
pkgInfo2 = value.(*InstallPackageAction).BpmPackage.PkgInfo
} else if value.GetActionType() == "fetch" {
pkgInfo2 = value.(*FetchPackageAction).DatabaseEntry.Info
} else {
continue
}
// Skip if same package
if pkgInfo.Name == pkgInfo2.Name {
continue
}
// Check for other package conflicts
if slices.Contains(pkgInfo.Conflicts, pkgInfo2.Name) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], pkgInfo2.Name)
}
for _, vpkg := range pkgInfo2.Provides {
if slices.Contains(pkgInfo.Conflicts, vpkg) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], vpkg+" ("+pkgInfo2.Name+")")
}
}
}
}
for _, value := range allPackages {
for _, conflict := range value.Conflicts {
if slices.ContainsFunc(allPackages, func(info *PackageInfo) bool {
return info.Name == conflict
}) {
conflicts[value.Name] = append(conflicts[value.Name], conflict)
}
}
}
return conflicts, nil
return conflicts
}
func (operation *BPMOperation) ShowOperationSummary() {
@@ -388,12 +429,12 @@ func (operation *BPMOperation) ShowOperationSummary() {
fmt.Println("Warning: Operating in " + operation.RootDir)
}
if operation.GetTotalDownloadSize() > 0 {
fmt.Printf("%s will be downloaded to complete this operation\n", unsignedBytesToHumanReadable(operation.GetTotalDownloadSize()))
fmt.Printf("%s will be downloaded to complete this operation\n", BytesToHumanReadable(operation.GetTotalDownloadSize()))
}
if operation.GetFinalActionSize(operation.RootDir) > 0 {
fmt.Printf("A total of %s will be installed after the operation finishes\n", bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)))
fmt.Printf("A total of %s will be installed after the operation finishes\n", BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)))
} else if operation.GetFinalActionSize(operation.RootDir) < 0 {
fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(bytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-"))
fmt.Printf("A total of %s will be freed after the operation finishes\n", strings.TrimPrefix(BytesToHumanReadable(operation.GetFinalActionSize(operation.RootDir)), "-"))
}
}
@@ -425,6 +466,40 @@ func (operation *BPMOperation) ShowSourcePackageContent() (sourcePackagesShown i
return sourcePackagesShown, nil
}
func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[string][]string) {
optionalDepends = make(map[string][]string)
// Find all optional dependencies
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 _, depend := range pkgInfo.OptionalDepends {
// Skip if dependency is already installed
if IsPackageInstalled(depend, 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) {
continue
}
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], depend)
}
}
return
}
func (operation *BPMOperation) RunHooks(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() {
+35 -47
View File
@@ -69,11 +69,11 @@ type PackageFileEntry struct {
OctalPerms uint32
UserID int
GroupID int
SizeInBytes uint64
SizeInBytes int64
}
func (pkg *BPMPackage) GetInstalledSize() uint64 {
var totalSize uint64 = 0
func (pkg *BPMPackage) GetInstalledSize() int64 {
var totalSize int64 = 0
for _, entry := range pkg.PkgFiles {
totalSize += entry.SizeInBytes
}
@@ -232,7 +232,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
if err != nil {
return nil, err
}
size, err := strconv.ParseUint(stringEntry[len(stringEntry)-1], 0, 64)
size, err := strconv.ParseInt(stringEntry[len(stringEntry)-1], 0, 64)
if err != nil {
return nil, err
}
@@ -536,60 +536,57 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
return pkgInfo, nil
}
func (bpmpkg *BPMPackage) CreateReadableInfo(rootDir string, humanReadableSize bool) string {
func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
ret := make([]string, 0)
appendArray := func(label string, array []string) {
if len(array) == 0 {
return
}
// Sort array
slices.Sort(array)
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
}
ret = append(ret, "Name: "+bpmpkg.PkgInfo.Name)
ret = append(ret, "Description: "+bpmpkg.PkgInfo.Description)
ret = append(ret, "Version: "+bpmpkg.PkgInfo.GetFullVersion())
if bpmpkg.PkgInfo.Url != "" {
ret = append(ret, "URL: "+bpmpkg.PkgInfo.Url)
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)
}
if bpmpkg.PkgInfo.License != "" {
ret = append(ret, "License: "+bpmpkg.PkgInfo.License)
if pkgInfo.License != "" {
ret = append(ret, "License: "+pkgInfo.License)
}
ret = append(ret, "Architecture: "+bpmpkg.PkgInfo.Arch)
if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.OutputArch != "" && bpmpkg.PkgInfo.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+bpmpkg.PkgInfo.Arch)
ret = append(ret, "Architecture: "+pkgInfo.Arch)
if pkgInfo.Type == "source" && pkgInfo.OutputArch != "" && pkgInfo.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+pkgInfo.Arch)
}
ret = append(ret, "Type: "+bpmpkg.PkgInfo.Type)
appendArray("Dependencies", bpmpkg.PkgInfo.Depends)
if bpmpkg.PkgInfo.Type == "source" {
appendArray("Make Dependencies", bpmpkg.PkgInfo.MakeDepends)
ret = append(ret, "Type: "+pkgInfo.Type)
appendArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Optional dependencies", bpmpkg.PkgInfo.OptionalDepends)
dependants := bpmpkg.PkgInfo.GetPackageDependants(rootDir)
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants := pkgInfo.GetPackageDependants(rootDir)
if len(dependants) > 0 {
appendArray("Dependant packages", dependants)
}
optionalDependants := bpmpkg.PkgInfo.GetPackageOptionalDependants(rootDir)
optionalDependants := pkgInfo.GetPackageOptionalDependants(rootDir)
if len(optionalDependants) > 0 {
appendArray("Optionally dependant packages", optionalDependants)
}
appendArray("Conflicting packages", bpmpkg.PkgInfo.Conflicts)
appendArray("Provided packages", bpmpkg.PkgInfo.Provides)
appendArray("Replaces packages", bpmpkg.PkgInfo.Replaces)
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
if bpmpkg.PkgInfo.Type == "source" && len(bpmpkg.PkgInfo.SplitPackages) != 0 {
splitPkgs := make([]string, len(bpmpkg.PkgInfo.SplitPackages))
for i, splitPkgInfo := range bpmpkg.PkgInfo.SplitPackages {
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)
}
if rootDir != "" && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(bpmpkg.PkgInfo.Name, rootDir)
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
var installationReasonString string
switch installationReason {
case InstallationReasonManual:
@@ -603,16 +600,7 @@ func (bpmpkg *BPMPackage) CreateReadableInfo(rootDir string, humanReadableSize b
}
ret = append(ret, "Installation Reason: "+installationReasonString)
}
if bpmpkg.PkgInfo.Type == "binary" {
installedSize := int64(bpmpkg.GetInstalledSize())
var installedSizeStr string
if humanReadableSize {
installedSizeStr = bytesToHumanReadable(installedSize)
} else {
installedSizeStr = strconv.FormatInt(installedSize, 10)
}
ret = append(ret, "Installed size: "+installedSizeStr)
}
return strings.Join(ret, "\n")
}
@@ -624,7 +612,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
}
// Initialize progress bar
bar := createProgressBar(int64(bpmpkg.GetInstalledSize()), "Installing "+bpmpkg.PkgInfo.Name, verbose)
bar := createProgressBar(bpmpkg.GetInstalledSize(), "Installing "+bpmpkg.PkgInfo.Name, verbose)
defer bar.Close()
tarballFile, err := readTarballFile(filename, "files.tar.gz")
@@ -1007,7 +995,7 @@ func installPackage(filename, rootDir string, verbose, force bool) error {
}
// Add or update package information for rootDir
localPackageInformation[rootDir][bpmpkg.PkgInfo.Name] = bpmpkg
localPackageInformation[rootDir][bpmpkg.PkgInfo.Name] = bpmpkg.PkgInfo
return nil
}
@@ -1039,12 +1027,12 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
return err
}
bar := createProgressBar(int64(bpmpkg.GetInstalledSize()), "Removing "+bpmpkg.PkgInfo.Name, verbose)
bar := createProgressBar(bpmpkg.GetInstalledSize(), "Removing "+bpmpkg.PkgInfo.Name, verbose)
defer bar.Close()
// Removing package files
for _, entry := range fileEntries {
bar.Add64(int64(entry.SizeInBytes))
bar.Add64(entry.SizeInBytes)
file := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(file)
if os.IsNotExist(err) {
+30 -29
View File
@@ -18,20 +18,6 @@ type BPMLock struct {
path string
}
func (lock *BPMLock) Unlock() error {
err := lock.file.Close()
if err != nil {
return err
}
err = os.Remove(lock.path)
if err != nil {
return err
}
return nil
}
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)
@@ -54,6 +40,20 @@ func LockBPM(rootDir string) (*BPMLock, error) {
return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil
}
func (lock *BPMLock) Unlock() error {
err := lock.file.Close()
if err != nil {
return err
}
err = os.Remove(lock.path)
if err != nil {
return err
}
return nil
}
func GetArch() string {
uname := syscall.Utsname{}
err := syscall.Uname(&uname)
@@ -69,6 +69,13 @@ func GetArch() string {
return string(byteString[:indexLength])
}
func CompareVersions(version1, version2 string) int {
v1 := version.NewVersion(version1)
v2 := version.NewVersion(version2)
return v1.Compare(v2)
}
func createProgressBar(max int64, description string, hideBar bool) *progressbar.ProgressBar {
var output io.Writer
if hideBar {
@@ -112,7 +119,7 @@ func stringSliceRemove(s []string, r string) []string {
return s
}
func unsignedBytesToHumanReadable(b uint64) string {
func BytesToHumanReadable(b int64) string {
bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
if math.Abs(bf) < 1024.0 {
@@ -123,20 +130,14 @@ func unsignedBytesToHumanReadable(b uint64) string {
return fmt.Sprintf("%.1fYiB", bf)
}
func bytesToHumanReadable(b int64) string {
bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
if math.Abs(bf) < 1024.0 {
return fmt.Sprintf("%3.1f%sB", bf, unit)
func removeDuplicates[T comparable](sliceList []T) []T {
allKeys := make(map[T]bool)
list := []T{}
for _, item := range sliceList {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
func CompareVersions(version1, version2 string) int {
v1 := version.NewVersion(version1)
v2 := version.NewVersion(version2)
return v1.Compare(v2)
return list
}