Compare commits

...

10 Commits

7 changed files with 429 additions and 157 deletions

View File

@ -45,7 +45,7 @@ You can remove an installed package by typing the following
bpm remove package_name bpm remove package_name
``` ```
To remove all unused dependencies try using the cleanup command To remove all unused dependencies and clean cached files try using the cleanup command
```sh ```sh
bpm cleanup bpm cleanup
``` ```

View File

@ -42,7 +42,11 @@ var doCleanup = false
var showRepoInfo = false var showRepoInfo = false
var installSrcPkgDepends = false var installSrcPkgDepends = false
var skipChecks = false var skipChecks = false
var outputFilename = "" var outputDirectory = ""
var cleanupDependencies = false
var cleanupCompilationFiles = false
var cleanupCompiledPackages = false
var cleanupFetchedPackages = false
func main() { func main() {
err := bpmlib.ReadConfig() err := bpmlib.ReadConfig()
@ -456,51 +460,58 @@ func resolveCommand() {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions") log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
} }
// Read local databases err := bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose)
err := bpmlib.ReadLocalDatabases()
if err != nil { if err != nil {
log.Fatalf("Error: could not read local databases: %s", err) log.Fatalf("Error: could not complete cache cleanup: %s", err)
} }
// Create cleanup operation if cleanupDependencies {
operation, err := bpmlib.CleanupPackages(rootDir, verbose) // Read local databases
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { err := bpmlib.ReadLocalDatabases()
log.Fatalf("Error: %s", err) if err != nil {
} else if err != nil { log.Fatalf("Error: could not read local databases: %s", err)
log.Fatalf("Error: could not setup operation: %s\n", err)
}
// Exit if operation contains no actions
if len(operation.Actions) == 0 {
fmt.Println("No action needs to be taken")
return
}
// Show operation summary
operation.ShowOperationSummary()
// Confirmation Prompt
if !yesAll {
fmt.Printf("Are you sure you wish to remove all %d packages? [y\\N] ", len(operation.Actions))
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package removal...")
os.Exit(1)
} }
}
// Execute operation // Create cleanup operation
err = operation.Execute(verbose, force) operation, err := bpmlib.CleanupPackages(rootDir, verbose)
if err != nil { if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: could not complete operation: %s\n", err) log.Fatalf("Error: %s", err)
} } else if err != nil {
log.Fatalf("Error: could not setup operation: %s\n", err)
}
// Executing hooks // Exit if operation contains no actions
fmt.Println("Running hooks...") if len(operation.Actions) == 0 {
err = operation.RunHooks(verbose) fmt.Println("No action needs to be taken")
if err != nil { return
log.Fatalf("Error: could not run hooks: %s\n", err) }
// Show operation summary
operation.ShowOperationSummary()
// Confirmation Prompt
if !yesAll {
fmt.Printf("Are you sure you wish to remove all %d packages? [y\\N] ", len(operation.Actions))
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling package removal...")
os.Exit(1)
}
}
// Execute operation
err = operation.Execute(verbose, force)
if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err)
}
// Executing hooks
fmt.Println("Running hooks...")
err = operation.RunHooks(verbose)
if err != nil {
log.Fatalf("Error: could not run hooks: %s\n", err)
}
} }
case file: case file:
files := subcommandArgs files := subcommandArgs
@ -609,7 +620,9 @@ func resolveCommand() {
} }
// Run 'bpm install' using the set privilege escalator command // Run 'bpm install' using the set privilege escalator command
cmd := exec.Command(bpmlib.BPMConfig.PrivilegeEscalatorCmd, executable, "install", "--installation-reason=dependency", strings.Join(unmetDepends, " ")) args := []string{executable, "install", "--installation-reason=dependency"}
args = append(args, unmetDepends...)
cmd := exec.Command(bpmlib.BPMConfig.PrivilegeEscalatorCmd, args...)
if yesAll { if yesAll {
cmd.Args = slices.Insert(cmd.Args, 3, "-y") cmd.Args = slices.Insert(cmd.Args, 3, "-y")
} }
@ -642,36 +655,40 @@ func resolveCommand() {
log.Fatalf("Error: could not get user home directory: %s", err) log.Fatalf("Error: could not get user home directory: %s", err)
} }
// Trim output filename // Trim output directory
outputFilename = strings.TrimSpace(outputFilename) outputDirectory = strings.TrimSpace(outputDirectory)
if outputFilename != "/" { if outputDirectory != "/" {
outputFilename = strings.TrimSuffix(outputFilename, "/") outputDirectory = strings.TrimSuffix(outputDirectory, "/")
} }
// Set output filename if empty // Set output directory if empty
if outputFilename == "" { if outputDirectory == "" {
outputFilename = path.Join(workdir, fmt.Sprintf("%s-%s-%d.bpm", bpmpkg.PkgInfo.Name, bpmpkg.PkgInfo.Version, bpmpkg.PkgInfo.Revision)) outputDirectory = workdir
} }
// Replace first tilde with user home directory // Replace first tilde with user home directory
if strings.Split(outputFilename, "/")[0] == "~" { if strings.Split(outputDirectory, "/")[0] == "~" {
outputFilename = strings.Replace(outputFilename, "~", homedir, 1) outputDirectory = strings.Replace(outputDirectory, "~", homedir, 1)
} }
// Prepend current working directory to output filename if not an absolute path // Prepend current working directory to output directory if not an absolute path
if outputFilename != "" && !strings.HasPrefix(outputFilename, "/") { if outputDirectory != "" && !strings.HasPrefix(outputDirectory, "/") {
outputFilename = filepath.Join(workdir, outputFilename) outputDirectory = filepath.Join(workdir, outputDirectory)
} }
// Clean path // Clean path
path.Clean(outputFilename) path.Clean(outputDirectory)
// Append archive filename if path is set to a directory // Ensure output directory exists and is a directory
if stat, err := os.Stat(outputFilename); err == nil && stat.IsDir() { stat, err := os.Stat(outputDirectory)
outputFilename = path.Join(outputFilename, fmt.Sprintf("%s-%s-%d.bpm", bpmpkg.PkgInfo.Name, bpmpkg.PkgInfo.Version, bpmpkg.PkgInfo.Revision)) if err != nil {
log.Fatalf("Error: could not stat output directory (%s): %s", outputDirectory, err)
}
if !stat.IsDir() {
log.Fatalf("Error: output directory (%s) is not a directory", outputDirectory)
} }
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputFilename, 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.Fatalf("Error: could not compile source package (%s): %s", sourcePackage, err)
} }
@ -725,12 +742,11 @@ func printHelp() {
fmt.Println(" -c lists the amount of installed packages") fmt.Println(" -c lists the amount of installed packages")
fmt.Println(" -n lists only the names of installed packages") fmt.Println(" -n lists only the names of installed packages")
fmt.Println("-> bpm search <search terms...> | Searches for packages through declared repositories") fmt.Println("-> bpm search <search terms...> | Searches for packages through declared repositories")
fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --reinstall-all, --no-optional, --installation-reason] <packages...> | installs the following files") fmt.Println("-> bpm install [-R, -v, -y, -f, --reinstall, --reinstall-all, --no-optional, --installation-reason] <packages...> | installs the following files")
fmt.Println(" -R=<path> lets you define the root path which will be used") fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" -f skips dependency, conflict and architecture checking") fmt.Println(" -f skips dependency, conflict and architecture checking")
fmt.Println(" -k keeps the compilation directory created by BPM after source package installation")
fmt.Println(" --reinstall Reinstalls packages even if they do not have a newer version available") fmt.Println(" --reinstall Reinstalls packages even if they do not have a newer version available")
fmt.Println(" --reinstall-all Same as --reinstall but also reinstalls dependencies") fmt.Println(" --reinstall-all Same as --reinstall but also reinstalls dependencies")
fmt.Println(" --no-optional Prevents installation of optional dependencies") fmt.Println(" --no-optional Prevents installation of optional dependencies")
@ -752,17 +768,21 @@ func printHelp() {
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" --unused removes only packages that aren't required as dependencies by other packages") fmt.Println(" --unused removes only packages that aren't required as dependencies by other packages")
fmt.Println(" --cleanup performs a dependency cleanup") fmt.Println(" --cleanup performs a dependency cleanup")
fmt.Println("-> bpm cleanup [-R, -v, -y] | remove all unused dependency packages") fmt.Println("-> bpm cleanup [-R, -v, -y, --depends, --compilation-files, --compiled-pkgs, --fetched-pkgs] | remove all unused dependencies and cache directories")
fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -R=<path> lets you define the root path which will be used") fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" --depends performs a dependency cleanup")
fmt.Println(" --compilation-files performs a cleanup of compilation files")
fmt.Println(" --compiled-pkgs performs a cleanup of compilation compiled binary packages")
fmt.Println(" --fetched-pkgs performs a cleanup of fetched packages from repositories")
fmt.Println("-> bpm file [-R] <files...> | shows what packages the following packages are managed by") fmt.Println("-> bpm file [-R] <files...> | shows what packages the following packages are managed by")
fmt.Println(" -R=<root_path> lets you define the root path which will be used") fmt.Println(" -R=<root_path> lets you define the root path which will be used")
fmt.Println("-> bpm compile [-d, -s, -o] <source packages...> | Compile source BPM package") fmt.Println("-> bpm compile [-d, -s, -o] <source packages...> | Compile source BPM package")
fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -d installs required dependencies for package compilation") fmt.Println(" -d installs required dependencies for package compilation")
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 filename") fmt.Println(" -o sets output directory")
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println("\033[1m----------------\033[0m") fmt.Println("\033[1m----------------\033[0m")
@ -818,6 +838,10 @@ func resolveFlags() {
cleanupFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") cleanupFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
cleanupFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing") cleanupFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
cleanupFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts") cleanupFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
cleanupFlagSet.BoolVar(&cleanupDependencies, "depends", false, "Perform a dependency cleanup")
cleanupFlagSet.BoolVar(&cleanupCompilationFiles, "compilation-files", false, "Perform a cleanup of compilation files")
cleanupFlagSet.BoolVar(&cleanupCompiledPackages, "compiled-pkgs", false, "Perform a cleanup of compilation compiled binary packages")
cleanupFlagSet.BoolVar(&cleanupFetchedPackages, "fetched-pkgs", false, "Perform a cleanup of fetched packages from repositories")
cleanupFlagSet.Usage = printHelp cleanupFlagSet.Usage = printHelp
// File flags // File flags
fileFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError) fileFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError)
@ -827,7 +851,7 @@ func resolveFlags() {
compileFlagSet := flag.NewFlagSet("Compile flags", flag.ExitOnError) compileFlagSet := flag.NewFlagSet("Compile flags", flag.ExitOnError)
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(&outputFilename, "o", "", "Set output filename") compileFlagSet.StringVar(&outputDirectory, "o", "", "Set output directory")
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")
@ -873,6 +897,18 @@ func resolveFlags() {
return return
} }
subcommandArgs = removeFlagSet.Args() subcommandArgs = removeFlagSet.Args()
} else if getCommandType() == cleanup {
err := cleanupFlagSet.Parse(subcommandArgs)
if err != nil {
return
}
if !cleanupDependencies && !cleanupCompilationFiles && !cleanupCompiledPackages && !cleanupFetchedPackages {
cleanupDependencies = true
cleanupCompilationFiles = true
cleanupCompiledPackages = true
cleanupFetchedPackages = true
}
subcommandArgs = cleanupFlagSet.Args()
} else if getCommandType() == file { } else if getCommandType() == file {
err := fileFlagSet.Parse(subcommandArgs) err := fileFlagSet.Parse(subcommandArgs)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ import (
var rootCompilationUID = "65534" var rootCompilationUID = "65534"
var rootCompilationGID = "65534" var rootCompilationGID = "65534"
func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks bool) (outputBpmPackages map[string]string, err error) { func CompileSourcePackage(archiveFilename, outputDirectory string, skipChecks bool) (outputBpmPackages map[string]string, err error) {
// Initialize map // Initialize map
outputBpmPackages = make(map[string]string) outputBpmPackages = make(map[string]string)
@ -60,7 +60,13 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
gid = os.Getgid() gid = os.Getgid()
} }
tempDirectory := path.Join(homeDir, ".cache/bpm/compilation/", bpmpkg.PkgInfo.Name) // Set temporary directory
var tempDirectory string
if os.Getuid() == 0 {
tempDirectory = path.Join("/var/cache/bpm/compilation/", bpmpkg.PkgInfo.Name)
} else {
tempDirectory = path.Join(homeDir, ".cache/bpm/compilation/", bpmpkg.PkgInfo.Name)
}
// Ensure temporary directory does not exist // Ensure temporary directory does not exist
if _, err := os.Stat(tempDirectory); err == nil { if _, err := os.Stat(tempDirectory); err == nil {
@ -144,8 +150,10 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = env cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{} if os.Getuid() == 0 {
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return nil, err return nil, err
@ -163,29 +171,27 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = env cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{} if os.Getuid() == 0 {
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
// Variable that will be used later
isSplitPkg := true
// Get all packages to compile // Get all packages to compile
packagesToCompile := bpmpkg.PkgInfo.SplitPackages packagesToCompile := bpmpkg.PkgInfo.SplitPackages
if len(packagesToCompile) == 0 { if !bpmpkg.PkgInfo.IsSplitPackage() {
packagesToCompile = append(packagesToCompile, bpmpkg.PkgInfo) packagesToCompile = append(packagesToCompile, bpmpkg.PkgInfo)
isSplitPkg = false
} }
// Compile each package // Compile each package
for _, pkg := range packagesToCompile { for _, pkg := range packagesToCompile {
// Get package function name // Get package function name
packageFunctionName := "package" packageFunctionName := "package"
if isSplitPkg { if bpmpkg.PkgInfo.IsSplitPackage() {
packageFunctionName = "package_" + pkg.Name packageFunctionName = "package_" + pkg.Name
} }
@ -221,8 +227,10 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = env cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{} if os.Getuid() == 0 {
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return nil, err return nil, err
@ -234,36 +242,17 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = env cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{} if os.Getuid() == 0 {
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return nil, fmt.Errorf("files.tar.gz archive could not be created: %s", err) return nil, fmt.Errorf("files.tar.gz archive could not be created: %s", err)
} }
// Clone source package info // Clone source package info
var pkgInfo PackageInfo pkgInfo := *pkg
if !isSplitPkg {
pkgInfo = *bpmpkg.PkgInfo
} else {
pkgInfo = *pkg
// Ensure required fields are set
if strings.TrimSpace(pkgInfo.Name) == "" {
return nil, fmt.Errorf("split package name is empty")
}
// Copy data from main source package
if pkgInfo.Description == "" {
pkgInfo.Description = bpmpkg.PkgInfo.Description
}
pkgInfo.Version = bpmpkg.PkgInfo.Version
pkgInfo.Revision = bpmpkg.PkgInfo.Revision
pkgInfo.Url = bpmpkg.PkgInfo.Url
if pkgInfo.License == "" {
pkgInfo.License = bpmpkg.PkgInfo.License
}
}
// Set package type to binary // Set package type to binary
pkgInfo.Type = "binary" pkgInfo.Type = "binary"
@ -313,8 +302,10 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
return nil, err return nil, err
} }
cmd.Env = append(env, "CURRENT_DIR="+currentDir) cmd.Env = append(env, "CURRENT_DIR="+currentDir)
cmd.SysProcAttr = &syscall.SysProcAttr{} if os.Getuid() == 0 {
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)} cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return nil, fmt.Errorf("BPM archive could not be created: %s", err) return nil, fmt.Errorf("BPM archive could not be created: %s", err)
@ -326,16 +317,8 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
return nil, err return nil, err
} }
// Set output filename if split package // Set output filename
if len(bpmpkg.PkgInfo.SplitPackages) != 1 { outputFilename := path.Join(outputDirectory, fmt.Sprintf("%s-%s-%d-%s.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision, pkgInfo.Arch))
// Get current working directory
workdir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("could not get working directory: %s", err)
}
outputFilename = path.Join(workdir, fmt.Sprintf("%s-%s-%d.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision))
}
// Move final BPM archive // Move final BPM archive
err = os.Rename(path.Join(tempDirectory, "final-archive.bpm"), outputFilename) err = os.Rename(path.Join(tempDirectory, "final-archive.bpm"), outputFilename)

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path"
"slices" "slices"
) )
@ -25,6 +26,7 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein
Changes: make(map[string]string), Changes: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
ForceInstallationReason: installationReason, ForceInstallationReason: installationReason,
compiledPackages: make(map[string]string),
} }
// Resolve packages // Resolve packages
@ -35,12 +37,25 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read package: %s", err) return nil, fmt.Errorf("could not read package: %s", err)
} }
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.IsSplitPackage() {
for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages {
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() {
continue
}
operation.AppendAction(&InstallPackageAction{
File: pkg,
IsDependency: false,
BpmPackage: bpmpkg,
SplitPackageToInstall: splitPkg.Name,
})
}
continue continue
} }
if bpmpkg.PkgInfo.Type == "source" && len(bpmpkg.PkgInfo.SplitPackages) != 0 { if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
return nil, fmt.Errorf("direct source package installation has not been implemented") continue
} }
operation.AppendAction(&InstallPackageAction{ operation.AppendAction(&InstallPackageAction{
@ -69,10 +84,6 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein
continue continue
} }
if entry.Info.Type == "source" && len(entry.Info.SplitPackages) != 0 {
return nil, fmt.Errorf("direct source package installation has not been implemented")
}
operation.AppendAction(&FetchPackageAction{ operation.AppendAction(&FetchPackageAction{
IsDependency: false, IsDependency: false,
RepositoryEntry: entry, RepositoryEntry: entry,
@ -128,6 +139,7 @@ func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencie
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string), Changes: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string),
} }
// Search for packages // Search for packages
@ -164,6 +176,7 @@ func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string), Changes: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string),
} }
// Do package cleanup // Do package cleanup
@ -175,6 +188,88 @@ func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err
return operation, nil return operation, nil
} }
func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose bool) error {
if cleanupCompilationFiles {
globalCompilationCacheDir := path.Join(rootDir, "var/cache/bpm/compilation")
// Ensure path exists and is a directory
if stat, err := os.Stat(globalCompilationCacheDir); err == nil && stat.IsDir() {
// Delete directory
if verbose {
log.Printf("Removing directory (%s)\n", globalCompilationCacheDir)
}
err = os.RemoveAll(globalCompilationCacheDir)
if err != nil {
return err
}
}
// Get home directories of users in root directory
homeDirs, err := os.ReadDir(path.Join(rootDir, "home"))
if err != nil {
return err
}
// Loop through all home directories
for _, homeDir := range homeDirs {
// Skip if not a directory
if !homeDir.IsDir() {
continue
}
localCompilationDir := path.Join(rootDir, "home", homeDir.Name(), ".cache/bpm/compilation")
// Ensure path exists and is a directory
if stat, err := os.Stat(localCompilationDir); err != nil || !stat.IsDir() {
continue
}
// Delete directory
if verbose {
log.Printf("Removing directory (%s)\n", localCompilationDir)
}
err = os.RemoveAll(localCompilationDir)
if err != nil {
return err
}
}
}
if cleanupCompiledPackages {
dirToRemove := path.Join(rootDir, "var/cache/bpm/compiled")
// Ensure path exists and is a directory
if stat, err := os.Stat(dirToRemove); err == nil && stat.IsDir() {
// Delete directory
if verbose {
log.Printf("Removing directory (%s)\n", dirToRemove)
}
err = os.RemoveAll(dirToRemove)
if err != nil {
return err
}
}
}
if cleanupFetchedPackages {
dirToRemove := path.Join(rootDir, "var/cache/bpm/fetched")
// Ensure path exists and is a directory
if stat, err := os.Stat(dirToRemove); err == nil && stat.IsDir() {
// Delete directory
if verbose {
log.Printf("Removing directory (%s)\n", dirToRemove)
}
err = os.RemoveAll(dirToRemove)
if err != nil {
return err
}
}
}
return nil
}
// UpdatePackages fetches the newest versions of all installed packages from // UpdatePackages fetches the newest versions of all installed packages from
func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) { func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) {
// Sync repositories // Sync repositories
@ -206,6 +301,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
Changes: make(map[string]string), Changes: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
ForceInstallationReason: InstallationReasonUnknown, ForceInstallationReason: InstallationReasonUnknown,
compiledPackages: make(map[string]string),
} }
// Search for packages // Search for packages

