15 Commits
10 changed files with 731 additions and 317 deletions
+86 -69
View File
@@ -89,7 +89,6 @@ func main() {
currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package installation")
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.Bool("runtime", true, "Install all runtime dependencies")
currentFlagSet.BoolP("optional", "o", false, "Install all optional dependencies")
currentFlagSet.String("installation-reason", "", "Specify the installation reason to use for the specified packages")
currentFlagSet.BoolP("reinstall", "r", false, "Reinstall the specified packages")
currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation")
@@ -142,7 +141,6 @@ func main() {
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases")
currentFlagSet.Bool("allow-downgrades", false, "Allow package downgrades")
currentFlagSet.BoolP("optional", "o", false, "Install all optional dependencies")
currentFlagSet.BoolP("skip-checks", "s", false, "Skip the check function in source.sh scripts")
currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Update installed packages", os.Args[2:])
@@ -154,7 +152,7 @@ func main() {
currentFlagSet.StringP("root", "R", "/", "Operate on specified root directory")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Show what packages own the specified paths", os.Args[2:])
getFileOwner()
getPathOwners()
case "c", "compile":
// Setup flags and help
currentFlagSet = flag.NewFlagSet("compile", flag.ExitOnError)
@@ -558,7 +556,6 @@ func installPackages() {
force, _ := currentFlagSet.GetBool("force")
yesAll, _ := currentFlagSet.GetBool("yes")
installRuntime, _ := currentFlagSet.GetBool("runtime")
installOptional, _ := currentFlagSet.GetBool("optional")
installationReason, _ := currentFlagSet.GetString("installation-reason")
reinstallPackages, _ := currentFlagSet.GetBool("reinstall")
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
@@ -620,7 +617,7 @@ func installPackages() {
}
// Create installation operation
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallPackages, installRuntime, installOptional, force, !skipChecks, verbose, packages...)
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallPackages, installRuntime, force, !skipChecks, verbose, packages...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err)
exitCode = 1
@@ -665,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()
@@ -687,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 {
@@ -695,17 +704,19 @@ 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 !installOptional && len(optionalDepends) != 0 {
if len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends {
@@ -768,6 +779,7 @@ func removePackages() {
return
} else if errors.As(err, &bpmlib.PackageRemovalDependencyErr{}) {
for pkg, dependants := range err.(bpmlib.PackageRemovalDependencyErr).RequiredPackages {
slices.Sort(dependants)
fmt.Printf("The following packages depend on package (%s): %s\n", pkg, strings.Join(dependants, ", "))
}
@@ -803,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 {
@@ -811,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() {
@@ -924,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 {
@@ -932,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!")
}
}
@@ -993,7 +1033,6 @@ func updatePackages() {
yesAll, _ := currentFlagSet.GetBool("yes")
noSync, _ := currentFlagSet.GetBool("no-sync")
allowDowngrades, _ := currentFlagSet.GetBool("allow-downgrades")
installOptional, _ := currentFlagSet.GetBool("optional")
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
compilationJobs, _ := currentFlagSet.GetInt("jobs")
@@ -1041,7 +1080,7 @@ func updatePackages() {
}
// Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, installOptional, force, !skipChecks, verbose)
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, force, !skipChecks, verbose)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err)
exitCode = 1
@@ -1086,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()
@@ -1108,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 {
@@ -1116,17 +1167,19 @@ 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 !installOptional && len(optionalDepends) != 0 {
if len(optionalDepends) != 0 {
// List optional dependencies
fmt.Println("The following optional dependenices have been discovered:")
for dependant, depends := range optionalDepends {
@@ -1138,7 +1191,7 @@ func updatePackages() {
}
}
func getFileOwner() {
func getPathOwners() {
// Get flags
rootDir, _ := currentFlagSet.GetString("root")
@@ -1161,7 +1214,7 @@ func getFileOwner() {
// Ensure file exists
stat, err := os.Lstat(path)
if os.IsNotExist(err) {
log.Printf("Error: file (%s) does not exist!\n", path)
log.Printf("Error: %s", err)
exitCode = 1
return
}
@@ -1174,57 +1227,21 @@ func getFileOwner() {
pathType = "Symlink"
}
// Get absolte path to path
absPath, err := filepath.Abs(path)
pathOwners, err := bpmlib.GetPathOwners(path, rootDir)
if err != nil {
log.Printf("Error: could not get absolute path of file (%s)\n", path)
log.Printf("Error: %s", err)
exitCode = 1
return
}
// Get path relative to rootDir
if !strings.HasPrefix(absPath, rootDir) {
log.Printf("Error: could not get path of file (%s) relative to root path", absPath)
exitCode = 1
return
}
absPath, err = filepath.Rel(rootDir, absPath)
if err != nil {
log.Printf("Error: could not get path of file (%s) relative to root path", absPath)
exitCode = 1
return
}
// Trim leading and trailing slashes
absPath = strings.TrimLeft(absPath, "/")
absPath = strings.TrimRight(absPath, "/")
// Get installed packages
pkgs, err := bpmlib.GetInstalledPackages(rootDir)
if err != nil {
log.Printf("Error: could not get installed packages: %s\n", err.Error())
exitCode = 1
return
}
// Add packages that own path to list
var pkgList []string
for _, pkg := range pkgs {
if slices.ContainsFunc(bpmlib.GetPackage(pkg, rootDir).PkgFiles, func(entry *bpmlib.PackageFileEntry) bool {
return entry.Path == absPath
}) {
pkgList = append(pkgList, pkg)
}
}
// Print packages
if len(pkgList) == 0 {
fmt.Printf("%s (%s) is not owned by any packages!\n", absPath, pathType)
if len(pathOwners) == 0 {
fmt.Printf("%s (%s) is not owned by any packages!\n", path, pathType)
exitCode = 1
return
} else {
fmt.Printf("%s (%s) is owned by the following packages:\n", absPath, pathType)
for _, pkg := range pkgList {
fmt.Printf("%s (%s) is owned by the following packages:\n", path, pathType)
for _, pkg := range pathOwners {
fmt.Println("- " + pkg)
}
}
+1
View File
@@ -8,6 +8,7 @@ import (
type MainBPMConfigStruct struct {
IgnorePackages []string `yaml:"ignore_packages"`
IgnorePaths []string `yaml:"ignore_paths"`
ShowSourcePackageContents string `yaml:"show_source_package_contents"`
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
Databases []configDatabase `yaml:"databases"`
+94 -3
View File
@@ -300,10 +300,52 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
dependantsMap := make(map[string][]string)
// Loop through all entries
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.Contains(e.Info.Depends, entry.Info.Name) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
// Skip iteration if comparing the same packages
if e.Info.Name == entry.Info.Name {
continue
}
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
continue
}
// Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
continue
}
// Loop through each virtual package
for _, vpkg := range entry.Info.Provides {
// Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
break
}
// Add installed package to list if its runtime dependencies contain a provided virtual package
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
break
}
}
}
}
@@ -332,7 +374,45 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.ContainsFunc(e.Info.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == entry.Info.Name
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
}
}
// Get keys
keySlice := slices.Collect(maps.Keys(dependantsMap))
slices.Sort(keySlice)
// Add all dependant entries to slice in alphabetical order
for _, entryName := range keySlice {
dbs := dependantsMap[entryName]
if len(dbs) > 1 {
for _, db := range dbs {
dependants = append(dependants, db+"/"+entryName)
}
} else {
dependants = append(dependants, entryName)
}
}
return dependants
}
func (entry *BPMDatabaseEntry) GetEntryMakeDependants() (dependants []string) {
dependantsMap := make(map[string][]string)
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.ContainsFunc(e.Info.MakeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
@@ -458,6 +538,7 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
}
builderWriteArray("Dependant packages", entry.GetEntryDependants(), true)
builderWriteArray("Optionally dependant packages", entry.GetEntryOptionalDependants(), true)
builderWriteArray("Make dependant packages", entry.GetEntryMakeDependants(), true)
// Other package relations
builderWriteArray("Conflicting packages", entry.Info.Conflicts, true)
@@ -490,6 +571,16 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
builder.WriteString("Installation reason: " + installationReasonString + "\n")
}
// Download size
downloadSize := entry.DownloadSize
var downloadSizeStr string
if showBytes {
downloadSizeStr = strconv.FormatInt(downloadSize, 10)
} else {
downloadSizeStr = BytesToHumanReadable(downloadSize)
}
builder.WriteString("Download size: " + downloadSizeStr + "\n")
// Installed size
if entry.Info.Type == "binary" {
installedSize := entry.InstalledSize
+102 -12
View File
@@ -5,7 +5,7 @@ import (
"strings"
)
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) {
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultipleProviders bool) (dependants []string) {
// Get installed package names
pkgs, ok := localPackageInformation[rootDir]
if !ok {
@@ -21,6 +21,7 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
@@ -29,6 +30,7 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
@@ -37,8 +39,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Loop through each virtual package
for _, vpkg := range pkgInfo.Provides {
if skipMultipleProviders && len(GetVirtualPackageInfo(vpkg, rootDir)) > 1 {
continue
}
// Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
@@ -47,6 +54,7 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its runtime dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
@@ -74,7 +82,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
// Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == pkgInfo.Name
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
@@ -84,7 +98,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its optional dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == vpkg
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
@@ -100,27 +120,30 @@ type ResolvedPackage struct {
InstallationReason InstallationReason
}
func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[string]string, includeRuntimeDepends, includeOptionalDepends bool, rootDir string) (resolved []ResolvedPackage, unresolved []string) {
func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[string]string, includeRuntimeDepends bool, rootDir string) (resolved []ResolvedPackage, unresolved []string) {
visited := make([]string, 0)
var dfs func(resolvedPkg *PackageInfo)
dfs = func(pkgInfo *PackageInfo) {
checkDependencies := func(dependencies []string, installationReason InstallationReason) {
for _, depend := range dependencies {
// Split dependency name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
// Ignore if package is already installed
if IsPackageInstalled(depend, rootDir) {
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
} else if providers := GetVirtualPackageInfo(depend, rootDir); len(providers) > 0 {
} else if providers := GetVirtualPackageInfo(dependName, rootDir); len(providers) > 0 {
continue
}
// Find database entry for dependency
var dependEntry *BPMDatabaseEntry
if resolvedVpkg, ok := resolvedVirtualPackages[depend]; ok {
if resolvedVpkg, ok := resolvedVirtualPackages[dependName]; ok {
dependEntry, _, _ = GetDatabaseEntry(resolvedVpkg)
} else if entry, _, _ := GetDatabaseEntry(depend); entry != nil {
} else if entry, _, _ := GetDatabaseEntry(dependName); entry != nil {
dependEntry = entry
} else if providers := GetDatabaseVirtualPackageEntry(depend); len(providers) > 0 {
} else if providers := GetDatabaseVirtualPackageEntry(dependName); len(providers) > 0 {
dependEntry = providers[0]
}
@@ -129,6 +152,17 @@ func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[strin
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
unresolved = append(unresolved, depend)
continue
}
// Skip ignored packages in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
if !slices.Contains(visited, dependEntry.Info.Name) {
dfs(dependEntry.Info)
resolved = append(resolved, ResolvedPackage{DatabaseEntry: dependEntry, InstallationReason: installationReason})
@@ -146,12 +180,68 @@ func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[strin
checkDependencies(pkgInfo.MakeDepends, InstallationReasonMakeDependency)
checkDependencies(pkgInfo.CheckDepends, InstallationReasonMakeDependency)
}
if includeOptionalDepends {
checkDependencies(pkgInfo.OptionalDepends, InstallationReasonManual)
}
}
dfs(pkgInfo)
return resolved, unresolved
}
func SplitPkgNameAndVersion(pkg string) (string, string, string) {
if strings.Contains(pkg, ">=") {
pkgSplit := strings.SplitN(pkg, ">=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">=", pkgVersion
} else if strings.Contains(pkg, ">") {
pkgSplit := strings.SplitN(pkg, ">", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">", pkgVersion
} else if strings.Contains(pkg, "<=") {
pkgSplit := strings.SplitN(pkg, "<=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<=", pkgVersion
} else if strings.Contains(pkg, "<") {
pkgSplit := strings.SplitN(pkg, "<", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<", pkgVersion
} else if strings.Contains(pkg, "=") {
pkgSplit := strings.SplitN(pkg, "=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "=", pkgVersion
}
return pkg, "", ""
}
func EvaluateDependency(pkg, matchVersion string) bool {
_, comparisonSymbol, pkgVersion := SplitPkgNameAndVersion(pkg)
switch comparisonSymbol {
case ">=":
return CompareVersions(matchVersion, pkgVersion) >= 0
case ">":
return CompareVersions(matchVersion, pkgVersion) > 0
case "<=":
return CompareVersions(matchVersion, pkgVersion) <= 0
case "<":
return CompareVersions(matchVersion, pkgVersion) < 0
case "=":
if cutPkgVersion, ok := strings.CutSuffix(pkgVersion, "*"); ok {
return strings.HasPrefix(matchVersion, cutPkgVersion)
} else {
return CompareVersions(matchVersion, pkgVersion) == 0
}
default:
return true
}
}
+4
View File
@@ -2,6 +2,7 @@ package bpmlib
import (
"fmt"
"slices"
"strings"
)
@@ -10,6 +11,7 @@ type PackageNotFoundErr struct {
}
func (e PackageNotFoundErr) Error() string {
slices.Sort(e.packages)
return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ")
}
@@ -18,6 +20,7 @@ type DependencyNotFoundErr struct {
}
func (e DependencyNotFoundErr) Error() string {
slices.Sort(e.dependencies)
return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ")
}
@@ -27,6 +30,7 @@ type PackageConflictErr struct {
}
func (e PackageConflictErr) Error() string {
slices.Sort(e.conflicts)
return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", "))
}
+122 -28
View File
@@ -12,12 +12,12 @@ import (
)
// InstallPackages installs the specified packages into the given root directory by fetching them from databases or directly from local bpm archives
func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallPackages bool, installRuntimeDependencies, installOptionalDependencies, forceInstallation, runChecks bool, verbose bool, packages ...string) (operation *BPMOperation, err error) {
func InstallPackages(rootDir string, forceInstallationReason InstallationReason, reinstallPackages bool, installRuntimeDependencies, forceInstallation, runChecks bool, verbose bool, packages ...string) (operation *BPMOperation, err error) {
// Setup operation struct
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),
@@ -51,7 +51,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
}
operation.AppendAction(&InstallPackageAction{
operation.Actions = append(operation.Actions, &InstallPackageAction{
File: pkg,
InstallationReason: installationReason,
BpmPackage: bpmpkg,
@@ -75,28 +75,37 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
}
operation.AppendAction(&InstallPackageAction{
operation.Actions = append(operation.Actions, &InstallPackageAction{
File: pkg,
InstallationReason: installationReason,
BpmPackage: bpmpkg,
})
} else {
// Split package name and required version
pkgName, _, _ := SplitPkgNameAndVersion(pkg)
var entry *BPMDatabaseEntry
if e, _, err := GetDatabaseEntry(pkg); err == nil {
if e, _, err := GetDatabaseEntry(pkgName); err == nil {
entry = e
} else if providers := GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 {
} else if providers := GetVirtualPackageInfo(pkgName, rootDir); len(providers) > 0 {
entry, _, err = GetDatabaseEntry(providers[0].Name)
if err != nil {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
}
} else if providers := GetDatabaseVirtualPackageEntry(pkg); len(providers) > 0 {
} else if providers := GetDatabaseVirtualPackageEntry(pkgName); len(providers) > 0 {
entry = providers[0]
} else {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
}
if !EvaluateDependency(pkg, entry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
}
if !reinstallPackages && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
continue
}
@@ -111,7 +120,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
}
operation.AppendAction(&FetchPackageAction{
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: installationReason,
DatabaseEntry: entry,
})
@@ -124,7 +133,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
// Resolve dependencies
operation.ResolveDependencies(installRuntimeDependencies, installOptionalDependencies)
operation.ResolveDependencies(installRuntimeDependencies)
if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
@@ -180,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),
}
@@ -197,7 +206,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
if bpmpkg == nil {
continue
}
operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg})
operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
}
// Do package cleanup
@@ -213,12 +222,12 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Get packages and their dependants
packageDepndants := make(map[string][]string, 0)
for _, action := range operation.Actions {
// Skip package if ignored
if slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
// Skip package if ignored in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
continue
}
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir)
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir, true)
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
}
@@ -234,7 +243,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Remove dependant packages if ignored
for pkg, required := range packageDepndants {
required = slices.DeleteFunc(required, func(pkgName string) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
return rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
})
packageDepndants[pkg] = required
}
@@ -258,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),
}
@@ -355,7 +364,7 @@ func CleanupCache(rootDir string, cleanupCompilationFiles, cleanupCompiledPackag
}
// UpdatePackages fetches the newest versions of all installed packages from
func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, installOptionalDependencies, forceInstallation, runChecks, verbose bool) (operation *BPMOperation, err error) {
func UpdatePackages(rootDir string, syncDatabase, allowDowngrades, forceInstallation, runChecks, verbose bool) (operation *BPMOperation, err error) {
// Sync databases
if syncDatabase {
err := SyncDatabase(verbose)
@@ -386,15 +395,16 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
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),
}
// Search for packages
pkgsNotFound := make([]string, 0)
for _, pkg := range pkgs {
if slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
continue
}
var entry *BPMDatabaseEntry
@@ -411,22 +421,106 @@ func UpdatePackages(rootDir string, syncDatabase bool, allowDowngrades bool, ins
} else {
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
operation.AppendAction(&FetchPackageAction{
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(),
DatabaseEntry: entry,
})
}
// Check for missing dependencies
for _, depend := range entry.Info.Depends {
// Split package name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
}
if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 {
continue
}
// Find database entry for missing dependency
dependEntry, _, err := GetDatabaseEntry(dependName)
if err != nil {
providers := GetDatabaseVirtualPackageEntry(dependName)
if len(providers) == 0 {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
dependEntry = providers[0]
}
// Skip dependency if action already exists
if ActionSliceIndex(operation.Actions, dependEntry.Info.Name) != -1 {
continue
}
// Skip dependency if ignored in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
// Fetch dependency
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: InstallationReasonDependency,
DatabaseEntry: dependEntry,
})
}
// Check for missing runtime dependencies
for _, depend := range entry.Info.RuntimeDepends {
// Split package name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
}
if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 {
continue
}
// Find database entry for missing dependency
dependEntry, _, err := GetDatabaseEntry(dependName)
if err != nil {
providers := GetDatabaseVirtualPackageEntry(dependName)
if len(providers) == 0 {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
dependEntry = providers[0]
}
// Skip dependency if ignored in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
// Fetch dependency
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: InstallationReasonDependency,
DatabaseEntry: dependEntry,
})
}
}
}
// Check for new dependencies in updated packages
operation.ResolveDependencies(true, installOptionalDependencies)
if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
} else if verbose {
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
}
// Return error if not all packages are found
if len(pkgsNotFound) != 0 {
return nil, PackageNotFoundErr{pkgsNotFound}
}
// Replace obsolete packages
+46 -67
View File
@@ -1,26 +1,27 @@
package bpmlib
import (
"bytes"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"os"
"os/exec"
"path"
"path/filepath"
"slices"
"strings"
"syscall"
"gopkg.in/yaml.v3"
)
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
@@ -33,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
@@ -61,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")
}
@@ -83,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
}
@@ -140,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
}
+36
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"slices"
"strconv"
"strings"
@@ -103,6 +104,41 @@ func GetInstalledPackages(rootDir string) (ret []string, err error) {
return ret, nil
}
func GetPathOwners(path, rootDir string) (ret []string, err error) {
// Get absolte path to path
path, err = filepath.Abs(path)
if err != nil {
return
}
path, err = filepath.Rel(rootDir, path)
if err != nil {
return
}
// Trim leading and trailing slashes
path = strings.TrimLeft(path, "/")
path = strings.TrimRight(path, "/")
// Get installed packages
pkgs, err := GetInstalledPackages(rootDir)
if err != nil {
return
}
// Add packages that own path to list
for _, pkg := range pkgs {
pkgFiles := getPackageFiles(pkg, rootDir)
if slices.ContainsFunc(pkgFiles, func(entry *PackageFileEntry) bool {
return entry.Path == path
}) {
ret = append(ret, pkg)
}
}
return ret, nil
}
func IsPackageInstalled(pkg, rootDir string) bool {
// Initialize local package information
err := InitializeLocalPackageInformation(rootDir)
+138 -107
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
@@ -23,72 +23,6 @@ type BPMOperation struct {
hasFetchedPackages bool
}
func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
for _, action := range operation.Actions {
if action.GetActionType() == "install" {
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
return true
}
} else if action.GetActionType() == "fetch" {
if action.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg {
return true
}
} else if action.GetActionType() == "remove" {
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
return true
}
}
}
return false
}
func (operation *BPMOperation) AppendAction(action OperationAction) {
operation.InsertActionAt(len(operation.Actions), action)
}
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
if len(operation.Actions) == index { // nil or empty slice or after last element
operation.Actions = append(operation.Actions, action)
} else {
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) {
operation.Actions = slices.DeleteFunc(operation.Actions, func(a OperationAction) bool {
if a.GetActionType() != actionType {
return false
}
if a.GetActionType() == "install" {
return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg
} else if a.GetActionType() == "fetch" {
return a.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg
} else if a.GetActionType() == "remove" {
return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg
}
return false
})
}
func (operation *BPMOperation) GetTotalDownloadSize() int64 {
var ret int64 = 0
for _, action := range operation.Actions {
@@ -131,7 +65,7 @@ func (operation *BPMOperation) GetFinalActionSize(rootDir string) int64 {
return ret
}
func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends, installOptionalDependencies bool) {
func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends bool) {
// Discover resolved virtual packages
resolvedVirtualPackages := make(map[string]string)
for _, value := range slices.Clone(operation.Actions) {
@@ -154,8 +88,8 @@ func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends, instal
}
// Discover all dependencies
pos := 0
for _, value := range slices.Clone(operation.Actions) {
newActions := make([]OperationAction, 0)
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
@@ -167,18 +101,19 @@ func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends, instal
continue
}
resolved, unresolved := ResolveDependencies(pkgInfo, resolvedVirtualPackages, installRuntimeDepends, installOptionalDependencies, operation.RootDir)
resolved, unresolved := ResolveDependencies(pkgInfo, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir)
// Append unresolved dependencies
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
operation.UnresolvedDepends = removeDuplicates(operation.UnresolvedDepends)
for _, resolvedPkg := range resolved {
if !operation.ActionsContainPackage(resolvedPkg.DatabaseEntry.Info.Name) && resolvedPkg.DatabaseEntry.Info.Name != pkgInfo.Name {
operation.InsertActionAt(pos, &FetchPackageAction{
if ActionSliceIndex(newActions, resolvedPkg.DatabaseEntry.Info.Name) == -1 { // Dependency not in actions slice
var action OperationAction = &FetchPackageAction{
InstallationReason: resolvedPkg.InstallationReason,
DatabaseEntry: resolvedPkg.DatabaseEntry,
})
}
newActions = append(newActions, action)
for _, vpkg := range resolvedPkg.DatabaseEntry.Info.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
@@ -186,36 +121,19 @@ func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends, instal
}
}
pos++
// Check if can move original action
if i := ActionSliceIndex(operation.Actions, resolvedPkg.DatabaseEntry.Info.Name); i != -1 {
newActions[len(newActions)-1] = operation.Actions[i]
}
}
}
pos++
}
}
func (operation *BPMOperation) RemoveNeededPackages() error {
removeActions := make(map[string]*RemovePackageAction)
for _, action := range slices.Clone(operation.Actions) {
if action.GetActionType() == "remove" {
removeActions[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = action.(*RemovePackageAction)
if ActionSliceIndex(newActions, pkgInfo.Name) == -1 {
newActions = append(newActions, value)
}
}
for pkg, action := range removeActions {
dependants := action.BpmPackage.PkgInfo.GetPackageDependants(operation.RootDir)
dependants = slices.DeleteFunc(dependants, func(d string) bool {
if _, ok := removeActions[d]; ok {
return true
}
return false
})
if len(dependants) != 0 {
operation.RemoveAction(pkg, action.GetActionType())
}
}
return nil
operation.Actions = newActions
}
func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
@@ -274,6 +192,9 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
// Loop through all dependencies
for _, depend := range depends {
// Remove required version
depend, _, _ = SplitPkgNameAndVersion(depend)
// Resolve dependency
var dependPkgInfo *PackageInfo
if providers := GetVirtualPackageInfo(depend, operation.RootDir); len(providers) > 0 {
@@ -328,10 +249,11 @@ func (operation *BPMOperation) ReplaceObsoletePackages() {
}
for _, r := range pkgInfo.Replaces {
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && !operation.ActionsContainPackage(bpmpkg.PkgInfo.Name) {
operation.InsertActionAt(0, &RemovePackageAction{
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && ActionSliceIndex(operation.Actions, bpmpkg.PkgInfo.Name) == -1 {
var action OperationAction = &RemovePackageAction{
BpmPackage: bpmpkg,
})
}
operation.Actions = slices.Insert(operation.Actions, 0, action)
}
}
}
@@ -540,16 +462,24 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
}
for _, depend := range pkgInfo.OptionalDepends {
// Get optional dependency name
dependSplit := strings.SplitN(depend, ":", 2)
dependName, _, _ := SplitPkgNameAndVersion(dependSplit[0])
// Skip if dependency is already installed
if IsPackageInstalled(dependSplit[0], operation.RootDir) {
if IsPackageInstalled(dependName, operation.RootDir) {
continue
}
// Skip if not a new dependency of the package
if installedPkg := GetPackage(pkgInfo.Name, operation.RootDir); installedPkg != nil && slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == dependSplit[0]
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == dependName
}) {
continue
}
@@ -557,7 +487,7 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
if len(dependSplit) == 2 {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", dependSplit[0], dependSplit[1]))
} else {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependSplit[0])
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependName)
}
}
}
@@ -565,7 +495,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
@@ -585,7 +515,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
@@ -669,9 +638,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 {
@@ -764,7 +758,6 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
}
}
}
fmt.Println("Operation complete!")
return nil
}
@@ -800,3 +793,41 @@ type RemovePackageAction struct {
func (action *RemovePackageAction) GetActionType() string {
return "remove"
}
func ActionSliceIndex(actions []OperationAction, pkg string) int {
for i, action := range actions {
if action.GetActionType() == "install" {
if action.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg {
return i
}
} else if action.GetActionType() == "fetch" {
if action.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg {
return i
}
} else if action.GetActionType() == "remove" {
if action.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg {
return i
}
}
}
return -1
}
func ActionSliceRemove(actions []OperationAction, pkg, actionType string) []OperationAction {
actions = slices.DeleteFunc(actions, func(a OperationAction) bool {
if a.GetActionType() != actionType {
return false
}
if a.GetActionType() == "install" {
return a.(*InstallPackageAction).BpmPackage.PkgInfo.Name == pkg
} else if a.GetActionType() == "fetch" {
return a.(*FetchPackageAction).DatabaseEntry.Info.Name == pkg
} else if a.GetActionType() == "remove" {
return a.(*RemovePackageAction).BpmPackage.PkgInfo.Name == pkg
}
return false
})
return actions
}
+102 -31
View File
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"slices"
"sort"
@@ -627,7 +628,7 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
builder.WriteString("\n")
}
}
builderWriteArray("Dependant packages", pkgInfo.GetPackageDependants(rootDir), true)
builderWriteArray("Dependant packages", pkgInfo.GetPackageDependants(rootDir, false), true)
builderWriteArray("Optionally dependant packages", pkgInfo.GetPackageOptionalDependants(rootDir), true)
// Other package relations
@@ -698,6 +699,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
extractFilename := path.Join(rootDir, header.Name)
switch header.Typeflag {
case tar.TypeDir:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping Directory: %s (Path was ignored)\n", extractFilename)
}
continue
}
if _, err := os.Stat(extractFilename); err == nil {
if verbose {
fmt.Printf("Skipping Directory: %s (Directory already exists)\n", extractFilename)
@@ -725,6 +737,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
}
bar.Add64(header.Size)
case tar.TypeReg:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping File: %s (Path was ignored)\n", extractFilename)
}
continue
}
skip := false
if _, err := os.Stat(extractFilename); err == nil {
for _, k := range bpmpkg.PkgInfo.Keep {
@@ -782,6 +805,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
}
bar.Add64(header.Size)
case tar.TypeSymlink:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping Symlink: %s (Path was ignored)\n", extractFilename)
}
continue
}
err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) {
return err
@@ -797,6 +831,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
}
bar.Add64(header.Size)
case tar.TypeLink:
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, header.Name)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping Hard Link: %s (Path was ignored)\n", extractFilename)
}
continue
}
if verbose {
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/")))
}
@@ -872,26 +917,39 @@ func installPackage(filename string, installationReason InstallationReason, root
fmt.Printf("Removing old files for package (%s)...\n", bpmpkg.PkgInfo.Name)
}
for _, entry := range fileEntries {
file := path.Join(rootDir, entry.Path)
stat, err := os.Lstat(file)
finalPath := path.Join(rootDir, entry.Path)
stat, err := os.Lstat(finalPath)
if os.IsNotExist(err) {
continue
}
if err != nil {
} else if err != nil {
return err
}
if len(files[entry.Path]) != 0 {
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, entry.Path)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Println("Skipping path: " + file + " (Path is managed by multiple packages)")
fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
}
continue
}
if len(files[entry.Path]) != 0 {
if verbose {
fmt.Println("Skipping path: " + finalPath + " (Path is managed by multiple packages)")
}
continue
}
shouldContinue := false
for _, value := range bpmpkg.PkgInfo.Keep {
if strings.HasSuffix(value, "/") {
if strings.HasPrefix(entry.Path, value) || entry.Path == strings.TrimSuffix(value, "/") {
if verbose {
fmt.Println("Skipping path: " + file + " (Path is set to be kept during reinstalls/updates)")
fmt.Println("Skipping path: " + finalPath + " (Path is set to be kept during reinstalls/updates)")
}
shouldContinue = true
continue
@@ -899,7 +957,7 @@ func installPackage(filename string, installationReason InstallationReason, root
} else {
if entry.Path == value {
if verbose {
fmt.Println("Skipping path: " + file + " (Path is set to be kept during reinstalls/updates)")
fmt.Println("Skipping path: " + finalPath + " (Path is set to be kept during reinstalls/updates)")
}
shouldContinue = true
continue
@@ -911,37 +969,37 @@ func installPackage(filename string, installationReason InstallationReason, root
}
if stat.Mode()&os.ModeSymlink != 0 {
if verbose {
fmt.Println("Removing: " + file)
fmt.Println("Removing: " + finalPath)
}
err := os.Remove(file)
err := os.Remove(finalPath)
if err != nil {
return err
}
continue
}
if stat.IsDir() {
dir, err := os.ReadDir(file)
dir, err := os.ReadDir(finalPath)
if err != nil {
return err
}
if len(dir) != 0 {
if verbose {
fmt.Println("Skipping non-empty directory: " + file)
fmt.Println("Skipping non-empty directory: " + finalPath)
}
continue
}
if verbose {
fmt.Println("Removing: " + file)
fmt.Println("Removing: " + finalPath)
}
err = os.Remove(file)
err = os.Remove(finalPath)
if err != nil {
return err
}
} else {
if verbose {
fmt.Println("Removing: " + file)
fmt.Println("Removing: " + finalPath)
}
err := os.Remove(file)
err := os.Remove(finalPath)
if err != nil {
return err
}
@@ -1111,31 +1169,44 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
// Removing package files
for _, entry := range fileEntries {
bar.Add64(entry.SizeInBytes)
file := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(file)
finalPath := path.Join(rootDir, entry.Path)
lstat, err := os.Lstat(finalPath)
if os.IsNotExist(err) {
continue
}
if err != nil {
} else if err != nil {
return err
}
// Check if path is set to be ignored
if ok := slices.ContainsFunc(MainBPMConfig.IgnorePaths, func(s string) bool {
matched, _ := filepath.Match(s, entry.Path)
return matched
}); rootDir == "/" && ok {
if verbose {
fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
}
continue
}
if len(files[entry.Path]) != 0 {
if verbose {
fmt.Println("Skipping path: " + file + "(Path is managed by multiple packages)")
fmt.Println("Skipping path: " + finalPath + "(Path is managed by multiple packages)")
}
continue
}
if lstat.Mode()&os.ModeSymlink != 0 {
if verbose {
fmt.Println("Removing: " + file)
fmt.Println("Removing: " + finalPath)
}
err := os.Remove(file)
err := os.Remove(finalPath)
if err != nil {
return err
}
continue
}
stat, err := os.Stat(file)
stat, err := os.Stat(finalPath)
if os.IsNotExist(err) {
continue
}
@@ -1143,28 +1214,28 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
return err
}
if stat.IsDir() {
dir, err := os.ReadDir(file)
dir, err := os.ReadDir(finalPath)
if err != nil {
return err
}
if len(dir) != 0 {
if verbose {
fmt.Println("Skipping non-empty directory: " + file)
fmt.Println("Skipping non-empty directory: " + finalPath)
}
continue
}
if verbose {
fmt.Println("Removing: " + file)
fmt.Println("Removing: " + finalPath)
}
err = os.Remove(file)
err = os.Remove(finalPath)
if err != nil {
return err
}
} else {
if verbose {
fmt.Println("Removing: " + file)
fmt.Println("Removing: " + finalPath)
}
err := os.Remove(file)
err := os.Remove(finalPath)
if err != nil {
return err
}