11 Commits
14 changed files with 675 additions and 269 deletions
+1
View File
@@ -9,6 +9,7 @@ require git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib v0.5.0
replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib replace git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib => ../bpmlib
require ( require (
github.com/drone/envsubst v1.0.3 // indirect
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+4
View File
@@ -1,3 +1,7 @@
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g= github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+253 -71
View File
@@ -21,7 +21,7 @@ import (
/* A simple-to-use package manager */ /* A simple-to-use package manager */
/* ------------------------------------------------------- */ /* ------------------------------------------------------- */
var bpmVer = "0.5.0" var bpmVer = "0.6.0"
var subcommand = "help" var subcommand = "help"
var subcommandArgs []string var subcommandArgs []string
@@ -44,12 +44,15 @@ var showDatabaseInfo = false
var installSrcPkgDepends = false var installSrcPkgDepends = false
var skipChecks = false var skipChecks = false
var outputDirectory = "" var outputDirectory = ""
var outputFd = -1
var cleanupDependencies = false var cleanupDependencies = false
var cleanupMakeDependencies = false var cleanupMakeDependencies = false
var cleanupCompilationFiles = false var cleanupCompilationFiles = false
var cleanupCompiledPackages = false var cleanupCompiledPackages = false
var cleanupFetchedPackages = false var cleanupFetchedPackages = false
var exitCode = 0
func main() { func main() {
err := bpmlib.ReadConfig() err := bpmlib.ReadConfig()
if err != nil { if err != nil {
@@ -57,6 +60,10 @@ func main() {
} }
resolveFlags() resolveFlags()
resolveCommand() resolveCommand()
if exitCode != 0 {
os.Exit(exitCode)
}
} }
type commandType uint8 type commandType uint8
@@ -120,7 +127,9 @@ func resolveCommand() {
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
for n, pkg := range packages { for n, pkg := range packages {
@@ -133,14 +142,18 @@ func resolveCommand() {
entry, _, err = bpmlib.GetDatabaseEntry(pkg) entry, _, err = bpmlib.GetDatabaseEntry(pkg)
if err != nil { if err != nil {
if entry = bpmlib.ResolveVirtualPackage(pkg); entry == nil { if entry = bpmlib.ResolveVirtualPackage(pkg); entry == nil {
log.Fatalf("Error: could not find package (%s) in any database\n", pkg) log.Printf("Error: could not find package (%s) in any database\n", pkg)
exitCode = 1
return
} }
} }
info = entry.Info info = entry.Info
} else if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() { } else 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.Fatalf("Error: could not read package: %s\n", err) log.Printf("Error: could not read package: %s\n", err)
exitCode = 1
return
} }
info = bpmpkg.PkgInfo info = bpmpkg.PkgInfo
isFile = true isFile = true
@@ -153,7 +166,9 @@ func resolveCommand() {
showInstallationReason = true showInstallationReason = true
} }
if info == nil { if info == nil {
log.Fatalf("Error: package (%s) is not installed\n", pkg) log.Printf("Error: package (%s) is not installed\n", pkg)
exitCode = 1
return
} }
if n != 0 { if n != 0 {
fmt.Println() fmt.Println()
@@ -161,7 +176,9 @@ func resolveCommand() {
if isFile { if isFile {
abs, err := filepath.Abs(pkg) abs, err := filepath.Abs(pkg)
if err != nil { if err != nil {
log.Fatalf("Error: could not get absolute path of file (%s)\n", abs) log.Printf("Error: could not get absolute path of file (%s)\n", abs)
exitCode = 1
return
} }
fmt.Println("File: " + abs) fmt.Println("File: " + abs)
} }
@@ -171,12 +188,15 @@ func resolveCommand() {
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
packages, err := bpmlib.GetInstalledPackages(rootDir) packages, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil { if err != nil {
log.Fatalf("Error: could not get installed packages: %s", err.Error()) log.Printf("Error: could not get installed packages: %s", err.Error())
exitCode = 1
return return
} }
if pkgListNumbers { if pkgListNumbers {
@@ -205,19 +225,23 @@ func resolveCommand() {
case search: case search:
searchTerms := subcommandArgs searchTerms := subcommandArgs
if len(searchTerms) == 0 { if len(searchTerms) == 0 {
log.Fatalf("Error: no search terms given") log.Printf("Error: no search terms given")
exitCode = 1
return
} }
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
for i, term := range searchTerms { for i, term := range searchTerms {
nameResults := make([]*bpmlib.PackageInfo, 0) nameResults := make([]*bpmlib.PackageInfo, 0)
descResults := make([]*bpmlib.PackageInfo, 0) descResults := make([]*bpmlib.PackageInfo, 0)
for _, db := range bpmlib.MainBPMConfig.Databases { for _, db := range bpmlib.BPMDatabases {
for _, entry := range db.Entries { for _, entry := range db.Entries {
if strings.Contains(entry.Info.Name, term) { if strings.Contains(entry.Info.Name, term) {
nameResults = append(nameResults, entry.Info) nameResults = append(nameResults, entry.Info)
@@ -228,7 +252,9 @@ func resolveCommand() {
} }
results := append(nameResults, descResults...) results := append(nameResults, descResults...)
if len(results) == 0 { if len(results) == 0 {
log.Fatalf("Error: no results for term (%s) were found\n", term) log.Printf("Error: no results for term (%s) were found\n", term)
exitCode = 1
return
} }
if i > 0 { if i > 0 {
fmt.Println() fmt.Println()
@@ -241,7 +267,9 @@ func resolveCommand() {
case install: case install:
// Check for required permissions // Check for required permissions
if os.Getuid() != 0 { if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions") log.Printf("Error: this subcommand needs to be run with superuser permissions")
exitCode = 1
return
} }
// Return if no packages are specified // Return if no packages are specified
@@ -261,7 +289,9 @@ func resolveCommand() {
ir = bpmlib.InstallationReasonMakeDependency ir = bpmlib.InstallationReasonMakeDependency
case "": case "":
default: default:
log.Fatalf("Error: %s is not a valid installation reason", installationReason) log.Printf("Error: %s is not a valid installation reason", installationReason)
exitCode = 1
return
} }
// Get reinstall method // Get reinstall method
@@ -274,18 +304,33 @@ func resolveCommand() {
reinstallMethod = bpmlib.ReinstallMethodNone reinstallMethod = bpmlib.ReinstallMethodNone
} }
// Read local databases // Create BPM Lock file
err := bpmlib.ReadLocalDatabaseFiles() fileLock, err := bpmlib.LockBPM(rootDir)
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not create BPM lock file: %s", err)
exitCode = 1
return
}
defer fileLock.Unlock()
// Read local databases
err = bpmlib.ReadLocalDatabaseFiles()
if err != nil {
log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
// Create installation operation // Create installation operation
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, installOptional, force, verbose, subcommandArgs...) operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallMethod, installOptional, force, verbose, subcommandArgs...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1
return
} else if err != nil { } else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err) log.Printf("Error: could not setup operation: %s\n", err)
exitCode = 1
return
} }
// Exit if operation contains no actions // Exit if operation contains no actions
@@ -309,42 +354,62 @@ func resolveCommand() {
text, _ := reader.ReadString('\n') text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package installation...") fmt.Println("Cancelling package installation...")
os.Exit(1) exitCode = 1
return
} }
} }
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err) log.Printf("Error: could not complete operation: %s\n", err)
exitCode = 1
return
} }
// Executing hooks // Executing hooks
fmt.Println("Running hooks...") fmt.Println("Running hooks...")
err = operation.RunHooks(verbose) err = operation.RunHooks(verbose)
if err != nil { if err != nil {
log.Fatalf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run hooks: %s\n", err)
exitCode = 1
return
} }
case update: case update:
// Check for required permissions // Check for required permissions
if os.Getuid() != 0 { if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions") log.Printf("Error: this subcommand needs to be run with superuser permissions")
exitCode = 1
return
} }
// Create BPM Lock file
fileLock, err := bpmlib.LockBPM(rootDir)
if err != nil {
log.Printf("Error: could not create BPM lock file: %s", err)
exitCode = 1
return
}
defer fileLock.Unlock()
// Read local databases if no sync // Read local databases if no sync
if nosync { if nosync {
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
} }
// Create update operation // Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !nosync, installOptional, force, verbose) operation, err := bpmlib.UpdatePackages(rootDir, !nosync, installOptional, force, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1
return
} else if err != nil { } else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err) log.Printf("Error: could not setup operation: %s\n", err)
} }
// Exit if operation contains no actions // Exit if operation contains no actions
@@ -363,28 +428,44 @@ func resolveCommand() {
text, _ := reader.ReadString('\n') text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package update...") fmt.Println("Cancelling package update...")
os.Exit(1) exitCode = 1
return
} }
} }
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err) log.Printf("Error: could not complete operation: %s\n", err)
exitCode = 1
return
} }
// Executing hooks // Executing hooks
fmt.Println("Running hooks...") fmt.Println("Running hooks...")
err = operation.RunHooks(verbose) err = operation.RunHooks(verbose)
if err != nil { if err != nil {
log.Fatalf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run hooks: %s\n", err)
exitCode = 1
return
} }
case sync: case sync:
// Check for required permissions // Check for required permissions
if os.Getuid() != 0 { if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions") log.Printf("Error: this subcommand needs to be run with superuser permissions")
exitCode = 1
return
} }
// Create BPM Lock file
fileLock, err := bpmlib.LockBPM(rootDir)
if err != nil {
log.Printf("Error: could not create BPM lock file: %s", err)
exitCode = 1
return
}
defer fileLock.Unlock()
// Confirmation Prompt // Confirmation Prompt
if !yesAll { if !yesAll {
fmt.Printf("Are you sure you wish to sync all databases? [y\\N] ") fmt.Printf("Are you sure you wish to sync all databases? [y\\N] ")
@@ -392,21 +473,26 @@ func resolveCommand() {
text, _ := reader.ReadString('\n') text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling database synchronization...") fmt.Println("Cancelling database synchronization...")
os.Exit(1) exitCode = 1
return
} }
} }
// Sync databases // Sync databases
err := bpmlib.SyncDatabase(verbose) err = bpmlib.SyncDatabase(verbose)
if err != nil { if err != nil {
log.Fatalf("Error: could not sync local database: %s\n", err) log.Printf("Error: could not sync local database: %s\n", err)
exitCode = 1
return
} }
fmt.Println("All package databases synced successfully!") fmt.Println("All package databases synced successfully!")
case remove: case remove:
// Check for required permissions // Check for required permissions
if os.Getuid() != 0 { if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions") log.Printf("Error: this subcommand needs to be run with superuser permissions")
exitCode = 1
return
} }
if len(subcommandArgs) == 0 { if len(subcommandArgs) == 0 {
@@ -414,18 +500,33 @@ func resolveCommand() {
return return
} }
// Read local databases // Create BPM Lock file
err := bpmlib.ReadLocalDatabaseFiles() fileLock, err := bpmlib.LockBPM(rootDir)
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not create BPM lock file: %s", err)
exitCode = 1
return
}
defer fileLock.Unlock()
// Read local databases
err = bpmlib.ReadLocalDatabaseFiles()
if err != nil {
log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
// Create remove operation // Create remove operation
operation, err := bpmlib.RemovePackages(rootDir, removeUnused, doCleanup, subcommandArgs...) operation, err := bpmlib.RemovePackages(rootDir, removeUnused, doCleanup, subcommandArgs...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1
return
} else if err != nil { } else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err) log.Printf("Error: could not setup operation: %s\n", err)
exitCode = 1
return
} }
// Exit if operation contains no actions // Exit if operation contains no actions
@@ -444,46 +545,70 @@ func resolveCommand() {
text, _ := reader.ReadString('\n') text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package removal...") fmt.Println("Cancelling package removal...")
os.Exit(1) exitCode = 1
return
} }
} }
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err) log.Printf("Error: could not complete operation: %s\n", err)
exitCode = 1
return
} }
// Executing hooks // Executing hooks
fmt.Println("Running hooks...") fmt.Println("Running hooks...")
err = operation.RunHooks(verbose) err = operation.RunHooks(verbose)
if err != nil { if err != nil {
log.Fatalf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run hooks: %s\n", err)
exitCode = 1
return
} }
case cleanup: case cleanup:
// Check for required permissions // Check for required permissions
if os.Getuid() != 0 { if os.Getuid() != 0 {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions") log.Printf("Error: this subcommand needs to be run with superuser permissions")
exitCode = 1
return
} }
err := bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose) // Create BPM Lock file
fileLock, err := bpmlib.LockBPM(rootDir)
if err != nil { if err != nil {
log.Fatalf("Error: could not complete cache cleanup: %s", err) log.Printf("Error: could not create BPM lock file: %s", err)
exitCode = 1
return
}
defer fileLock.Unlock()
err = bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose)
if err != nil {
log.Printf("Error: could not complete cache cleanup: %s", err)
exitCode = 1
return
} }
if cleanupDependencies || cleanupMakeDependencies { if cleanupDependencies || cleanupMakeDependencies {
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
// Create cleanup operation // Create cleanup operation
operation, err := bpmlib.CleanupPackages(cleanupMakeDependencies, rootDir) operation, err := bpmlib.CleanupPackages(cleanupMakeDependencies, rootDir)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1
return
} else if err != nil { } else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err) log.Printf("Error: could not setup operation: %s\n", err)
exitCode = 1
return
} }
// Exit if operation contains no actions // Exit if operation contains no actions
@@ -502,21 +627,26 @@ func resolveCommand() {
text, _ := reader.ReadString('\n') text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" { if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package removal...") fmt.Println("Cancelling package removal...")
os.Exit(1) exitCode = 1
return
} }
} }
// Execute operation // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err) log.Printf("Error: could not complete operation: %s\n", err)
exitCode = 1
return
} }
// Executing hooks // Executing hooks
fmt.Println("Running hooks...") fmt.Println("Running hooks...")
err = operation.RunHooks(verbose) err = operation.RunHooks(verbose)
if err != nil { if err != nil {
log.Fatalf("Error: could not run hooks: %s\n", err) log.Printf("Error: could not run hooks: %s\n", err)
exitCode = 1
return
} }
} }
case file: case file:
@@ -528,23 +658,33 @@ func resolveCommand() {
for _, file := range files { for _, file := range files {
absFile, err := filepath.Abs(file) absFile, err := filepath.Abs(file)
if err != nil { if err != nil {
log.Fatalf("Error: could not get absolute path of file (%s)\n", file) log.Printf("Error: could not get absolute path of file (%s)\n", file)
exitCode = 1
return
} }
stat, err := os.Stat(absFile) stat, err := os.Stat(absFile)
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Fatalf("Error: file (%s) does not exist!\n", absFile) log.Printf("Error: file (%s) does not exist!\n", absFile)
exitCode = 1
return
} }
pkgs, err := bpmlib.GetInstalledPackages(rootDir) pkgs, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil { if err != nil {
log.Fatalf("Error: could not get installed packages: %s\n", err.Error()) log.Printf("Error: could not get installed packages: %s\n", err.Error())
exitCode = 1
return
} }
if !strings.HasPrefix(absFile, rootDir) { if !strings.HasPrefix(absFile, rootDir) {
log.Fatalf("Error: could not get path of file (%s) relative to root path", absFile) log.Printf("Error: could not get path of file (%s) relative to root path", absFile)
exitCode = 1
return
} }
absFile, err = filepath.Rel(rootDir, absFile) absFile, err = filepath.Rel(rootDir, absFile)
if err != nil { if err != nil {
log.Fatalf("Error: could not get path of file (%s) relative to root path", absFile) log.Printf("Error: could not get path of file (%s) relative to root path", absFile)
exitCode = 1
return
} }
absFile = strings.TrimPrefix(absFile, "/") absFile = strings.TrimPrefix(absFile, "/")
if stat.IsDir() { if stat.IsDir() {
@@ -577,24 +717,32 @@ func resolveCommand() {
// Read local databases // Read local databases
err := bpmlib.ReadLocalDatabaseFiles() err := bpmlib.ReadLocalDatabaseFiles()
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Printf("Error: could not read local databases: %s", err)
exitCode = 1
return
} }
// Compile packages // Compile packages
for _, sourcePackage := range subcommandArgs { for _, sourcePackage := range subcommandArgs {
if _, err := os.Stat(sourcePackage); os.IsNotExist(err) { if _, err := os.Stat(sourcePackage); os.IsNotExist(err) {
log.Fatalf("Error: file (%s) does not exist!", sourcePackage) log.Printf("Error: file (%s) does not exist!", sourcePackage)
exitCode = 1
return
} }
// Read archive // Read archive
bpmpkg, err := bpmlib.ReadPackage(sourcePackage) bpmpkg, err := bpmlib.ReadPackage(sourcePackage)
if err != nil { if err != nil {
log.Fatalf("Could not read package (%s): %s", sourcePackage, err) log.Printf("Could not read package (%s): %s", sourcePackage, err)
exitCode = 1
return
} }
// Ensure archive is source BPM package // Ensure archive is source BPM package
if bpmpkg.PkgInfo.Type != "source" { if bpmpkg.PkgInfo.Type != "source" {
log.Fatalf("Error: cannot compile a non-source package!") log.Printf("Error: cannot compile a non-source package!")
exitCode = 1
return
} }
// Get direct runtime and make dependencies // Get direct runtime and make dependencies
@@ -609,7 +757,9 @@ func resolveCommand() {
unmetDepends := slices.Clone(totalDepends) unmetDepends := slices.Clone(totalDepends)
installedPackages, err := bpmlib.GetInstalledPackages("/") installedPackages, err := bpmlib.GetInstalledPackages("/")
if err != nil { if err != nil {
log.Fatalf("Error: could not get installed packages: %s\n", err) log.Printf("Error: could not get installed packages: %s\n", err)
exitCode = 1
return
} }
for i := len(unmetDepends) - 1; i >= 0; i-- { for i := len(unmetDepends) - 1; i >= 0; i-- {
if slices.Contains(installedPackages, unmetDepends[i]) { if slices.Contains(installedPackages, unmetDepends[i]) {
@@ -624,7 +774,9 @@ func resolveCommand() {
// Get path to current executable // Get path to current executable
executable, err := os.Executable() executable, err := os.Executable()
if err != nil { if err != nil {
log.Fatalf("Error: could not get path to executable: %s\n", err) log.Printf("Error: could not get path to executable: %s\n", err)
exitCode = 1
return
} }
// Run 'bpm install' using the set privilege escalator command // Run 'bpm install' using the set privilege escalator command
@@ -642,25 +794,33 @@ func resolveCommand() {
} }
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
log.Fatalf("Error: dependency installation command failed: %s\n", err) log.Printf("Error: dependency installation command failed: %s\n", err)
exitCode = 1
return
} }
} else { } else {
// Ensure the required dependencies are installed // Ensure the required dependencies are installed
if len(unmetDepends) != 0 { if len(unmetDepends) != 0 {
log.Fatalf("Error: could not resolve dependencies: the following dependencies were not found in any databases: " + strings.Join(unmetDepends, ", ")) log.Printf("Error: could not resolve dependencies: the following dependencies were not found in any databases: " + strings.Join(unmetDepends, ", "))
exitCode = 1
return
} }
} }
// Get current working directory // Get current working directory
workdir, err := os.Getwd() workdir, err := os.Getwd()
if err != nil { if err != nil {
log.Fatalf("Error: could not get working directory: %s", err) log.Printf("Error: could not get working directory: %s", err)
exitCode = 1
return
} }
// Get user home directory // Get user home directory
homedir, err := os.UserHomeDir() homedir, err := os.UserHomeDir()
if err != nil { if err != nil {
log.Fatalf("Error: could not get user home directory: %s", err) log.Printf("Error: could not get user home directory: %s", err)
exitCode = 1
return
} }
// Trim output directory // Trim output directory
@@ -690,19 +850,35 @@ func resolveCommand() {
// Ensure output directory exists and is a directory // Ensure output directory exists and is a directory
stat, err := os.Stat(outputDirectory) stat, err := os.Stat(outputDirectory)
if err != nil { if err != nil {
log.Fatalf("Error: could not stat output directory (%s): %s", outputDirectory, err) log.Printf("Error: could not stat output directory (%s): %s", outputDirectory, err)
exitCode = 1
return
} }
if !stat.IsDir() { if !stat.IsDir() {
log.Fatalf("Error: output directory (%s) is not a directory", outputDirectory) log.Printf("Error: output directory (%s) is not a directory", outputDirectory)
exitCode = 1
return
} }
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks) outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, skipChecks)
if err != nil { if err != nil {
log.Fatalf("Error: could not compile source package (%s): %s", sourcePackage, err) log.Printf("Error: could not compile source package (%s): %s", sourcePackage, err)
exitCode = 1
return
} }
for k, v := range outputBpmPackages { for k, v := range outputBpmPackages {
if outputFd < 0 {
fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v) fmt.Printf("Package (%s) was successfully compiled! Binary package generated at: %s\n", k, v)
} else {
f := os.NewFile(uintptr(outputFd), "output_file_descrptor")
defer f.Close()
if f == nil {
log.Printf("Warning: invalid file descriptor: %d", outputFd)
break
}
fmt.Fprintln(f, v)
}
} }
// Remove unused packages // Remove unused packages
@@ -710,7 +886,9 @@ func resolveCommand() {
// Get path to current executable // Get path to current executable
executable, err := os.Executable() executable, err := os.Executable()
if err != nil { if err != nil {
log.Fatalf("Error: could not get path to executable: %s\n", err) log.Printf("Error: could not get path to executable: %s\n", err)
exitCode = 1
return
} }
// Run 'bpm cleanup' using the set privilege escalator command // Run 'bpm cleanup' using the set privilege escalator command
@@ -726,7 +904,9 @@ func resolveCommand() {
} }
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
log.Fatalf("Error: dependency cleanup command failed: %s\n", err) log.Printf("Error: dependency cleanup command failed: %s\n", err)
exitCode = 1
return
} }
} }
} }
@@ -793,6 +973,7 @@ func printHelp() {
fmt.Println(" -s skips the check function in source.sh scripts") fmt.Println(" -s skips the check function in source.sh scripts")
fmt.Println(" -o sets output directory") fmt.Println(" -o sets output directory")
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" --fd=<file descriptor> Set the file descriptor output package names will be written to")
fmt.Println("\033[1m----------------\033[0m") fmt.Println("\033[1m----------------\033[0m")
} }
@@ -862,6 +1043,7 @@ func resolveFlags() {
compileFlagSet.BoolVar(&installSrcPkgDepends, "d", false, "Install required dependencies for package compilation") compileFlagSet.BoolVar(&installSrcPkgDepends, "d", false, "Install required dependencies for package compilation")
compileFlagSet.BoolVar(&skipChecks, "s", false, "Skip the check function in source.sh scripts") compileFlagSet.BoolVar(&skipChecks, "s", false, "Skip the check function in source.sh scripts")
compileFlagSet.StringVar(&outputDirectory, "o", "", "Set output directory") compileFlagSet.StringVar(&outputDirectory, "o", "", "Set output directory")
compileFlagSet.IntVar(&outputFd, "fd", -1, "Set the file descriptor output package names will be written to")
compileFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing") compileFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
compileFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") compileFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
compileFlagSet.Usage = printHelp compileFlagSet.Usage = printHelp
+182 -48
View File
@@ -1,15 +1,20 @@
package bpmlib package bpmlib
import ( import (
"crypto/sha256"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"slices"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"github.com/drone/envsubst"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -31,12 +36,6 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return nil, errors.New("cannot compile a non-source package") return nil, errors.New("cannot compile a non-source package")
} }
// Read compilation options file in current directory
compilationOptions, err := readCompilationOptionsFile()
if err != nil {
return nil, err
}
// Get HOME directory // Get HOME directory
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
@@ -122,6 +121,12 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return nil, err return nil, err
} }
// Download files
err = downloadPackageFiles(bpmpkg.PkgInfo, tempDirectory)
if err != nil {
return nil, err
}
// Setup environment for commands // Setup environment for commands
env := os.Environ() env := os.Environ()
env = append(env, "HOME="+tempDirectory) env = append(env, "HOME="+tempDirectory)
@@ -131,12 +136,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name) env = append(env, "BPM_PKG_NAME="+bpmpkg.PkgInfo.Name)
env = append(env, "BPM_PKG_VERSION="+bpmpkg.PkgInfo.Version) 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_REVISION="+strconv.Itoa(bpmpkg.PkgInfo.Revision))
// Check for architecture override in compilation options env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch)
if val, ok := compilationOptions["ARCH"]; ok {
env = append(env, "BPM_PKG_ARCH="+val)
} else {
env = append(env, "BPM_PKG_ARCH="+GetArch())
}
env = append(env, CompilationBPMConfig.CompilationEnvironment...) env = append(env, CompilationBPMConfig.CompilationEnvironment...)
// Execute prepare and build functions in source.sh script // Execute prepare and build functions in source.sh script
@@ -259,14 +259,12 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
pkgInfo.Type = "binary" pkgInfo.Type = "binary"
// Set package architecture // Set package architecture
if val, ok := compilationOptions["ARCH"]; ok { pkgInfo.Arch = pkg.OutputArch
pkgInfo.Arch = val pkgInfo.OutputArch = ""
} else {
pkgInfo.Arch = GetArch()
}
// Remove split package field // Remove split package and downloads fields
pkgInfo.SplitPackages = nil pkgInfo.SplitPackages = nil
pkgInfo.Downloads = nil
// Marshal package info // Marshal package info
pkgInfoBytes, err := yaml.Marshal(pkgInfo) pkgInfoBytes, err := yaml.Marshal(pkgInfo)
@@ -338,46 +336,182 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bo
return outputBpmPackages, nil return outputBpmPackages, nil
} }
func readCompilationOptionsFile() (options map[string]string, err error) { func downloadPackageFiles(pkgInfo *PackageInfo, tempDirectory string) error {
// Initialize options map for _, download := range pkgInfo.Downloads {
options = make(map[string]string) // 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_SOURCE":
return path.Join(tempDirectory, "source/")
default:
return ""
}
}
// Check if file compilation options file exists downloadUrl, err := envsubst.Eval(strings.TrimSpace(download.Url), replaceVars)
stat, err := os.Stat(".compilation-options")
if err != nil { if err != nil {
return nil, nil return err
} }
extractTo, err := envsubst.Eval(strings.TrimSpace(download.ExtractTo), replaceVars)
// Ensure it is a regular file
if !stat.Mode().IsRegular() {
return nil, fmt.Errorf("%s is not a regular file", stat.Name())
}
// Read file data
data, err := os.ReadFile(stat.Name())
if err != nil { if err != nil {
return nil, err return err
}
cloneTo, err := envsubst.Eval(strings.TrimSpace(download.CloneTo), replaceVars)
if err != nil {
return err
} }
for _, line := range strings.Split(string(data), "\n") { // Make relative paths absolute
// Trim line if extractTo != "" && extractTo[0] != '/' {
line = strings.TrimSpace(line) extractTo = path.Join(tempDirectory, extractTo)
}
// Skip empty lines // Make relative paths absolute
if line == "" { if cloneTo != "" && cloneTo[0] != '/' {
continue cloneTo = path.Join(tempDirectory, cloneTo)
} }
// Split line switch download.Type {
split := strings.SplitN(line, "=", 2) case "", "file":
filepath := path.Join(tempDirectory, path.Base(downloadUrl))
// Throw error if line isn't valid if download.Filepath != "" && download.Filepath[0] != '/' {
if len(split) < 2 { filepath = path.Join(tempDirectory, download.Filepath)
return nil, fmt.Errorf("invalid line in compilation-options file: '%s'", line)
} }
options[split[0]] = split[1] err := downloadFile(downloadUrl, filepath, 0644)
if err != nil {
return err
} }
return options, nil if download.Checksum != "skip" {
f, err := os.Open(filepath)
if err != nil {
return err
}
defer f.Close()
h := sha256.New()
_, err = io.Copy(h, f)
if err != nil {
return err
}
if hex.EncodeToString(h.Sum(nil)) != download.Checksum {
fmt.Printf("Downloaded file checksum: %x\n", h.Sum(nil))
return fmt.Errorf("downloaded file checksums did not match")
}
} else {
fmt.Println("Skipping checksum checking...")
}
if !download.NoExtract && (strings.Contains(filepath, ".tar") || strings.HasSuffix(filepath, ".tgz")) {
cmd := exec.Command("tar", "xvf", filepath, "--strip-components="+strconv.Itoa(download.ExtractStripComponents))
cmd.Dir = tempDirectory
if extractTo != "" {
err := os.MkdirAll(extractTo, 0755)
if err != nil {
return err
}
cmd.Args = append(cmd.Args, "-C", extractTo)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
} else if !download.NoExtract && strings.HasSuffix(filepath, ".zip") {
cmd := exec.Command("unzip", filepath)
if extractTo != "" {
err := os.MkdirAll(extractTo, 0755)
if err != nil {
return err
}
cmd.Args = append(cmd.Args, "-d", extractTo)
} else {
err := os.Mkdir(path.Join(tempDirectory, strings.TrimSuffix(path.Base(filepath), ".zip")), 0755)
if err != nil {
return err
}
cmd.Args = append(cmd.Args, "-d", path.Join(tempDirectory, strings.TrimSuffix(path.Base(filepath), ".zip")))
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}
}
case "git":
// Replace variables in git branch
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
default:
return ""
}
})
if err != nil {
return err
}
cmd := exec.Command("git", "clone", "--depth=1", downloadUrl)
cmd.Dir = tempDirectory
if gitBranch != "" {
cmd.Args = slices.Insert(cmd.Args, len(cmd.Args)-1, "--branch="+gitBranch)
}
if cloneTo != "" {
err := os.MkdirAll(cloneTo, 0755)
if err != nil {
return err
}
cmd.Args = append(cmd.Args, cloneTo)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
if download.Checksum != "skip" {
cmd := exec.Command("git", "rev-parse", "HEAD")
if cloneTo != "" {
cmd.Dir = cloneTo
} else {
cmd.Dir = path.Join(tempDirectory, strings.TrimSuffix(path.Base(downloadUrl), ".git"))
}
branchChecksum, err := cmd.Output()
if err != nil {
return err
}
if strings.TrimSpace(string(branchChecksum)) != download.Checksum {
fmt.Printf("Git branch checksum: %s\n", strings.TrimSpace(string(branchChecksum)))
return fmt.Errorf("Cloned git repository checksum did not match")
}
} else {
fmt.Println("Skipping checksum checking...")
}
default:
return fmt.Errorf("unknown download type (%s)", download.Type)
}
}
return nil
} }
+7 -1
View File
@@ -9,7 +9,13 @@ import (
type MainBPMConfigStruct struct { type MainBPMConfigStruct struct {
IgnorePackages []string `yaml:"ignore_packages"` IgnorePackages []string `yaml:"ignore_packages"`
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"` CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
Databases []*BPMDatabase `yaml:"databases"` Databases []configDatabase `yaml:"databases"`
}
type configDatabase struct {
Name string `yaml:"name"`
Source string `yaml:"source"`
Disabled *bool `yaml:"disabled"`
} }
type CompilationBPMConfigStruct struct { type CompilationBPMConfigStruct struct {
+46 -79
View File
@@ -3,8 +3,6 @@ package bpmlib
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
@@ -14,27 +12,28 @@ import (
) )
type BPMDatabase struct { type BPMDatabase struct {
Name string `yaml:"name"` DatabaseVersion int `yaml:"database_version"`
Source string `yaml:"source"` Entries map[string]*BPMDatabaseEntry `yaml:"entries"`
Disabled *bool `yaml:"disabled"`
Entries map[string]*BPMDatabaseEntry
VirtualPackages map[string][]string VirtualPackages map[string][]string
Source string
} }
type BPMDatabaseEntry struct { type BPMDatabaseEntry struct {
Info *PackageInfo `yaml:"info"` Info *PackageInfo `yaml:"info"`
Download string `yaml:"download"` Filepath string `yaml:"filepath"`
DownloadSize uint64 `yaml:"download_size"` DownloadSize uint64 `yaml:"download_size"`
InstalledSize uint64 `yaml:"installed_size"` InstalledSize uint64 `yaml:"installed_size"`
Database *BPMDatabase Database *BPMDatabase
} }
var BPMDatabases = make(map[string]*BPMDatabase)
func (db *BPMDatabase) ContainsPackage(pkg string) bool { func (db *BPMDatabase) ContainsPackage(pkg string) bool {
_, ok := db.Entries[pkg] _, ok := db.Entries[pkg]
return ok return ok
} }
func (db *BPMDatabase) ReadLocalDatabase() error { func (db *configDatabase) ReadLocalDatabase() error {
dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb" dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb"
if _, err := os.Stat(dbFile); err != nil { if _, err := os.Stat(dbFile); err != nil {
return nil return nil
@@ -45,37 +44,23 @@ func (db *BPMDatabase) ReadLocalDatabase() error {
return err return err
} }
data := string(bytes) // Unmarshal yaml
for _, b := range strings.Split(data, "---") { database := &BPMDatabase{}
entry := BPMDatabaseEntry{ err = yaml.Unmarshal(bytes, database)
Info: &PackageInfo{
Name: "",
Description: "",
Version: "",
Revision: 1,
Url: "",
License: "",
Arch: "",
Type: "",
Keep: make([]string, 0),
Depends: make([]string, 0),
MakeDepends: make([]string, 0),
OptionalDepends: make([]string, 0),
Conflicts: make([]string, 0),
Provides: make([]string, 0),
},
Download: "",
DownloadSize: 0,
InstalledSize: 0,
Database: db,
}
err := yaml.Unmarshal([]byte(b), &entry)
if err != nil { if err != nil {
return err return err
} }
// Create database entries // Initialize struct values
database.VirtualPackages = make(map[string][]string)
database.Source = db.Source
entriesToRemove := make([]string, 0)
for entryName, entry := range database.Entries {
entry.Database = database
if entry.Info.IsSplitPackage() { if entry.Info.IsSplitPackage() {
// Handle split packages
for _, splitPkg := range entry.Info.SplitPackages { for _, splitPkg := range entry.Info.SplitPackages {
// Turn split package into json data // Turn split package into json data
splitPkgJson, err := yaml.Marshal(splitPkg) splitPkgJson, err := yaml.Marshal(splitPkg)
@@ -101,35 +86,41 @@ func (db *BPMDatabase) ReadLocalDatabase() error {
splitPkgClone.Url = entry.Info.Url splitPkgClone.Url = entry.Info.Url
// Create entry for split package // Create entry for split package
db.Entries[splitPkg.Name] = &BPMDatabaseEntry{ database.Entries[splitPkg.Name] = &BPMDatabaseEntry{
Info: &splitPkgClone, Info: &splitPkgClone,
Download: entry.Download, Filepath: entry.Filepath,
DownloadSize: entry.DownloadSize, DownloadSize: entry.DownloadSize,
InstalledSize: 0, InstalledSize: 0,
Database: db, Database: database,
} }
// Add virtual packages to database // Add virtual packages to database
for _, p := range splitPkg.Provides { for _, p := range splitPkg.Provides {
db.VirtualPackages[p] = append(db.VirtualPackages[p], splitPkg.Name) database.VirtualPackages[p] = append(database.VirtualPackages[p], splitPkg.Name)
} }
// Add current entry to list for removal
entriesToRemove = append(entriesToRemove, entryName)
} }
} else { } else {
// Create entry for package
db.Entries[entry.Info.Name] = &entry
// Add virtual packages to database // Add virtual packages to database
for _, p := range entry.Info.Provides { for _, p := range entry.Info.Provides {
db.VirtualPackages[p] = append(db.VirtualPackages[p], entry.Info.Name) database.VirtualPackages[p] = append(database.VirtualPackages[p], entry.Info.Name)
}
} }
} }
// Remove entries
for _, entryName := range entriesToRemove {
delete(database.Entries, entryName)
} }
BPMDatabases[db.Name] = database
return nil return nil
} }
func (db *BPMDatabase) SyncLocalDatabaseFile() error { func (db *configDatabase) SyncLocalDatabaseFile() error {
dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb" dbFile := "/var/lib/bpm/databases/" + db.Name + ".bpmdb"
// Get URL to database // Get URL to database
@@ -139,14 +130,10 @@ func (db *BPMDatabase) SyncLocalDatabaseFile() error {
} }
// Retrieve data from URL // Retrieve data from URL
resp, err := http.Get(u) buffer, err := retrieveUrlData(u)
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close()
// Load data into byte buffer
buffer, err := io.ReadAll(resp.Body)
// Unmarshal data to ensure it is a valid BPM database // Unmarshal data to ensure it is a valid BPM database
err = yaml.Unmarshal(buffer, &BPMDatabase{}) err = yaml.Unmarshal(buffer, &BPMDatabase{})
@@ -174,10 +161,6 @@ func (db *BPMDatabase) SyncLocalDatabaseFile() error {
func ReadLocalDatabaseFiles() (err error) { func ReadLocalDatabaseFiles() (err error) {
for _, db := range MainBPMConfig.Databases { for _, db := range MainBPMConfig.Databases {
// Initialize struct values
db.Entries = make(map[string]*BPMDatabaseEntry)
db.VirtualPackages = make(map[string][]string)
// Read database // Read database
err = db.ReadLocalDatabase() err = db.ReadLocalDatabase()
if err != nil { if err != nil {
@@ -188,15 +171,6 @@ func ReadLocalDatabaseFiles() (err error) {
return nil return nil
} }
func GetDatabase(name string) *BPMDatabase {
for _, db := range MainBPMConfig.Databases {
if db.Name == name {
return db
}
}
return nil
}
func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) { func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) {
split := strings.Split(str, "/") split := strings.Split(str, "/")
if len(split) == 1 { if len(split) == 1 {
@@ -204,7 +178,7 @@ func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) {
if pkgName == "" { if pkgName == "" {
return nil, nil, errors.New("could not find database entry for this package") return nil, nil, errors.New("could not find database entry for this package")
} }
for _, db := range MainBPMConfig.Databases { for _, db := range BPMDatabases {
if db.ContainsPackage(pkgName) { if db.ContainsPackage(pkgName) {
return db.Entries[pkgName], db, nil return db.Entries[pkgName], db, nil
} }
@@ -216,7 +190,7 @@ func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) {
if dbName == "" || pkgName == "" { if dbName == "" || pkgName == "" {
return nil, nil, errors.New("could not find database entry for this package") return nil, nil, errors.New("could not find database entry for this package")
} }
db := GetDatabase(dbName) db := BPMDatabases[dbName]
if db == nil || !db.ContainsPackage(pkgName) { if db == nil || !db.ContainsPackage(pkgName) {
return nil, nil, errors.New("could not find database entry for this package") return nil, nil, errors.New("could not find database entry for this package")
} }
@@ -227,7 +201,7 @@ func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) {
} }
func FindReplacement(pkg string) *BPMDatabaseEntry { func FindReplacement(pkg string) *BPMDatabaseEntry {
for _, db := range MainBPMConfig.Databases { for _, db := range BPMDatabases {
for _, entry := range db.Entries { for _, entry := range db.Entries {
for _, replaced := range entry.Info.Replaces { for _, replaced := range entry.Info.Replaces {
if replaced == pkg { if replaced == pkg {
@@ -241,7 +215,7 @@ func FindReplacement(pkg string) *BPMDatabaseEntry {
} }
func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry { func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry {
for _, db := range MainBPMConfig.Databases { for _, db := range BPMDatabases {
if v, ok := db.VirtualPackages[vpkg]; ok { if v, ok := db.VirtualPackages[vpkg]; ok {
for _, pkg := range v { for _, pkg := range v {
return db.Entries[pkg] return db.Entries[pkg]
@@ -253,30 +227,23 @@ func ResolveVirtualPackage(vpkg string) *BPMDatabaseEntry {
} }
func (db *BPMDatabase) FetchPackage(pkg string) (string, error) { func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
// Check if package exists in database
if !db.ContainsPackage(pkg) { if !db.ContainsPackage(pkg) {
return "", errors.New("could not fetch package '" + pkg + "'") return "", errors.New("could not fetch package '" + pkg + "'")
} }
// Get package url from database
entry := db.Entries[pkg] entry := db.Entries[pkg]
URL, err := url.JoinPath(db.Source, entry.Download) u, err := url.JoinPath(db.Source, entry.Filepath)
if err != nil { if err != nil {
return "", err return "", err
} }
resp, err := http.Get(URL)
if err != nil {
return "", err
}
defer resp.Body.Close()
err = os.MkdirAll("/var/cache/bpm/fetched/", 0755) // Download package from url
err = downloadFile(u, path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), 0644)
if err != nil { if err != nil {
return "", err return "", err
} }
out, err := os.Create("/var/cache/bpm/fetched/" + path.Base(entry.Download))
if err != nil {
return "", err
}
defer out.Close()
_, err = io.Copy(out, resp.Body) return path.Join("/var/cache/bpm/fetched/", path.Base(entry.Filepath)), nil
return "/var/cache/bpm/fetched/" + path.Base(entry.Download), nil
} }
+5 -5
View File
@@ -164,14 +164,14 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
if rootDir != "/" { if rootDir != "/" {
sourcePackages := make([]string, 0) sourcePackages := make([]string, 0)
for _, action := range operation.Actions { for _, action := range operation.Actions {
switch action.(type) { switch action := action.(type) {
case *InstallPackageAction: case *InstallPackageAction:
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Type == "source" { if action.BpmPackage.PkgInfo.Type == "source" {
sourcePackages = append(sourcePackages, action.(*InstallPackageAction).BpmPackage.PkgInfo.Name) sourcePackages = append(sourcePackages, action.BpmPackage.PkgInfo.Name)
} }
case *FetchPackageAction: case *FetchPackageAction:
if action.(*FetchPackageAction).DatabaseEntry.Info.Type == "source" { if action.DatabaseEntry.Info.Type == "source" {
sourcePackages = append(sourcePackages, action.(*FetchPackageAction).DatabaseEntry.Info.Name) sourcePackages = append(sourcePackages, action.DatabaseEntry.Info.Name)
} }
} }
} }
+1
View File
@@ -3,6 +3,7 @@ module git.enumerated.dev/bubble-package-manager/bpm/src/bpmlib
go 1.23 go 1.23
require ( require (
github.com/drone/envsubst v1.0.3
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
+4
View File
@@ -1,3 +1,7 @@
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g= github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f h1:xt29M2T6STgldg+WEP51gGePQCsQvklmP2eIhPIBK3g=
github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= github.com/knqyf263/go-rpm-version v0.0.0-20240918084003-2afd7dc6a38f/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+2 -6
View File
@@ -1,7 +1,6 @@
package bpmlib package bpmlib
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path" "path"
@@ -169,14 +168,11 @@ func GetAllPackageFiles(rootDir string, excludePackages ...string) (map[string][
} }
bpmpkg := GetPackage(pkgName, rootDir) bpmpkg := GetPackage(pkgName, rootDir)
if bpmpkg == nil { if bpmpkg == nil {
return nil, errors.New(fmt.Sprintf("could not get BPM package (%s)", pkgName)) return nil, fmt.Errorf("could not get BPM package (%s)", pkgName)
} }
for _, entry := range bpmpkg.PkgFiles { for _, entry := range bpmpkg.PkgFiles {
if _, ok := ret[entry.Path]; ok {
ret[entry.Path] = append(ret[entry.Path], bpmpkg) ret[entry.Path] = append(ret[entry.Path], bpmpkg)
} else {
ret[entry.Path] = []*BPMPackage{bpmpkg}
}
} }
} }
+61
View File
@@ -0,0 +1,61 @@
package bpmlib
import (
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
)
func retrieveUrlData(u string) ([]byte, error) {
resp, err := http.Get(u)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Load data into byte buffer
buffer, err := io.ReadAll(resp.Body)
return buffer, nil
}
func downloadFile(u, filepath string, perm os.FileMode) error {
if strings.HasSuffix(filepath, "/") {
return fmt.Errorf("Filepath must not end in '/'")
}
data, err := retrieveUrlData(u)
if err != nil {
return err
}
// Create parent directories
err = os.MkdirAll(path.Dir(filepath), 0755)
if err != nil {
return err
}
// Create file
file, err := os.Create(filepath)
if err != nil {
return err
}
defer file.Close()
// Copy data
_, err = file.Write(data)
if err != nil {
return err
}
// Set file permissions
err = file.Chmod(perm)
if err != nil {
return err
}
return nil
}
+5 -5
View File
@@ -448,7 +448,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
var bpmpkg *BPMPackage var bpmpkg *BPMPackage
// Check if package has already been fetched from download link // Check if package has already been fetched from download link
if _, ok := fetchedPackages[entry.Download]; !ok { if _, ok := fetchedPackages[entry.Filepath]; !ok {
// Fetch package from database // Fetch package from database
fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name) fetchedPackage, err := entry.Database.FetchPackage(entry.Info.Name)
if err != nil { if err != nil {
@@ -462,12 +462,12 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
} }
// Add fetched package to map // Add fetched package to map
fetchedPackages[entry.Download] = fetchedPackage fetchedPackages[entry.Filepath] = fetchedPackage
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name) fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
} else { } else {
// Read fetched package // Read fetched package
bpmpkg, err = ReadPackage(fetchedPackages[entry.Download]) bpmpkg, err = ReadPackage(fetchedPackages[entry.Filepath])
if err != nil { if err != nil {
return fmt.Errorf("could not read package (%s): %s\n", entry.Info.Name, err) return fmt.Errorf("could not read package (%s): %s\n", entry.Info.Name, err)
} }
@@ -477,14 +477,14 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
if bpmpkg.PkgInfo.IsSplitPackage() { if bpmpkg.PkgInfo.IsSplitPackage() {
operation.Actions[i] = &InstallPackageAction{ operation.Actions[i] = &InstallPackageAction{
File: fetchedPackages[entry.Download], File: fetchedPackages[entry.Filepath],
InstallationReason: action.(*FetchPackageAction).InstallationReason, InstallationReason: action.(*FetchPackageAction).InstallationReason,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
SplitPackageToInstall: entry.Info.Name, SplitPackageToInstall: entry.Info.Name,
} }
} else { } else {
operation.Actions[i] = &InstallPackageAction{ operation.Actions[i] = &InstallPackageAction{
File: fetchedPackages[entry.Download], File: fetchedPackages[entry.Filepath],
InstallationReason: action.(*FetchPackageAction).InstallationReason, InstallationReason: action.(*FetchPackageAction).InstallationReason,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
} }
+33 -26
View File
@@ -26,24 +26,43 @@ type BPMPackage struct {
} }
type PackageInfo struct { type PackageInfo struct {
Name string `yaml:"name,omitempty"` Name string `yaml:"name"`
Description string `yaml:"description,omitempty"` Description string `yaml:"description,omitempty"`
Version string `yaml:"version,omitempty"` Version string `yaml:"version,omitempty"`
Revision int `yaml:"revision,omitempty"` Revision int `yaml:"revision,omitempty"`
Url string `yaml:"url,omitempty"` Url string `yaml:"url,omitempty"`
License string `yaml:"license,omitempty"` License string `yaml:"license,omitempty"`
Arch string `yaml:"architecture,omitempty"` Arch string `yaml:"architecture,omitempty"`
OutputArch string `yaml:"output_architecture,omitempty"`
Type string `yaml:"type,omitempty"` Type string `yaml:"type,omitempty"`
Keep []string `yaml:"keep,omitempty"` Keep []string `yaml:"keep,omitempty"`
Depends []string `yaml:"depends,omitempty"` Depends []string `yaml:"depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"` OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
Conflicts []string `yaml:"conflicts,omitempty"` Conflicts []string `yaml:"conflicts,omitempty"`
Replaces []string `yaml:"replaces,omitempty"` Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"` Provides []string `yaml:"provides,omitempty"`
Downloads []PackageDownload `yaml:"downloads,omitempty"`
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"` SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
} }
type PackageDownload struct {
Url string `yaml:"url"`
Type string `yaml:"type,omitempty"`
Filepath string `yaml:"filepath,omitempty,omitempty"`
// Archive options
NoExtract bool `yaml:"no_extract,omitempty"`
ExtractTo string `yaml:"extract_to,omitempty"`
ExtractStripComponents int `yaml:"extract_strip_components,omitempty"`
// Git options
CloneTo string `yaml:"clone_to,omitempty"`
GitBranch string `yaml:"git_branch,omitempty"`
Checksum string `yaml:"checksum,omitempty"`
}
type PackageFileEntry struct { type PackageFileEntry struct {
Path string Path string
OctalPerms uint32 OctalPerms uint32
@@ -177,10 +196,10 @@ func ReadPackage(filename string) (*BPMPackage, error) {
} }
file, err := os.Open(filename) file, err := os.Open(filename)
defer file.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer file.Close()
tr := tar.NewReader(file) tr := tar.NewReader(file)
for { for {
@@ -303,14 +322,6 @@ func ReadPackageScripts(filename string) (map[string]string, error) {
return ret, nil return ret, nil
} }
type packageOperation uint8
const (
packageOperationInstall packageOperation = 0
packageOperationUpdate = 1
packageOperationRemove = 2
)
func executePackageScript(pkg, rootDir string, verbose bool, packageScript string) error { func executePackageScript(pkg, rootDir string, verbose bool, packageScript string) error {
var bpmpkg *BPMPackage var bpmpkg *BPMPackage
var err error var err error
@@ -413,14 +424,8 @@ func executePackageScript(pkg, rootDir string, verbose bool, packageScript strin
func ReadPackageInfo(contents string) (*PackageInfo, error) { func ReadPackageInfo(contents string) (*PackageInfo, error) {
pkgInfo := &PackageInfo{ pkgInfo := &PackageInfo{
Name: "",
Description: "",
Version: "",
Revision: 1, Revision: 1,
Url: "", OutputArch: GetArch(),
License: "",
Arch: "",
Type: "",
Keep: make([]string, 0), Keep: make([]string, 0),
Depends: make([]string, 0), Depends: make([]string, 0),
MakeDepends: make([]string, 0), MakeDepends: make([]string, 0),
@@ -428,6 +433,7 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
Conflicts: make([]string, 0), Conflicts: make([]string, 0),
Replaces: make([]string, 0), Replaces: make([]string, 0),
Provides: make([]string, 0), Provides: make([]string, 0),
Downloads: make([]PackageDownload, 0),
SplitPackages: make([]*PackageInfo, 0), SplitPackages: make([]*PackageInfo, 0),
} }
err := yaml.Unmarshal([]byte(contents), &pkgInfo) err := yaml.Unmarshal([]byte(contents), &pkgInfo)
@@ -462,28 +468,29 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
return nil, fmt.Errorf("invalid split package name: %s", splitPkg.Name) return nil, fmt.Errorf("invalid split package name: %s", splitPkg.Name)
} }
// Turn split package into json data // Turn split package into yaml data
splitPkgJson, err := yaml.Marshal(splitPkg) splitPkgYaml, err := yaml.Marshal(splitPkg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Clone all main package fields onto split package // Clone all main package fields onto split package
pkgInfoClone := *pkgInfo *splitPkg = *pkgInfo
pkgInfo.SplitPackages[i] = &pkgInfoClone
// Set split package field of split package to nil // Set split package field of split package to nil
pkgInfo.SplitPackages[i].SplitPackages = nil splitPkg.SplitPackages = nil
// Unmarshal json data back to struct // Unmarshal json data back to struct
err = yaml.Unmarshal(splitPkgJson, &pkgInfo.SplitPackages[i]) err = yaml.Unmarshal(splitPkgYaml, splitPkg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Force set split package version, revision // Force set split package version, revision
pkgInfo.SplitPackages[i].Version = pkgInfo.Version splitPkg.Version = pkgInfo.Version
pkgInfo.SplitPackages[i].Revision = pkgInfo.Revision splitPkg.Revision = pkgInfo.Revision
pkgInfo.SplitPackages[i] = splitPkg
} }
return pkgInfo, nil return pkgInfo, nil
+43
View File
@@ -3,9 +3,52 @@ package bpmlib
import ( import (
"fmt" "fmt"
"math" "math"
"os"
"path"
"syscall" "syscall"
) )
type BPMLock struct {
file *os.File
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)
if err != nil {
return nil, err
}
// Create file
f, err := os.Create(path.Join(rootDir, "var/lib/bpm/bpm.lock"))
if err != nil {
return nil, err
}
// Get exclusive file lock on file
err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
return nil, err
}
return &BPMLock{f, path.Join(rootDir, "var/lib/bpm/bpm.lock")}, nil
}
func GetArch() string { func GetArch() string {
uname := syscall.Utsname{} uname := syscall.Utsname{}
err := syscall.Uname(&uname) err := syscall.Uname(&uname)