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
```
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
bpm cleanup
```

View File

@ -42,7 +42,11 @@ var doCleanup = false
var showRepoInfo = false
var installSrcPkgDepends = false
var skipChecks = false
var outputFilename = ""
var outputDirectory = ""
var cleanupDependencies = false
var cleanupCompilationFiles = false
var cleanupCompiledPackages = false
var cleanupFetchedPackages = false
func main() {
err := bpmlib.ReadConfig()
@ -456,51 +460,58 @@ func resolveCommand() {
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
}
// Read local databases
err := bpmlib.ReadLocalDatabases()
err := bpmlib.CleanupCache(rootDir, cleanupCompilationFiles, cleanupCompiledPackages, cleanupFetchedPackages, verbose)
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
operation, err := bpmlib.CleanupPackages(rootDir, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err)
} else if err != nil {
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)
if cleanupDependencies {
// Read local databases
err := bpmlib.ReadLocalDatabases()
if err != nil {
log.Fatalf("Error: could not read local databases: %s", err)
}
}
// Execute operation
err = operation.Execute(verbose, force)
if err != nil {
log.Fatalf("Error: could not complete operation: %s\n", err)
}
// Create cleanup operation
operation, err := bpmlib.CleanupPackages(rootDir, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Fatalf("Error: %s", err)
} else if err != nil {
log.Fatalf("Error: could not setup 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)
// 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
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:
files := subcommandArgs
@ -609,7 +620,9 @@ func resolveCommand() {
}
// 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 {
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)
}
// Trim output filename
outputFilename = strings.TrimSpace(outputFilename)
if outputFilename != "/" {
outputFilename = strings.TrimSuffix(outputFilename, "/")
// Trim output directory
outputDirectory = strings.TrimSpace(outputDirectory)
if outputDirectory != "/" {
outputDirectory = strings.TrimSuffix(outputDirectory, "/")
}
// Set output filename if empty
if outputFilename == "" {
outputFilename = path.Join(workdir, fmt.Sprintf("%s-%s-%d.bpm", bpmpkg.PkgInfo.Name, bpmpkg.PkgInfo.Version, bpmpkg.PkgInfo.Revision))
// Set output directory if empty
if outputDirectory == "" {
outputDirectory = workdir
}
// Replace first tilde with user home directory
if strings.Split(outputFilename, "/")[0] == "~" {
outputFilename = strings.Replace(outputFilename, "~", homedir, 1)
if strings.Split(outputDirectory, "/")[0] == "~" {
outputDirectory = strings.Replace(outputDirectory, "~", homedir, 1)
}
// Prepend current working directory to output filename if not an absolute path
if outputFilename != "" && !strings.HasPrefix(outputFilename, "/") {
outputFilename = filepath.Join(workdir, outputFilename)
// Prepend current working directory to output directory if not an absolute path
if outputDirectory != "" && !strings.HasPrefix(outputDirectory, "/") {
outputDirectory = filepath.Join(workdir, outputDirectory)
}
// Clean path
path.Clean(outputFilename)
path.Clean(outputDirectory)
// Append archive filename if path is set to a directory
if stat, err := os.Stat(outputFilename); err == nil && stat.IsDir() {
outputFilename = path.Join(outputFilename, fmt.Sprintf("%s-%s-%d.bpm", bpmpkg.PkgInfo.Name, bpmpkg.PkgInfo.Version, bpmpkg.PkgInfo.Revision))
// Ensure output directory exists and is a directory
stat, err := os.Stat(outputDirectory)
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 {
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(" -n lists only the names of installed packages")
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(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
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-all Same as --reinstall but also reinstalls 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(" --unused removes only packages that aren't required as dependencies by other packages")
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(" -R=<path> lets you define the root path which will be used")
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(" -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(" -v Show additional information about what BPM is doing")
fmt.Println(" -d installs required dependencies for package compilation")
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("\033[1m----------------\033[0m")
@ -818,6 +838,10 @@ func resolveFlags() {
cleanupFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
cleanupFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
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
// File flags
fileFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError)
@ -827,7 +851,7 @@ func resolveFlags() {
compileFlagSet := flag.NewFlagSet("Compile flags", flag.ExitOnError)
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.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(&yesAll, "y", false, "Skip confirmation prompts")
@ -873,6 +897,18 @@ func resolveFlags() {
return
}
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 {
err := fileFlagSet.Parse(subcommandArgs)
if err != nil {

View File

@ -15,7 +15,7 @@ import (
var rootCompilationUID = "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
outputBpmPackages = make(map[string]string)
@ -60,7 +60,13 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
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
if _, err := os.Stat(tempDirectory); err == nil {
@ -144,8 +150,10 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run()
if err != nil {
return nil, err
@ -163,29 +171,27 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run()
if err != nil {
return nil, err
}
}
// Variable that will be used later
isSplitPkg := true
// Get all packages to compile
packagesToCompile := bpmpkg.PkgInfo.SplitPackages
if len(packagesToCompile) == 0 {
if !bpmpkg.PkgInfo.IsSplitPackage() {
packagesToCompile = append(packagesToCompile, bpmpkg.PkgInfo)
isSplitPkg = false
}
// Compile each package
for _, pkg := range packagesToCompile {
// Get package function name
packageFunctionName := "package"
if isSplitPkg {
if bpmpkg.PkgInfo.IsSplitPackage() {
packageFunctionName = "package_" + pkg.Name
}
@ -221,8 +227,10 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run()
if err != nil {
return nil, err
@ -234,36 +242,17 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run()
if err != nil {
return nil, fmt.Errorf("files.tar.gz archive could not be created: %s", err)
}
// Clone source package info
var pkgInfo PackageInfo
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
}
}
pkgInfo := *pkg
// Set package type to binary
pkgInfo.Type = "binary"
@ -313,8 +302,10 @@ func CompileSourcePackage(archiveFilename, outputFilename string, skipChecks boo
return nil, err
}
cmd.Env = append(env, "CURRENT_DIR="+currentDir)
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
if os.Getuid() == 0 {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
}
err = cmd.Run()
if err != nil {
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
}
// Set output filename if split package
if len(bpmpkg.PkgInfo.SplitPackages) != 1 {
// 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))
}
// Set output filename
outputFilename := path.Join(outputDirectory, fmt.Sprintf("%s-%s-%d-%s.bpm", pkgInfo.Name, pkgInfo.Version, pkgInfo.Revision, pkgInfo.Arch))
// Move final BPM archive
err = os.Rename(path.Join(tempDirectory, "final-archive.bpm"), outputFilename)

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"path"
"slices"
)
@ -25,6 +26,7 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein
Changes: make(map[string]string),
RootDir: rootDir,
ForceInstallationReason: installationReason,
compiledPackages: make(map[string]string),
}
// Resolve packages
@ -35,12 +37,25 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein
if err != nil {
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
}
if bpmpkg.PkgInfo.Type == "source" && len(bpmpkg.PkgInfo.SplitPackages) != 0 {
return nil, fmt.Errorf("direct source package installation has not been implemented")
if reinstallMethod == ReinstallMethodNone && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
continue
}
operation.AppendAction(&InstallPackageAction{
@ -69,10 +84,6 @@ func InstallPackages(rootDir string, installationReason InstallationReason, rein
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{
IsDependency: false,
RepositoryEntry: entry,
@ -128,6 +139,7 @@ func RemovePackages(rootDir string, removeUnusedPackagesOnly, cleanupDependencie
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
compiledPackages: make(map[string]string),
}
// Search for packages
@ -164,6 +176,7 @@ func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err
UnresolvedDepends: make([]string, 0),
Changes: make(map[string]string),
RootDir: rootDir,
compiledPackages: make(map[string]string),
}
// Do package cleanup
@ -175,6 +188,88 @@ func CleanupPackages(rootDir string, verbose bool) (operation *BPMOperation, err
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
func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependencies, forceInstallation, verbose bool) (operation *BPMOperation, err error) {
// Sync repositories
@ -206,6 +301,7 @@ func UpdatePackages(rootDir string, syncDatabase bool, installOptionalDependenci
Changes: make(map[string]string),
RootDir: rootDir,
ForceInstallationReason: InstallationReasonUnknown,
compiledPackages: make(map[string]string),
}
// Search for packages

View File

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

View File

@ -70,6 +70,25 @@ func (pkgInfo *PackageInfo) GetFullVersion() string {
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
const (
@ -149,7 +168,6 @@ func ReadPackage(filename string) (*BPMPackage, error) {
var pkgFiles []*PackageFileEntry
if _, err := os.Stat(filename); os.IsNotExist(err) {
fmt.Println("a")
return nil, err
}
@ -388,7 +406,7 @@ func executePackageScripts(filename, rootDir string, operation packageOperation,
}
func ReadPackageInfo(contents string) (*PackageInfo, error) {
pkgInfo := PackageInfo{
pkgInfo := &PackageInfo{
Name: "",
Description: "",
Version: "",
@ -410,6 +428,8 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
if err != nil {
return nil, err
}
// Ensure required fields are set properly
if pkgInfo.Name == "" {
return nil, errors.New("this package contains no name")
} else if pkgInfo.Description == "" {
@ -423,10 +443,46 @@ func ReadPackageInfo(contents string) (*PackageInfo, error) {
} else if pkgInfo.Type == "" {
return nil, errors.New("this package contains no type")
}
for i := 0; i < len(pkgInfo.Keep); i++ {
pkgInfo.Keep[i] = strings.TrimPrefix(pkgInfo.Keep[i], "/")
for _, val := range pkgInfo.Keep {
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 {

View File

@ -73,10 +73,56 @@ func (repo *Repository) ReadLocalDatabase() error {
return err
}
for _, p := range entry.Info.Provides {
repo.VirtualPackages[p] = append(repo.VirtualPackages[p], entry.Info.Name)
// Create repository entries
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
@ -220,16 +266,16 @@ func (repo *Repository) FetchPackage(pkg string) (string, error) {
}
defer resp.Body.Close()
err = os.MkdirAll("/var/cache/bpm/packages/", 0755)
err = os.MkdirAll("/var/cache/bpm/fetched/", 0755)
if err != nil {
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 {
return "", err
}
defer out.Close()
_, 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
}