5 Commits
7 changed files with 216 additions and 93 deletions
+30
View File
@@ -577,6 +577,9 @@ func installPackages() {
} }
} }
// Get optional dependencies
optionalDepends := operation.GetOptionalDependencies()
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -593,6 +596,18 @@ func installPackages() {
exitCode = 1 exitCode = 1
return return
} }
// Show optional dependencies
if !installOptional && len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following opitonal 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() { func removePackages() {
@@ -942,6 +957,9 @@ func updatePackages() {
} }
} }
// Get optional dependencies
optionalDepends := operation.GetOptionalDependencies()
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -958,6 +976,18 @@ func updatePackages() {
exitCode = 1 exitCode = 1
return return
} }
// Show optional dependencies
if !installOptional && len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following opitonal 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() { func getFileOwner() {
+3 -3
View File
@@ -28,8 +28,8 @@ type BPMDatabase struct {
type BPMDatabaseEntry struct { type BPMDatabaseEntry struct {
Info *PackageInfo `yaml:"info"` Info *PackageInfo `yaml:"info"`
Filepath string `yaml:"filepath"` Filepath string `yaml:"filepath"`
DownloadSize uint64 `yaml:"download_size"` DownloadSize int64 `yaml:"download_size"`
InstalledSize uint64 `yaml:"installed_size"` InstalledSize int64 `yaml:"installed_size"`
Database *BPMDatabase Database *BPMDatabase
} }
@@ -400,7 +400,7 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, humanReadableS
ret = append(ret, "Installation Reason: "+installationReasonString) ret = append(ret, "Installation Reason: "+installationReasonString)
} }
if entry.Info.Type == "binary" { if entry.Info.Type == "binary" {
installedSize := int64(entry.InstalledSize) installedSize := entry.InstalledSize
var installedSizeStr string var installedSizeStr string
if humanReadableSize { if humanReadableSize {
installedSizeStr = bytesToHumanReadable(installedSize) installedSizeStr = bytesToHumanReadable(installedSize)
+23 -6
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 // 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, installOptionalDependencies, forceInstallation, verbose bool, packages ...string) (operation *BPMOperation, err error) {
// Setup operation struct // Setup operation struct
operation = &BPMOperation{ operation = &BPMOperation{
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
@@ -31,6 +30,9 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
// Remove duplicates from packages
packages = removeDuplicates(packages)
// Resolve packages // Resolve packages
pkgsNotFound := make([]string, 0) pkgsNotFound := make([]string, 0)
for _, pkg := range packages { for _, pkg := range packages {
@@ -145,12 +147,9 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
operation.ReplaceObsoletePackages() operation.ReplaceObsoletePackages()
// Check for conflicts // Check for conflicts
conflicts, err := operation.CheckForConflicts() conflicts := operation.CheckForConflicts()
if err != nil {
return nil, fmt.Errorf("could not complete package conflict check: %s", err)
}
if len(conflicts) > 0 { if len(conflicts) > 0 {
err = nil err = fmt.Errorf("conflicts detected")
for pkg, conflict := range conflicts { for pkg, conflict := range conflicts {
err = errors.Join(err, PackageConflictErr{pkg, conflict}) err = errors.Join(err, PackageConflictErr{pkg, conflict})
} }
@@ -196,6 +195,9 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
// Remove duplicates from packages
packages = removeDuplicates(packages)
// Search for packages // Search for packages
for _, pkg := range packages { for _, pkg := range packages {
bpmpkg := GetPackage(pkg, rootDir) bpmpkg := GetPackage(pkg, rootDir)
@@ -441,6 +443,21 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
// Replace obsolete packages // Replace obsolete packages
operation.ReplaceObsoletePackages() 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 return operation, nil
} }
+1 -1
View File
@@ -225,7 +225,7 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
if err != nil { if err != nil {
return 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 { if err != nil {
return nil return nil
} }
+122 -47
View File
@@ -87,8 +87,8 @@ func (operation *BPMOperation) RemoveAction(pkg, actionType string) {
}) })
} }
func (operation *BPMOperation) GetTotalDownloadSize() uint64 { func (operation *BPMOperation) GetTotalDownloadSize() int64 {
var ret uint64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
if action.GetActionType() == "fetch" { if action.GetActionType() == "fetch" {
ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize ret += action.(*FetchPackageAction).DatabaseEntry.DownloadSize
@@ -97,8 +97,8 @@ func (operation *BPMOperation) GetTotalDownloadSize() uint64 {
return ret return ret
} }
func (operation *BPMOperation) GetTotalInstalledSize() uint64 { func (operation *BPMOperation) GetTotalInstalledSize() int64 {
var ret uint64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
if action.GetActionType() == "install" { if action.GetActionType() == "install" {
ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize() ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
@@ -113,14 +113,17 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
var ret int64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
if action.GetActionType() == "install" { if action.GetActionType() == "install" {
ret += int64(action.(*InstallPackageAction).BpmPackage.GetInstalledSize()) ret += action.(*InstallPackageAction).BpmPackage.GetInstalledSize()
if IsPackageInstalled(action.(*InstallPackageAction).BpmPackage.PkgInfo.Name, rootDir) { 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" { } 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" { } else if action.GetActionType() == "remove" {
ret -= int64(action.(*RemovePackageAction).BpmPackage.GetInstalledSize()) ret -= action.(*RemovePackageAction).BpmPackage.GetInstalledSize()
} }
} }
return ret 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) 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)
}
allPackages[i] = bpmpkg.PkgInfo
}
// Add all new packages to the allPackages slice // Get installed packages
installedPackages := localPackageInformation[operation.RootDir]
// Get packages to be removed
removedPackages := make([]string, 0)
for _, value := range slices.Clone(operation.Actions) { for _, value := range slices.Clone(operation.Actions) {
if value.GetActionType() != "remove" {
continue
}
removedPackages = append(removedPackages, value.(*RemovePackageAction).BpmPackage.PkgInfo.Name)
}
// Check for conflicts
for _, value := range slices.Clone(operation.Actions) {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" { if value.GetActionType() == "install" {
action := value.(*InstallPackageAction) pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
pkgInfo := action.BpmPackage.PkgInfo
allPackages = append(allPackages, pkgInfo)
} else if value.GetActionType() == "fetch" { } else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction) pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
pkgInfo := action.DatabaseEntry.Info } else {
allPackages = append(allPackages, pkgInfo) continue
} else if value.GetActionType() == "remove" { }
action := value.(*RemovePackageAction)
pkgInfo := action.BpmPackage.PkgInfo // Check for conflicts with installed packages
for i := len(allPackages) - 1; i >= 0; i-- { for _, installedPkg := range installedPackages {
info := allPackages[i] // Skip if package is to be removed
if info.Name == pkgInfo.Name { if slices.Contains(removedPackages, installedPkg.PkgInfo.Name) {
allPackages = append(allPackages[:i], allPackages[i+1:]...) continue
}
// Skip if same package
if pkgInfo.Name == installedPkg.PkgInfo.Name {
continue
}
// Check for new package conflicts
if slices.Contains(pkgInfo.Conflicts, installedPkg.PkgInfo.Name) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], installedPkg.PkgInfo.Name)
}
for _, vpkg := range installedPkg.PkgInfo.Provides {
if slices.Contains(pkgInfo.Conflicts, vpkg) {
conflicts[pkgInfo.Name] = append(conflicts[pkgInfo.Name], vpkg+" ("+installedPkg.PkgInfo.Name+")")
}
}
// Check for installed package conflicts
for _, vpkg := range pkgInfo.Provides {
if slices.Contains(installedPkg.PkgInfo.Conflicts, vpkg) {
conflicts[installedPkg.PkgInfo.Name] = append(conflicts[installedPkg.PkgInfo.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 { return conflicts
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
} }
func (operation *BPMOperation) ShowOperationSummary() { func (operation *BPMOperation) ShowOperationSummary() {
@@ -388,7 +429,7 @@ func (operation *BPMOperation) ShowOperationSummary() {
fmt.Println("Warning: Operating in " + operation.RootDir) fmt.Println("Warning: Operating in " + operation.RootDir)
} }
if operation.GetTotalDownloadSize() > 0 { 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 { 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)))
@@ -425,6 +466,40 @@ func (operation *BPMOperation) ShowSourcePackageContent() (sourcePackagesShown i
return sourcePackagesShown, nil 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 { func (operation *BPMOperation) RunHooks(verbose bool) error {
// Return if hooks directory does not exist // Return if hooks directory does not exist
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() { if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
+8 -8
View File
@@ -69,11 +69,11 @@ type PackageFileEntry struct {
OctalPerms uint32 OctalPerms uint32
UserID int UserID int
GroupID int GroupID int
SizeInBytes uint64 SizeInBytes int64
} }
func (pkg *BPMPackage) GetInstalledSize() uint64 { func (pkg *BPMPackage) GetInstalledSize() int64 {
var totalSize uint64 = 0 var totalSize int64 = 0
for _, entry := range pkg.PkgFiles { for _, entry := range pkg.PkgFiles {
totalSize += entry.SizeInBytes totalSize += entry.SizeInBytes
} }
@@ -232,7 +232,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@@ -604,7 +604,7 @@ func (bpmpkg *BPMPackage) CreateReadableInfo(rootDir string, humanReadableSize b
ret = append(ret, "Installation Reason: "+installationReasonString) ret = append(ret, "Installation Reason: "+installationReasonString)
} }
if bpmpkg.PkgInfo.Type == "binary" { if bpmpkg.PkgInfo.Type == "binary" {
installedSize := int64(bpmpkg.GetInstalledSize()) installedSize := bpmpkg.GetInstalledSize()
var installedSizeStr string var installedSizeStr string
if humanReadableSize { if humanReadableSize {
installedSizeStr = bytesToHumanReadable(installedSize) installedSizeStr = bytesToHumanReadable(installedSize)
@@ -624,7 +624,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
} }
// Initialize progress bar // 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() defer bar.Close()
tarballFile, err := readTarballFile(filename, "files.tar.gz") tarballFile, err := readTarballFile(filename, "files.tar.gz")
@@ -1039,12 +1039,12 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
return err return err
} }
bar := createProgressBar(int64(bpmpkg.GetInstalledSize()), "Removing "+bpmpkg.PkgInfo.Name, verbose) bar := createProgressBar(bpmpkg.GetInstalledSize(), "Removing "+bpmpkg.PkgInfo.Name, verbose)
defer bar.Close() defer bar.Close()
// Removing package files // Removing package files
for _, entry := range fileEntries { for _, entry := range fileEntries {
bar.Add64(int64(entry.SizeInBytes)) bar.Add64(entry.SizeInBytes)
file := path.Join(rootDir, entry.Path) file := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(file) lstat, err := os.Lstat(file)
if os.IsNotExist(err) { if os.IsNotExist(err) {
+31 -30
View File
@@ -18,20 +18,6 @@ type BPMLock struct {
path string 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) { func LockBPM(rootDir string) (*BPMLock, error) {
// Create parent directories if they don't already exist // Create parent directories if they don't already exist
err := os.MkdirAll(path.Join(rootDir, "/var/lib/bpm"), 0755) 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 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 { func GetArch() string {
uname := syscall.Utsname{} uname := syscall.Utsname{}
err := syscall.Uname(&uname) err := syscall.Uname(&uname)
@@ -69,6 +69,13 @@ func GetArch() string {
return string(byteString[:indexLength]) 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 { func createProgressBar(max int64, description string, hideBar bool) *progressbar.ProgressBar {
var output io.Writer var output io.Writer
if hideBar { if hideBar {
@@ -112,17 +119,6 @@ func stringSliceRemove(s []string, r string) []string {
return s return s
} }
func unsignedBytesToHumanReadable(b uint64) 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)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
func bytesToHumanReadable(b int64) string { func bytesToHumanReadable(b int64) string {
bf := float64(b) bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} { for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
@@ -134,9 +130,14 @@ func bytesToHumanReadable(b int64) string {
return fmt.Sprintf("%.1fYiB", bf) return fmt.Sprintf("%.1fYiB", bf)
} }
func CompareVersions(version1, version2 string) int { func removeDuplicates[T comparable](sliceList []T) []T {
v1 := version.NewVersion(version1) allKeys := make(map[T]bool)
v2 := version.NewVersion(version2) list := []T{}
for _, item := range sliceList {
return v1.Compare(v2) if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
} }