Improve hook system and add pre-transaction hooks

This commit is contained in:
2026-05-10 16:43:58 +03:00
parent a4e20b7d88
commit 9122ffb9ca
4 changed files with 187 additions and 108 deletions
+72 -16
View File
@@ -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
+4 -4
View File
@@ -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),
+44 -66
View File
@@ -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
}
+67 -22
View File
@@ -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
}