8 Commits
7 changed files with 241 additions and 73 deletions
+97 -25
View File
@@ -26,7 +26,7 @@ import (
/* A simple-to-use package manager */ /* A simple-to-use package manager */
/* ------------------------------------------------------- */ /* ------------------------------------------------------- */
var bpmVer = "0.6.0" var bpmVer = "0.6.1"
var currentFlagSet *flag.FlagSet var currentFlagSet *flag.FlagSet
@@ -45,7 +45,7 @@ func main() {
} }
switch subcommand { switch subcommand {
case "version": case "v", "version":
fmt.Println("Bubble Package Manager (BPM)") fmt.Println("Bubble Package Manager (BPM)")
fmt.Println("Version: " + bpmVer) fmt.Println("Version: " + bpmVer)
case "q", "query": case "q", "query":
@@ -62,6 +62,10 @@ func main() {
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory") currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("count", "c", false, "Show total package count") currentFlagSet.BoolP("count", "c", false, "Show total package count")
currentFlagSet.BoolP("names", "n", false, "Show all package names") currentFlagSet.BoolP("names", "n", false, "Show all package names")
currentFlagSet.BoolP("database", "d", false, "Show packages from remote databases")
currentFlagSet.Bool("manual", false, "Show packages installed as dependencies")
currentFlagSet.Bool("depends", false, "Show packages installed as dependencies")
currentFlagSet.Bool("make-depends", false, "Show packages installed as make dependencies")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "List packages", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "List packages", os.Args[2:])
showPackageList() showPackageList()
@@ -185,9 +189,6 @@ func showPackageInfo() {
} }
for n, pkg := range packages { for n, pkg := range packages {
var info *bpmlib.PackageInfo
isFile := false
showInstallationReason := false
if showDatabaseInfo { if showDatabaseInfo {
var err error var err error
var entry *bpmlib.BPMDatabaseEntry var entry *bpmlib.BPMDatabaseEntry
@@ -199,8 +200,18 @@ func showPackageInfo() {
return return
} }
} }
info = entry.Info
} else if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() { if n != 0 {
fmt.Println()
}
fmt.Println(entry.CreateReadableInfo(rootDir))
return
}
var info *bpmlib.PackageInfo
isFile := false
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err := bpmlib.ReadPackage(pkg) bpmpkg, err := bpmlib.ReadPackage(pkg)
if err != nil { if err != nil {
log.Printf("Error: could not read package: %s\n", err) log.Printf("Error: could not read package: %s\n", err)
@@ -215,7 +226,6 @@ func showPackageInfo() {
} else { } else {
info = bpmlib.GetPackageInfo(pkg, rootDir) info = bpmlib.GetPackageInfo(pkg, rootDir)
} }
showInstallationReason = true
} }
if info == nil { if info == nil {
log.Printf("Error: package (%s) is not installed\n", pkg) log.Printf("Error: package (%s) is not installed\n", pkg)
@@ -234,7 +244,7 @@ func showPackageInfo() {
} }
fmt.Println("File: " + abs) fmt.Println("File: " + abs)
} }
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, showInstallationReason, info, rootDir)) fmt.Println(info.CreateReadableInfo(rootDir))
} }
} }
@@ -243,6 +253,16 @@ func showPackageList() {
rootDir, _ := currentFlagSet.GetString("root") rootDir, _ := currentFlagSet.GetString("root")
showPkgCount, _ := currentFlagSet.GetBool("count") showPkgCount, _ := currentFlagSet.GetBool("count")
showPkgNames, _ := currentFlagSet.GetBool("names") showPkgNames, _ := currentFlagSet.GetBool("names")
showDatabase, _ := currentFlagSet.GetBool("database")
showManual, _ := currentFlagSet.GetBool("manual")
showDepends, _ := currentFlagSet.GetBool("depends")
showMakeDepends, _ := currentFlagSet.GetBool("make-depends")
if !isFlagSet(currentFlagSet, "manual") && !isFlagSet(currentFlagSet, "depends") && !isFlagSet(currentFlagSet, "make-depends") {
showManual = true
showDepends = true
showMakeDepends = true
}
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
@@ -252,33 +272,85 @@ func showPackageList() {
return return
} }
packages, err := bpmlib.GetInstalledPackages(rootDir) installedPackages, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil { if err != nil {
log.Printf("Error: could not get installed packages: %s", err.Error()) log.Printf("Error: could not get installed packages: %s", err.Error())
exitCode = 1 exitCode = 1
return return
} }
databaseEntries := make([]*bpmlib.BPMDatabaseEntry, 0)
for _, db := range bpmlib.BPMDatabases {
databaseEntries = append(databaseEntries, slices.Collect(maps.Values(db.Entries))...)
}
if showPkgCount { if showPkgCount {
fmt.Println(len(packages)) if showDatabase {
fmt.Println(len(databaseEntries))
} else {
fmt.Println(len(installedPackages))
}
} else if showPkgNames { } else if showPkgNames {
for _, pkg := range packages { if showDatabase {
fmt.Println(pkg) for _, entry := range databaseEntries {
fmt.Println(entry.Database.Name + "/" + entry.Info.Name)
}
} else {
for _, pkg := range installedPackages {
installationReason := bpmlib.GetInstallationReason(pkg, rootDir)
if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
continue
} else if installationReason == bpmlib.InstallationReasonMakeDependency && !showMakeDepends {
continue
} else if installationReason == bpmlib.InstallationReasonUnknown && (!showManual || !showDepends || !showMakeDepends) {
continue
}
fmt.Println(pkg)
}
} }
} else { } else {
if len(packages) == 0 { if showDatabase {
fmt.Println("No packages have been installed") if len(databaseEntries) == 0 {
return fmt.Println("There are no database entries available")
} return
for n, pkg := range packages {
info := bpmlib.GetPackageInfo(pkg, rootDir)
if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg)
continue
} }
if n != 0 { for n, entry := range databaseEntries {
fmt.Println() if n != 0 {
fmt.Println()
}
fmt.Println(entry.CreateReadableInfo(rootDir))
}
} else {
if len(installedPackages) == 0 {
fmt.Println("No packages have been installed")
return
}
for n, pkg := range installedPackages {
info := bpmlib.GetPackageInfo(pkg, rootDir)
if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg)
continue
}
installationReason := bpmlib.GetInstallationReason(info.Name, rootDir)
if installationReason == bpmlib.InstallationReasonManual && !showManual {
continue
} else if installationReason == bpmlib.InstallationReasonDependency && !showDepends {
continue
} else if installationReason == bpmlib.InstallationReasonMakeDependency && !showMakeDepends {
continue
} else if installationReason == bpmlib.InstallationReasonUnknown && (!showManual || !showDepends || !showMakeDepends) {
continue
}
if n != 0 {
fmt.Println()
}
fmt.Println(info.CreateReadableInfo(rootDir))
} }
fmt.Println(bpmlib.CreateReadableInfo(true, true, true, true, info, rootDir))
} }
} }
} }
+101
View File
@@ -3,6 +3,7 @@ package bpmlib
import ( import (
"errors" "errors"
"fmt" "fmt"
"maps"
"net/url" "net/url"
"os" "os"
"path" "path"
@@ -16,6 +17,7 @@ type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"` DatabaseVersion int `yaml:"database_version"`
Entries map[string]*BPMDatabaseEntry `yaml:"entries"` Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
VirtualPackages map[string][]string VirtualPackages map[string][]string
Name string
Source string Source string
} }
@@ -54,6 +56,7 @@ func (db *configDatabase) ReadLocalDatabase() error {
// Initialize struct values // Initialize struct values
database.VirtualPackages = make(map[string][]string) database.VirtualPackages = make(map[string][]string)
database.Name = db.Name
database.Source = db.Source database.Source = db.Source
entriesToRemove := make([]string, 0) entriesToRemove := make([]string, 0)
@@ -247,3 +250,101 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil
} }
func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
dependantsMap := make(map[string][]string)
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.Contains(e.Info.Depends, entry.Info.Name) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
}
}
// Get keys
keySlice := slices.Collect(maps.Keys(dependantsMap))
slices.Sort(keySlice)
// Add all dependant entries to slice in alphabetical order
for _, entryName := range keySlice {
dbs := dependantsMap[entryName]
if len(dbs) > 1 {
for _, db := range dbs {
dependants = append(dependants, db+"/"+entryName)
}
} else {
dependants = append(dependants, entryName)
}
}
return dependants
}
func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string) string {
ret := make([]string, 0)
appendArray := func(label string, array []string, sort bool) {
if len(array) == 0 {
return
}
if sort {
// Sort array
slices.Sort(array)
}
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
}
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)
}
if entry.Info.License != "" {
ret = append(ret, "License: "+entry.Info.License)
}
ret = append(ret, "Architecture: "+entry.Info.Arch)
if entry.Info.Type == "source" && entry.Info.OutputArch != "" && entry.Info.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+entry.Info.OutputArch)
}
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)
}
appendArray("Conflicting packages", entry.Info.Conflicts, true)
appendArray("Provided packages", entry.Info.Provides, true)
appendArray("Replaces packages", entry.Info.Replaces, true)
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)
}
if rootDir != "" && IsPackageInstalled(entry.Info.Name, rootDir) {
installationReason := GetInstallationReason(entry.Info.Name, rootDir)
var installationReasonString string
switch installationReason {
case InstallationReasonManual:
installationReasonString = "Manual"
case InstallationReasonDependency:
installationReasonString = "Dependency"
case InstallationReasonMakeDependency:
installationReasonString = "Make dependency"
default:
installationReasonString = "Unknown"
}
ret = append(ret, "Installation Reason: "+installationReasonString)
}
return strings.Join(ret, "\n")
}
+9 -18
View File
@@ -1,7 +1,6 @@
package bpmlib package bpmlib
import ( import (
"errors"
"fmt" "fmt"
"slices" "slices"
) )
@@ -170,19 +169,11 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
} }
} }
func GetPackageDependants(pkgName string, rootDir string) ([]string, error) { func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
ret := make([]string, 0)
// Get BPM package
pkg := GetPackage(pkgName, rootDir)
if pkg == nil {
return nil, errors.New("package not found: " + pkgName)
}
// Get installed package names // Get installed package names
pkgs, err := GetInstalledPackages(rootDir) pkgs, err := GetInstalledPackages(rootDir)
if err != nil { if err != nil {
return nil, errors.New("could not get installed packages") return nil
} }
// Loop through all installed packages // Loop through all installed packages
@@ -190,33 +181,33 @@ func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
// Get installed BPM package // Get installed BPM package
installedPkg := GetPackage(installedPkgName, rootDir) installedPkg := GetPackage(installedPkgName, rootDir)
if installedPkg == nil { if installedPkg == nil {
return nil, errors.New("package not found: " + installedPkgName) return nil
} }
// Skip iteration if comparing the same packages // Skip iteration if comparing the same packages
if installedPkg.PkgInfo.Name == pkgName { if installedPkg.PkgInfo.Name == pkgInfo.Name {
continue continue
} }
// Add installed package to list if its dependencies include pkgName // Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
return n == pkgName return n == pkgInfo.Name
}) { }) {
ret = append(ret, installedPkgName) dependants = append(dependants, installedPkgName)
continue continue
} }
// Loop through each virtual package // Loop through each virtual package
for _, vpkg := range pkg.PkgInfo.Provides { for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its dependencies contain a provided virtual package // Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
return n == vpkg return n == vpkg
}) { }) {
ret = append(ret, installedPkgName) dependants = append(dependants, installedPkgName)
break break
} }
} }
} }
return ret, nil return dependants
} }
+1 -5
View File
@@ -221,11 +221,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Get packages and their dependants // Get packages and their dependants
packageDepndants := make(map[string][]string, 0) packageDepndants := make(map[string][]string, 0)
for _, action := range operation.Actions { for _, action := range operation.Actions {
dependants, err := GetPackageDependants(action.(*RemovePackageAction).BpmPackage.PkgInfo.Name, rootDir) dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir)
if err != nil {
return nil, fmt.Errorf("could not get package dependants: %s", err)
}
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
} }
+3
View File
@@ -75,6 +75,9 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
ret = append(ret, bpmpkg.PkgInfo.Name) ret = append(ret, bpmpkg.PkgInfo.Name)
} }
// Sort packages
slices.Sort(ret)
return ret, nil return ret, nil
} }
+1 -4
View File
@@ -176,10 +176,7 @@ func (operation *BPMOperation) RemoveNeededPackages() error {
} }
for pkg, action := range removeActions { for pkg, action := range removeActions {
dependants, err := GetPackageDependants(action.BpmPackage.PkgInfo.Name, operation.RootDir) dependants := action.BpmPackage.PkgInfo.GetPackageDependants(operation.RootDir)
if err != nil {
return errors.New("could not get dependant packages for package (" + pkg + ")")
}
dependants = slices.DeleteFunc(dependants, func(d string) bool { dependants = slices.DeleteFunc(dependants, func(d string) bool {
if _, ok := removeActions[d]; ok { if _, ok := removeActions[d]; ok {
return true return true
+29 -21
View File
@@ -507,39 +507,46 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
return pkgInfo, nil return pkgInfo, nil
} }
func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showInstallationReason bool, pkgInfo *PackageInfo, rootDir string) string { func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
ret := make([]string, 0) ret := make([]string, 0)
appendArray := func(label string, array []string) { appendArray := func(label string, array []string) {
if len(array) == 0 { if len(array) == 0 {
return return
} }
// Sort array
slices.Sort(array)
ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", "))) ret = append(ret, fmt.Sprintf("%s: %s", label, strings.Join(array, ", ")))
} }
ret = append(ret, "Name: "+pkgInfo.Name) ret = append(ret, "Name: "+pkgInfo.Name)
ret = append(ret, "Description: "+pkgInfo.Description) ret = append(ret, "Description: "+pkgInfo.Description)
ret = append(ret, "Version: "+pkgInfo.GetFullVersion()) ret = append(ret, "Version: "+pkgInfo.GetFullVersion())
ret = append(ret, "URL: "+pkgInfo.Url) if pkgInfo.Url != "" {
ret = append(ret, "License: "+pkgInfo.License) ret = append(ret, "URL: "+pkgInfo.Url)
if showArchitecture {
ret = append(ret, "Architecture: "+pkgInfo.Arch)
} }
if showType { if pkgInfo.License != "" {
ret = append(ret, "Type: "+pkgInfo.Type) ret = append(ret, "License: "+pkgInfo.License)
} }
if showPackageRelations { ret = append(ret, "Architecture: "+pkgInfo.Arch)
appendArray("Dependencies", pkgInfo.Depends) if pkgInfo.Type == "source" && pkgInfo.OutputArch != "" && pkgInfo.OutputArch != GetArch() {
if pkgInfo.Type == "source" { ret = append(ret, "Output architecture: "+pkgInfo.Arch)
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants, err := GetPackageDependants(pkgInfo.Name, rootDir)
if err == nil {
appendArray("Dependant packages", dependants)
}
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
} }
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)
}
appendArray("Conflicting packages", pkgInfo.Conflicts)
appendArray("Provided packages", pkgInfo.Provides)
appendArray("Replaces packages", pkgInfo.Replaces)
if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 { if pkgInfo.Type == "source" && len(pkgInfo.SplitPackages) != 0 {
splitPkgs := make([]string, len(pkgInfo.SplitPackages)) splitPkgs := make([]string, len(pkgInfo.SplitPackages))
for i, splitPkgInfo := range pkgInfo.SplitPackages { for i, splitPkgInfo := range pkgInfo.SplitPackages {
@@ -547,7 +554,8 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showIn
} }
appendArray("Split Packages", splitPkgs) appendArray("Split Packages", splitPkgs)
} }
if IsPackageInstalled(pkgInfo.Name, rootDir) && showInstallationReason {
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(pkgInfo.Name, rootDir) installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
var installationReasonString string var installationReasonString string
switch installationReason { switch installationReason {