From 9122ffb9ca3bdeb31552b6cb9fdd8df36a235f02 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Sun, 10 May 2026 16:43:58 +0300 Subject: [PATCH] Improve hook system and add pre-transaction hooks --- src/bpm/main.go | 88 +++++++++++++++++++++++++------ src/bpmlib/general.go | 8 +-- src/bpmlib/hooks.go | 110 ++++++++++++++++----------------------- src/bpmlib/operations.go | 89 +++++++++++++++++++++++-------- 4 files changed, 187 insertions(+), 108 deletions(-) diff --git a/src/bpm/main.go b/src/bpm/main.go index 81f3590..c743884 100644 --- a/src/bpm/main.go +++ b/src/bpm/main.go @@ -662,6 +662,9 @@ func installPackages() { return } + // Get files that will be modifie during this operation + operation.GetModifiedFiles() + if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" || bpmlib.MainBPMConfig.ShowSourcePackageContents == "install-only" { // Show source package contents sourcePackagesShown, err := operation.ShowSourcePackageContent() @@ -684,6 +687,15 @@ func installPackages() { // Get optional dependencies optionalDepends := operation.GetOptionalDependencies() + // Executing pre-operation hooks + fmt.Println("Running pre-operation hooks...") + err = operation.RunPreHooks(verbose) + if err != nil { + log.Printf("Error: could not run pre-operation hooks: %s\n", err) + exitCode = 1 + return + } + // Execute operation err = operation.Execute(verbose, force) if err != nil { @@ -692,15 +704,17 @@ func installPackages() { return } - // Executing hooks - fmt.Println("Running hooks...") - err = operation.RunHooks(verbose) + // Executing post-operation hooks + fmt.Println("Running post-operation hooks...") + err = operation.RunPostHooks(verbose) if err != nil { - log.Printf("Error: could not run hooks: %s\n", err) + log.Printf("Error: could not run post-operation hooks: %s\n", err) exitCode = 1 return } + fmt.Println("Operation complete!") + // Show optional dependencies if len(optionalDepends) != 0 { // List optional dependencies @@ -801,6 +815,18 @@ func removePackages() { } } + // Get files that will be modifie during this operation + operation.GetModifiedFiles() + + // Executing pre-operation hooks + fmt.Println("Running pre-operation hooks...") + err = operation.RunPreHooks(verbose) + if err != nil { + log.Printf("Error: could not run pre-operation hooks: %s\n", err) + exitCode = 1 + return + } + // Execute operation err = operation.Execute(verbose, force) if err != nil { @@ -809,14 +835,16 @@ func removePackages() { return } - // Executing hooks - fmt.Println("Running hooks...") - err = operation.RunHooks(verbose) + // Executing post-operation hooks + fmt.Println("Running post-operation hooks...") + err = operation.RunPostHooks(verbose) if err != nil { - log.Printf("Error: could not run hooks: %s\n", err) + log.Printf("Error: could not run post-operation hooks: %s\n", err) exitCode = 1 return } + + fmt.Println("Operation complete!") } func doCleanup() { @@ -922,6 +950,18 @@ func doCleanup() { } } + // Get files that will be modifie during this operation + operation.GetModifiedFiles() + + // Executing pre-operation hooks + fmt.Println("Running pre-operation hooks...") + err = operation.RunPreHooks(verbose) + if err != nil { + log.Printf("Error: could not run pre-operation hooks: %s\n", err) + exitCode = 1 + return + } + // Execute operation err = operation.Execute(verbose, force) if err != nil { @@ -930,14 +970,16 @@ func doCleanup() { return } - // Executing hooks - fmt.Println("Running hooks...") - err = operation.RunHooks(verbose) + // Executing post-operation hooks + fmt.Println("Running post-operation hooks...") + err = operation.RunPostHooks(verbose) if err != nil { - log.Printf("Error: could not run hooks: %s\n", err) + log.Printf("Error: could not run post-operation hooks: %s\n", err) exitCode = 1 return } + + fmt.Println("Operation complete!") } } @@ -1083,6 +1125,9 @@ func updatePackages() { return } + // Get files that will be modifie during this operation + operation.GetModifiedFiles() + if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" { // Show source package contents sourcePackagesShown, err := operation.ShowSourcePackageContent() @@ -1105,6 +1150,15 @@ func updatePackages() { // Get optional dependencies optionalDepends := operation.GetOptionalDependencies() + // Executing pre-operation hooks + fmt.Println("Running pre-operation hooks...") + err = operation.RunPreHooks(verbose) + if err != nil { + log.Printf("Error: could not run pre-operation hooks: %s\n", err) + exitCode = 1 + return + } + // Execute operation err = operation.Execute(verbose, force) if err != nil { @@ -1113,15 +1167,17 @@ func updatePackages() { return } - // Executing hooks - fmt.Println("Running hooks...") - err = operation.RunHooks(verbose) + // Executing post-operation hooks + fmt.Println("Running post-operation hooks...") + err = operation.RunPostHooks(verbose) if err != nil { - log.Printf("Error: could not run hooks: %s\n", err) + log.Printf("Error: could not run post-operation hooks: %s\n", err) exitCode = 1 return } + fmt.Println("Operation complete!") + // Show optional dependencies if len(optionalDepends) != 0 { // List optional dependencies diff --git a/src/bpmlib/general.go b/src/bpmlib/general.go index 07a8be3..73ee455 100644 --- a/src/bpmlib/general.go +++ b/src/bpmlib/general.go @@ -17,7 +17,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason, operation = &BPMOperation{ Actions: make([]OperationAction, 0), UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), + ModifiedFiles: make(map[string]string), RunChecks: runChecks, RootDir: rootDir, compiledPackages: make(map[string]string), @@ -189,7 +189,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages .. operation = &BPMOperation{ Actions: make([]OperationAction, 0), UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), + ModifiedFiles: make(map[string]string), RootDir: rootDir, compiledPackages: make(map[string]string), } @@ -267,7 +267,7 @@ func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOpe operation = &BPMOperation{ Actions: make([]OperationAction, 0), UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), + ModifiedFiles: make(map[string]string), RootDir: rootDir, compiledPackages: make(map[string]string), } @@ -395,7 +395,7 @@ func UpdatePackages(rootDir string, syncDatabase, allowDowngrades, forceInstalla operation = &BPMOperation{ Actions: make([]OperationAction, 0), UnresolvedDepends: make([]string, 0), - Changes: make(map[string]string), + ModifiedFiles: make(map[string]string), RunChecks: runChecks, RootDir: rootDir, compiledPackages: make(map[string]string), diff --git a/src/bpmlib/hooks.go b/src/bpmlib/hooks.go index 9d60136..5f3f38e 100644 --- a/src/bpmlib/hooks.go +++ b/src/bpmlib/hooks.go @@ -1,11 +1,11 @@ package bpmlib import ( + "bytes" "errors" "fmt" "os" "os/exec" - "path" "path/filepath" "slices" "strings" @@ -15,13 +15,13 @@ import ( ) type BPMHook struct { - SourcePath string - SourceContent string - TriggerOperations []string `yaml:"trigger_operations"` - TargetType string `yaml:"target_type"` - Targets []string `yaml:"targets"` - Depends []string `yaml:"depends"` - Run string `yaml:"run"` + SourcePath string + SourceContent string + TriggerActions []string `yaml:"trigger_actions"` + TriggerPreOperation bool `yaml:"trigger_pre_operation"` + Targets []string `yaml:"targets"` + Run string `yaml:"run"` + PassTargets bool `yaml:"pass_targets"` } // createHook returns a BPMHook instance based on the content of the given string @@ -34,13 +34,11 @@ func createHook(sourcePath string) (*BPMHook, error) { // Create base hook structure hook := &BPMHook{ - SourcePath: sourcePath, - SourceContent: string(bytes), - TriggerOperations: nil, - TargetType: "", - Targets: nil, - Depends: nil, - Run: "", + SourcePath: sourcePath, + SourceContent: string(bytes), + TriggerActions: nil, + Targets: nil, + Run: "", } // Unmarshal yaml string @@ -62,19 +60,15 @@ func (hook *BPMHook) IsValid() error { ValidOperations := []string{"install", "upgrade", "remove"} // Return error if any trigger operation is not valid or none are given - if len(hook.TriggerOperations) == 0 { + if len(hook.TriggerActions) == 0 { return errors.New("no trigger operations specified") } - for _, operation := range hook.TriggerOperations { + for _, operation := range hook.TriggerActions { if !slices.Contains(ValidOperations, operation) { return errors.New("trigger operation '" + operation + "' is not valid") } } - if hook.TargetType != "package" && hook.TargetType != "path" { - return errors.New("target type '" + hook.TargetType + "' is not valid") - } - if len(hook.Run) == 0 { return errors.New("command to run is empty") } @@ -84,55 +78,30 @@ func (hook *BPMHook) IsValid() error { } // Execute hook if all conditions are met -func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, rootDir string) error { - // Check if package dependencies are met - installedPackages, err := GetInstalledPackages(rootDir) - if err != nil { - return err - } - - for _, depend := range hook.Depends { - if !slices.Contains(installedPackages, depend) { - return nil - } - } - - // Get modified files slice - modifiedFiles := make([]*PackageFileEntry, 0) - for pkg := range packageChanges { - if GetPackage(pkg, rootDir) != nil { - modifiedFiles = append(modifiedFiles, GetPackage(pkg, rootDir).PkgFiles...) - } - - } - +func (hook *BPMHook) Execute(modifiedFiles map[string]string, preOperation bool, verbose bool, rootDir string) error { // Check if any targets are met - targetMet := false + targetsMet := make([]string, 0) for _, target := range hook.Targets { - if targetMet { - break - } - if hook.TargetType == "package" { - for change, operation := range packageChanges { - if target == change && slices.Contains(hook.TriggerOperations, operation) { - targetMet = true - break - } + for modifiedFile, action := range modifiedFiles { + // Check if this hook is triggered by this file's action + if !slices.Contains(hook.TriggerActions, action) { + continue } - } else { - glob, err := filepath.Glob(path.Join(rootDir, target)) - if err != nil { - return err + + // Check if file has already been checked + if slices.Contains(targetsMet, modifiedFile) { + continue } - for _, change := range modifiedFiles { - if slices.Contains(glob, path.Join(rootDir, change.Path)) { - targetMet = true - break - } + + if matched, _ := filepath.Match(target, modifiedFile); !matched { + continue } + + targetsMet = append(targetsMet, modifiedFile) } } - if !targetMet { + + if len(targetsMet) == 0 { return nil } @@ -141,16 +110,25 @@ func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, roo cmd := exec.Command(splitCommand[0], splitCommand[1:]...) // Setup subprocess environment cmd.Dir = "/" + // Pass targets + if hook.PassTargets { + buffer := bytes.Buffer{} + buffer.WriteString(strings.Join(targetsMet, "\n") + "\n") + + cmd.Stdin = &buffer + } // Run hook in chroot if using the -R flag if rootDir != "/" { cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir} } - if verbose { - fmt.Printf("Running hook (%s) with run command: %s\n", hook.SourcePath, strings.Join(splitCommand, " ")) + if !verbose { + fmt.Printf("Running hook (%s)\n", filepath.Base(hook.SourcePath)) + } else { + fmt.Printf("Running hook (%s) with run command: %s\n", filepath.Base(hook.SourcePath), strings.Join(splitCommand, " ")) } - err = cmd.Run() + err := cmd.Run() if err != nil { return err } diff --git a/src/bpmlib/operations.go b/src/bpmlib/operations.go index 7df342d..b5fcc28 100644 --- a/src/bpmlib/operations.go +++ b/src/bpmlib/operations.go @@ -14,7 +14,7 @@ import ( type BPMOperation struct { Actions []OperationAction UnresolvedDepends []string - Changes map[string]string + ModifiedFiles map[string]string CompilationJobs int RunChecks bool RootDir string @@ -53,24 +53,6 @@ func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) operation.Actions = append(operation.Actions[:index+1], operation.Actions[index:]...) // index < len(a) operation.Actions[index] = action } - - if action.GetActionType() == "install" { - pkgInfo := action.(*InstallPackageAction).BpmPackage.PkgInfo - if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) { - operation.Changes[pkgInfo.Name] = "install" - } else { - operation.Changes[pkgInfo.Name] = "upgrade" - } - } else if action.GetActionType() == "fetch" { - pkgInfo := action.(*FetchPackageAction).DatabaseEntry.Info - if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) { - operation.Changes[pkgInfo.Name] = "install" - } else { - operation.Changes[pkgInfo.Name] = "upgrade" - } - } else if action.GetActionType() == "remove" { - operation.Changes[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = "remove" - } } func (operation *BPMOperation) RemoveAction(pkg, actionType string) { @@ -552,7 +534,7 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st return } -func (operation *BPMOperation) RunHooks(verbose bool) error { +func (operation *BPMOperation) RunPreHooks(verbose bool) error { // Return if hooks directory does not exist if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() { return nil @@ -572,7 +554,46 @@ func (operation *BPMOperation) RunHooks(verbose bool) error { log.Printf("Error while reading hook (%s): %s", entry.Name(), err) } - err = hook.Execute(operation.Changes, verbose, operation.RootDir) + if !hook.TriggerPreOperation { + continue + } + + err = hook.Execute(operation.ModifiedFiles, false, verbose, operation.RootDir) + if err != nil { + log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err) + continue + } + } + } + + return nil +} + +func (operation *BPMOperation) RunPostHooks(verbose bool) error { + // Return if hooks directory does not exist + if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() { + return nil + } + + // Get directory entries in hooks directory + dirEntries, err := os.ReadDir(path.Join(operation.RootDir, "var/lib/bpm/hooks")) + if err != nil { + return err + } + + // Find all hooks, validate and execute them + for _, entry := range dirEntries { + if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), ".bpmhook") { + hook, err := createHook(path.Join(operation.RootDir, "var/lib/bpm/hooks", entry.Name())) + if err != nil { + log.Printf("Error while reading hook (%s): %s", entry.Name(), err) + } + + if hook.TriggerPreOperation { + continue + } + + err = hook.Execute(operation.ModifiedFiles, false, verbose, operation.RootDir) if err != nil { log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err) continue @@ -656,9 +677,34 @@ func (operation *BPMOperation) FetchPackages() (err error) { } operation.hasFetchedPackages = true + return nil } +func (operation *BPMOperation) GetModifiedFiles() { + // Get modified files + for _, action := range operation.Actions { + if action.GetActionType() == "install" { + installAction := action.(*InstallPackageAction) + isUpgrade := IsPackageInstalled(installAction.BpmPackage.PkgInfo.Name, operation.RootDir) + + for _, pkgFile := range installAction.BpmPackage.PkgFiles { + operation.ModifiedFiles[pkgFile.Path] = "install" + if isUpgrade { + operation.ModifiedFiles[pkgFile.Path] = "upgrade" + } + } + } + if action.GetActionType() == "remove" { + removeAction := action.(*RemovePackageAction) + + for _, pkgFile := range removeAction.BpmPackage.PkgFiles { + operation.ModifiedFiles[pkgFile.Path] = "remove" + } + } + } +} + func (operation *BPMOperation) Execute(verbose, force bool) (err error) { // Fetch packages if !operation.hasFetchedPackages { @@ -751,7 +797,6 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) { } } } - fmt.Println("Operation complete!") return nil }