View File

@ -16,6 +16,7 @@ type BPMOperation struct {
Changes map[string]string Changes map[string]string
RootDir string RootDir string
ForceInstallationReason InstallationReason ForceInstallationReason InstallationReason
compiledPackages map[string]string
} }
func (operation *BPMOperation) ActionsContainPackage(pkg string) bool { func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
@ -337,6 +338,9 @@ func (operation *BPMOperation) ShowOperationSummary() {
var pkgInfo *PackageInfo var pkgInfo *PackageInfo
if value.GetActionType() == "install" { if value.GetActionType() == "install" {
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
if value.(*InstallPackageAction).SplitPackageToInstall != "" {
pkgInfo = pkgInfo.GetSplitPackageInfo(value.(*InstallPackageAction).SplitPackageToInstall)
}
} else if value.GetActionType() == "fetch" { } else if value.GetActionType() == "fetch" {
pkgInfo = value.(*FetchPackageAction).RepositoryEntry.Info pkgInfo = value.(*FetchPackageAction).RepositoryEntry.Info
} else { } else {
@ -409,30 +413,68 @@ func (operation *BPMOperation) RunHooks(verbose bool) error {
return nil return nil
} }
func (operation *BPMOperation) Execute(verbose, force bool) error { func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Fetch packages from repositories // Fetch packages from repositories
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool { if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
return action.GetActionType() == "fetch" return action.GetActionType() == "fetch"
}) { }) {
fmt.Println("Fetching packages from available repositories...") fmt.Println("Fetching packages from available repositories...")
// Create map for fetched packages
fetchedPackages := make(map[string]string)
for i, action := range operation.Actions { for i, action := range operation.Actions {
if action.GetActionType() != "fetch" { if action.GetActionType() != "fetch" {
continue continue
} }
// Get repository entry
entry := action.(*FetchPackageAction).RepositoryEntry entry := action.(*FetchPackageAction).RepositoryEntry
fetchedPackage, err := entry.Repository.FetchPackage(entry.Info.Name)
if err != nil { // Create bpmpkg variable
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err)) var bpmpkg *BPMPackage
// Check if package has already been fetched from download link
if _, ok := fetchedPackages[entry.Download]; !ok {
// Fetch package from repository
fetchedPackage, err := entry.Repository.FetchPackage(entry.Info.Name)
if err != nil {
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err))
}
// Read fetched package
bpmpkg, err = ReadPackage(fetchedPackage)
if err != nil {
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err))
}
// Add fetched package to map
fetchedPackages[entry.Download] = fetchedPackage
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
} else {
// Read fetched package
bpmpkg, err = ReadPackage(fetchedPackages[entry.Download])
if err != nil {
return errors.New(fmt.Sprintf("could not read package (%s): %s\n", entry.Info.Name, err))
}
fmt.Printf("Package (%s) was successfully fetched!\n", entry.Info.Name)
} }
bpmpkg, err := ReadPackage(fetchedPackage)
if err != nil { if bpmpkg.PkgInfo.IsSplitPackage() {
return errors.New(fmt.Sprintf("could not fetch package (%s): %s\n", entry.Info.Name, err)) operation.Actions[i] = &InstallPackageAction{
} File: fetchedPackages[entry.Download],
fmt.Printf("Package (%s) was successfully fetched!\n", bpmpkg.PkgInfo.Name) IsDependency: action.(*FetchPackageAction).IsDependency,
operation.Actions[i] = &InstallPackageAction{ BpmPackage: bpmpkg,
File: fetchedPackage, SplitPackageToInstall: entry.Info.Name,
IsDependency: action.(*FetchPackageAction).IsDependency, }
BpmPackage: bpmpkg, } else {
operation.Actions[i] = &InstallPackageAction{
File: fetchedPackages[entry.Download],
IsDependency: action.(*FetchPackageAction).IsDependency,
BpmPackage: bpmpkg,
}
} }
} }
} }
@ -473,9 +515,8 @@ func (operation *BPMOperation) Execute(verbose, force bool) error {
// Compile package if type is 'source' // Compile package if type is 'source'
if bpmpkg.PkgInfo.Type == "source" { if bpmpkg.PkgInfo.Type == "source" {
// Get path to compiled package directory and output filename // Get path to compiled package directory
compiledDir := path.Join(operation.RootDir, "/var/lib/bpm/compiled/") compiledDir := path.Join(operation.RootDir, "/var/cache/bpm/compiled/")
outputFilename := path.Join(compiledDir, fmt.Sprintf("%s-%s-%d.bpm", bpmpkg.PkgInfo.Name, bpmpkg.PkgInfo.Version, bpmpkg.PkgInfo.Revision))
// Create compiled package directory if not exists // Create compiled package directory if not exists
if _, err := os.Stat(compiledDir); err != nil { if _, err := os.Stat(compiledDir); err != nil {
@ -485,15 +526,28 @@ func (operation *BPMOperation) Execute(verbose, force bool) error {
} }
} }
// Compile source package // Get package name to install
outputBpmPackages, err := CompileSourcePackage(value.File, outputFilename, false) pkgNameToInstall := bpmpkg.PkgInfo.Name
if err != nil { if bpmpkg.PkgInfo.IsSplitPackage() {
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err) pkgNameToInstall = value.SplitPackageToInstall
}
// Compile source package if not compiled already
if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok {
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, false)
if err != nil {
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
}
// Add compiled packages to slice
for pkgName, pkgFile := range outputBpmPackages {
operation.compiledPackages[pkgName] = pkgFile
}
} }
// Set values // Set values
fileToInstall = outputBpmPackages[bpmpkg.PkgInfo.Name] fileToInstall = operation.compiledPackages[pkgNameToInstall]
bpmpkg, err = ReadPackage(outputFilename) bpmpkg, err = ReadPackage(fileToInstall)
if err != nil { if err != nil {
return fmt.Errorf("could not read package (%s): %s\n", fileToInstall, err) return fmt.Errorf("could not read package (%s): %s\n", fileToInstall, err)
} }
@ -531,9 +585,10 @@ type OperationAction interface {
} }
type InstallPackageAction struct { type InstallPackageAction struct {
File string File string
IsDependency bool IsDependency bool
BpmPackage *BPMPackage SplitPackageToInstall string
BpmPackage *BPMPackage
} }
func (action *InstallPackageAction) GetActionType() string { func (action *InstallPackageAction) GetActionType() string {

View File

@ -70,6 +70,25 @@ func (pkgInfo *PackageInfo) GetFullVersion() string {
return pkgInfo.Version + "-" + strconv.Itoa(pkgInfo.Revision) return pkgInfo.Version + "-" + strconv.Itoa(pkgInfo.Revision)
} }
func (pkgInfo *PackageInfo) IsSplitPackage() bool {
// Return false if not a source package
if pkgInfo.Type != "source" {
return false
}
return len(pkgInfo.SplitPackages) > 0
}
func (pkgInfo *PackageInfo) GetSplitPackageInfo(splitPkg string) *PackageInfo {
for _, splitPkgInfo := range pkgInfo.SplitPackages {
if splitPkgInfo.Name == splitPkg {
return splitPkgInfo
}
}
return nil
}
type InstallationReason string type InstallationReason string
const ( const (
@ -149,7 +168,6 @@ func ReadPackage(filename string) (*BPMPackage, error) {
var pkgFiles []*PackageFileEntry var pkgFiles []*PackageFileEntry
if _, err := os.Stat(filename); os.IsNotExist(err) { if _, err := os.Stat(filename); os.IsNotExist(err) {
fmt.Println("a")
return nil, err return nil, err
} }
@ -388,7 +406,7 @@ func executePackageScripts(filename, rootDir string, operation packageOperation,
} }
func ReadPackageInfo(contents string) (*PackageInfo, error) { func ReadPackageInfo(contents string) (*PackageInfo, error) {
pkgInfo := PackageInfo{ pkgInfo := &PackageInfo{
Name: "", Name: "",
Description: "", Description: "",
Version: "", Version: "",
@ -410,6 +428,8 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Ensure required fields are set properly
if pkgInfo.Name == "" { if pkgInfo.Name == "" {
return nil, errors.New("this package contains no name") return nil, errors.New("this package contains no name")
} else if pkgInfo.Description == "" { } else if pkgInfo.Description == "" {
@ -423,10 +443,46 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
} else if pkgInfo.Type == "" { } else if pkgInfo.Type == "" {
return nil, errors.New("this package contains no type") return nil, errors.New("this package contains no type")
} }
for i := 0; i < len(pkgInfo.Keep); i++ { for _, val := range pkgInfo.Keep {
pkgInfo.Keep[i] = strings.TrimPrefix(pkgInfo.Keep[i], "/") if strings.HasPrefix(val, "/") {
return nil, fmt.Errorf("cannot keep file (%s) after update because it starts with a slash", val)
}
} }
return &pkgInfo, nil
// Setup split package information
for i, splitPkg := range pkgInfo.SplitPackages {
// Ensure split package contains a name and one that is different from the main package name
if splitPkg.Name == "" || splitPkg.Name == pkgInfo.Name {
return nil, fmt.Errorf("invalid split package name: %s", splitPkg.Name)
}
// Turn split package into json data
splitPkgJson, err := yaml.Marshal(splitPkg)
if err != nil {
return nil, err
}
// Clone all main package fields onto split package
pkgInfoClone := *pkgInfo
pkgInfo.SplitPackages[i] = &pkgInfoClone
// Set make depends and split package field of split package to nil
pkgInfo.SplitPackages[i].MakeDepends = nil
pkgInfo.SplitPackages[i].SplitPackages = nil
// Unmarshal json data back to struct
err = yaml.Unmarshal(splitPkgJson, &pkgInfo.SplitPackages[i])
if err != nil {
return nil, err
}
// Force set split package version, revision and URL
pkgInfo.SplitPackages[i].Version = pkgInfo.Version
pkgInfo.SplitPackages[i].Revision = pkgInfo.Revision
pkgInfo.SplitPackages[i].Url = pkgInfo.Url
}
return pkgInfo, nil
} }
func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, pkgInfo *PackageInfo, rootDir string) string { func CreateReadableInfo(showArchitecture, showType, showPackageRelations bool, pkgInfo *PackageInfo, rootDir string) string {

View File

@ -73,10 +73,56 @@ func (repo *Repository) ReadLocalDatabase() error {
return err return err
} }
for _, p := range entry.Info.Provides { // Create repository entries
repo.VirtualPackages[p] = append(repo.VirtualPackages[p], entry.Info.Name) if entry.Info.IsSplitPackage() {
for _, splitPkg := range entry.Info.SplitPackages {
// Turn split package into json data
splitPkgJson, err := yaml.Marshal(splitPkg)
if err != nil {
return err
}
// Clone all main package fields onto split package
splitPkgClone := *entry.Info
// Set split package field of split package to nil
splitPkgClone.SplitPackages = nil
// Unmarshal json data back to struct
err = yaml.Unmarshal(splitPkgJson, &splitPkgClone)
if err != nil {
return err
}
// Force set split package version, revision and URL
splitPkgClone.Version = entry.Info.Version
splitPkgClone.Revision = entry.Info.Revision
splitPkgClone.Url = entry.Info.Url
// Create entry for split package
repo.Entries[splitPkg.Name] = &RepositoryEntry{
Info: &splitPkgClone,
Download: entry.Download,
DownloadSize: entry.DownloadSize,
InstalledSize: 0,
Repository: repo,
}
// Add virtual packages to repository
for _, p := range splitPkg.Provides {
repo.VirtualPackages[p] = append(repo.VirtualPackages[p], splitPkg.Name)
}
}
} else {
// Create entry for package
repo.Entries[entry.Info.Name] = &entry
// Add virtual packages to repository
for _, p := range entry.Info.Provides {
repo.VirtualPackages[p] = append(repo.VirtualPackages[p], entry.Info.Name)
}
} }
repo.Entries[entry.Info.Name] = &entry
} }
return nil return nil
@ -220,16 +266,16 @@ func (repo *Repository) FetchPackage(pkg string) (string, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
err = os.MkdirAll("/var/cache/bpm/packages/", 0755) err = os.MkdirAll("/var/cache/bpm/fetched/", 0755)
if err != nil { if err != nil {
return "", err return "", err
} }
out, err := os.Create("/var/cache/bpm/packages/" + path.Base(entry.Download)) out, err := os.Create("/var/cache/bpm/fetched/" + path.Base(entry.Download))
if err != nil { if err != nil {
return "", err return "", err
} }
defer out.Close() defer out.Close()
_, err = io.Copy(out, resp.Body) _, err = io.Copy(out, resp.Body)
return "/var/cache/bpm/packages/" + path.Base(entry.Download), nil return "/var/cache/bpm/fetched/" + path.Base(entry.Download), nil
} }