8 Commits
7 changed files with 241 additions and 73 deletions
+88 -16
View File
@@ -26,7 +26,7 @@ import (
/* A simple-to-use package manager */
/* ------------------------------------------------------- */
var bpmVer = "0.6.0"
var bpmVer = "0.6.1"
var currentFlagSet *flag.FlagSet
@@ -45,7 +45,7 @@ func main() {
}
switch subcommand {
case "version":
case "v", "version":
fmt.Println("Bubble Package Manager (BPM)")
fmt.Println("Version: " + bpmVer)
case "q", "query":
@@ -62,6 +62,10 @@ func main() {
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
currentFlagSet.BoolP("count", "c", false, "Show total package count")
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:])
showPackageList()
@@ -185,9 +189,6 @@ func showPackageInfo() {
}
for n, pkg := range packages {
var info *bpmlib.PackageInfo
isFile := false
showInstallationReason := false
if showDatabaseInfo {
var err error
var entry *bpmlib.BPMDatabaseEntry
@@ -199,8 +200,18 @@ func showPackageInfo() {
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)
if err != nil {
log.Printf("Error: could not read package: %s\n", err)
@@ -215,7 +226,6 @@ func showPackageInfo() {
} else {
info = bpmlib.GetPackageInfo(pkg, rootDir)
}
showInstallationReason = true
}
if info == nil {
log.Printf("Error: package (%s) is not installed\n", pkg)
@@ -234,7 +244,7 @@ func showPackageInfo() {
}
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")
showPkgCount, _ := currentFlagSet.GetBool("count")
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
err := bpmlib.ReadLocalDatabaseFiles()
@@ -252,33 +272,85 @@ func showPackageList() {
return
}
packages, err := bpmlib.GetInstalledPackages(rootDir)
installedPackages, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil {
log.Printf("Error: could not get installed packages: %s", err.Error())
exitCode = 1
return
}
databaseEntries := make([]*bpmlib.BPMDatabaseEntry, 0)
for _, db := range bpmlib.BPMDatabases {
databaseEntries = append(databaseEntries, slices.Collect(maps.Values(db.Entries))...)
}
if showPkgCount {
fmt.Println(len(packages))
if showDatabase {
fmt.Println(len(databaseEntries))
} else {
fmt.Println(len(installedPackages))
}
} else if showPkgNames {
for _, pkg := range packages {
fmt.Println(pkg)
if showDatabase {
for _, entry := range databaseEntries {
fmt.Println(entry.Database.Name + "/" + entry.Info.Name)
}
} else {
if len(packages) == 0 {
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 {
if showDatabase {
if len(databaseEntries) == 0 {
fmt.Println("There are no database entries available")
return
}
for n, entry := range databaseEntries {
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 packages {
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(bpmlib.CreateReadableInfo(true, true, true, true, info, rootDir))
fmt.Println(info.CreateReadableInfo(rootDir))
}
}
}
}
+101
View File
@@ -3,6 +3,7 @@ package bpmlib
import (
"errors"
"fmt"
"maps"
"net/url"
"os"
"path"
@@ -16,6 +17,7 @@ type BPMDatabase struct {
DatabaseVersion int `yaml:"database_version"`
Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
VirtualPackages map[string][]string
Name string
Source string
}
@@ -54,6 +56,7 @@ func (db *configDatabase) ReadLocalDatabase() error {
// Initialize struct values
database.VirtualPackages = make(map[string][]string)
database.Name = db.Name
database.Source = db.Source
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
}
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
import (
"errors"
"fmt"
"slices"
)
@@ -170,19 +169,11 @@ func resolvePackageDependenciesFromDatabase(resolved *[]pkgInstallationReason, u
}
}
func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
ret := make([]string, 0)
// Get BPM package
pkg := GetPackage(pkgName, rootDir)
if pkg == nil {
return nil, errors.New("package not found: " + pkgName)
}
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
// Get installed package names
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return nil, errors.New("could not get installed packages")
return nil
}
// Loop through all installed packages
@@ -190,33 +181,33 @@ func GetPackageDependants(pkgName string, rootDir string) ([]string, error) {
// Get installed BPM package
installedPkg := GetPackage(installedPkgName, rootDir)
if installedPkg == nil {
return nil, errors.New("package not found: " + installedPkgName)
return nil
}
// Skip iteration if comparing the same packages
if installedPkg.PkgInfo.Name == pkgName {
if installedPkg.PkgInfo.Name == pkgInfo.Name {
continue
}
// Add installed package to list if its dependencies include pkgName
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
}
// 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
if slices.ContainsFunc(installedPkg.PkgInfo.Depends, func(n string) bool {
return n == vpkg
}) {
ret = append(ret, installedPkgName)
dependants = append(dependants, installedPkgName)
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
packageDepndants := make(map[string][]string, 0)
for _, action := range operation.Actions {
dependants, err := GetPackageDependants(action.(*RemovePackageAction).BpmPackage.PkgInfo.Name, rootDir)
if err != nil {
return nil, fmt.Errorf("could not get package dependants: %s", err)
}
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir)
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)
}
// Sort packages
slices.Sort(ret)
return ret, nil
}
+1 -4
View File
@@ -176,10 +176,7 @@ func (operation *BPMOperation) RemoveNeededPackages() error {
}
for pkg, action := range removeActions {
dependants, err := GetPackageDependants(action.BpmPackage.PkgInfo.Name, operation.RootDir)
if err != nil {
return errors.New("could not get dependant packages for package (" + pkg + ")")
}
dependants := action.BpmPackage.PkgInfo.GetPackageDependants(operation.RootDir)
dependants = slices.DeleteFunc(dependants, func(d string) bool {
if _, ok := removeActions[d]; ok {
return true
+17 -9
View File
@@ -507,39 +507,46 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
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)
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: "+pkgInfo.Name)
ret = append(ret, "Description: "+pkgInfo.Description)
ret = append(ret, "Version: "+pkgInfo.GetFullVersion())
if pkgInfo.Url != "" {
ret = append(ret, "URL: "+pkgInfo.Url)
}
if pkgInfo.License != "" {
ret = append(ret, "License: "+pkgInfo.License)
if showArchitecture {
}
ret = append(ret, "Architecture: "+pkgInfo.Arch)
if pkgInfo.Type == "source" && pkgInfo.OutputArch != "" && pkgInfo.OutputArch != GetArch() {
ret = append(ret, "Output architecture: "+pkgInfo.Arch)
}
if showType {
ret = append(ret, "Type: "+pkgInfo.Type)
}
if showPackageRelations {
appendArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
appendArray("Make Dependencies", pkgInfo.MakeDepends)
}
appendArray("Optional dependencies", pkgInfo.OptionalDepends)
dependants, err := GetPackageDependants(pkgInfo.Name, rootDir)
if err == nil {
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 {
splitPkgs := make([]string, len(pkgInfo.SplitPackages))
for i, splitPkgInfo := range pkgInfo.SplitPackages {
@@ -547,7 +554,8 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showIn
}
appendArray("Split Packages", splitPkgs)
}
if IsPackageInstalled(pkgInfo.Name, rootDir) && showInstallationReason {
if rootDir != "" && IsPackageInstalled(pkgInfo.Name, rootDir) {
installationReason := GetInstallationReason(pkgInfo.Name, rootDir)
var installationReasonString string
switch installationReason {