20 Commits
Author SHA1 Message Date
EnumDev 80382c849b Add package compilation flags 2026-06-27 17:22:30 +03:00
EnumDev 6f524ab6fa Add '--force' flag to compilation subcommand 2026-06-23 08:23:32 +03:00
EnumDev cb01c96ad6 Improve update operation new dependency resolution 2026-06-22 13:56:03 +03:00
EnumDev e6456105e1 Improve optional dependency detection during package installation 2026-06-21 09:47:28 +03:00
EnumDev a496777b9e Fix virtual package resolution 2026-06-14 18:57:41 +03:00
EnumDev cb191dc16a Unify normal and virtual package entry resolution 2026-06-14 17:59:44 +03:00
EnumDev 140cd3c64b Changes in package format 2026-06-10 11:41:35 +03:00
EnumDev 677da23c9d Include installed package files in upgrade actions 2026-05-28 19:35:07 +03:00
EnumDev 7f279ef8e9 Allow querying packages with version matching 2026-05-14 21:13:46 +03:00
EnumDev 3950c5f0d6 Dependency resolution improvements 2026-05-12 20:03:40 +03:00
EnumDev f3c2b9f0f6 Make 'ignore_packages' and 'ignore_paths' config options only work without rootdir 2026-05-10 19:45:07 +03:00
EnumDev dc8959c2f8 Add 'ignore_paths' config option 2026-05-10 17:26:38 +03:00
EnumDev 9122ffb9ca Improve hook system and add pre-transaction hooks 2026-05-10 16:43:58 +03:00
EnumDev a4e20b7d88 Add dependency version requirement resolution 2026-05-09 19:06:20 +03:00
EnumDev a03c9ad9ce Do not add fetch action for missing dependency again if it already exists 2026-04-26 15:13:25 +03:00
EnumDev 3f205ec412 Update GetEntryDependants function 2026-04-26 15:07:23 +03:00
EnumDev 2f9487affe Sort packages by name in error messages 2026-04-24 20:41:40 +03:00
EnumDev 596d8753a7 Allow removal of packages that depend on virtual packages with more than 1 provider 2026-04-24 20:41:16 +03:00
EnumDev 2de4eea7a4 Improve ignore_package functionality 2026-04-24 18:40:08 +03:00
EnumDev 5d0312856d Install missing dependencies on update 2026-04-20 17:17:54 +03:00
11 changed files with 1390 additions and 432 deletions
+134 -39
View File
@@ -92,7 +92,7 @@ func main() {
currentFlagSet.String("installation-reason", "", "Specify the installation reason to use for the specified packages") currentFlagSet.String("installation-reason", "", "Specify the installation reason to use for the specified packages")
currentFlagSet.BoolP("reinstall", "r", false, "Reinstall 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") currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation")
currentFlagSet.BoolP("skip-checks", "s", false, "Skip the check function in source.sh scripts") currentFlagSet.Bool("skip-checks", false, "Skip the check function in recipe.sh scripts")
setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Install the specified packages", os.Args[2:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Install the specified packages", os.Args[2:])
installPackages() installPackages()
@@ -141,7 +141,7 @@ func main() {
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts") currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases") currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases")
currentFlagSet.Bool("allow-downgrades", false, "Allow package downgrades") currentFlagSet.Bool("allow-downgrades", false, "Allow package downgrades")
currentFlagSet.BoolP("skip-checks", "s", false, "Skip the check function in source.sh scripts") currentFlagSet.Bool("skip-checks", false, "Skip the check function in recipe.sh scripts")
currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation") 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:]) setupFlagsAndHelp(currentFlagSet, fmt.Sprintf("bpm %s <options>", subcommand), "Update installed packages", os.Args[2:])
@@ -161,7 +161,7 @@ func main() {
currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package compilation") currentFlagSet.BoolP("force", "f", false, "Bypass warnings during package compilation")
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts") currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("depends", "d", false, "Install required dependencies for package compilation") currentFlagSet.BoolP("depends", "d", false, "Install required dependencies for package compilation")
currentFlagSet.BoolP("skip-checks", "s", false, "Skip the check function in source.sh scripts") currentFlagSet.Bool("skip-checks", false, "Skip the check function in recipe.sh scripts")
currentFlagSet.BoolP("keep", "k", false, "Keep compilation files after successful package compilation") currentFlagSet.BoolP("keep", "k", false, "Keep compilation files after successful package compilation")
currentFlagSet.BoolP("output-directory", "o", false, "Set the output directory for the binary packages") currentFlagSet.BoolP("output-directory", "o", false, "Set the output directory for the binary packages")
currentFlagSet.Int("output-fd", -1, "Set the file descriptor output package names will be written to") currentFlagSet.Int("output-fd", -1, "Set the file descriptor output package names will be written to")
@@ -239,18 +239,20 @@ func showPackageInfo() {
} }
for n, pkg := range packages { for n, pkg := range packages {
if showDatabaseInfo { // Deconstruct package string
var err error d, err := bpmlib.DeconstructPackageString(pkg)
var entry *bpmlib.BPMDatabaseEntry
entry, _, err = bpmlib.GetDatabaseEntry(pkg)
if err != nil { if err != nil {
if providers := bpmlib.GetDatabaseVirtualPackageEntry(pkg); len(providers) > 0 { log.Printf("Error: could not deconstruct package string: %s\n", err)
entry = providers[0]
} else {
log.Printf("Error: could not find package (%s) in any database\n", pkg)
exitCode = 1 exitCode = 1
return return
} }
if showDatabaseInfo {
entry := bpmlib.ResolveDatabaseEntry(d, rootDir)
if entry == nil {
log.Printf("Error: could not find package (%s) in any database\n", pkg)
exitCode = 1
return
} }
if n != 0 { if n != 0 {
@@ -263,8 +265,8 @@ func showPackageInfo() {
var bpmpkg *bpmlib.BPMPackage var bpmpkg *bpmlib.BPMPackage
isFile := false isFile := false
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() { if stat, err := os.Stat(d.PkgName); err == nil && !stat.IsDir() {
bpmpkg, err = bpmlib.ReadPackage(pkg) bpmpkg, err = bpmlib.ReadPackage(d.PkgName)
if err != nil { if err != nil {
log.Printf("Error: could not read package: %s\n", err) log.Printf("Error: could not read package: %s\n", err)
exitCode = 1 exitCode = 1
@@ -272,22 +274,30 @@ func showPackageInfo() {
} }
isFile = true isFile = true
} else { } else {
if providers := bpmlib.GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 { if providers := bpmlib.GetVirtualPackageInfo(d.PkgName, rootDir); len(providers) > 0 {
bpmpkg = bpmlib.GetPackage(providers[0].Name, rootDir) bpmpkg = bpmlib.GetPackage(providers[0].Name, rootDir)
} else { } else {
bpmpkg = bpmlib.GetPackage(pkg, rootDir) bpmpkg = bpmlib.GetPackage(d.PkgName, rootDir)
} }
} }
if bpmpkg == nil { if bpmpkg == nil {
log.Printf("Error: package (%s) is not installed\n", pkg) log.Printf("Error: package (%s) is not installed\n", pkg)
exitCode = 1 exitCode = 1
return return
} }
if !bpmlib.EvaluatePackageString(bpmpkg.PkgInfo, d) {
log.Printf("Error: package (%s) is not installed\n", pkg)
exitCode = 1
return
}
if n != 0 { if n != 0 {
fmt.Println() fmt.Println()
} }
if isFile { if isFile {
abs, err := filepath.Abs(pkg) abs, err := filepath.Abs(d.PkgName)
if err != nil { if err != nil {
log.Printf("Error: could not get absolute path of file (%s)\n", abs) log.Printf("Error: could not get absolute path of file (%s)\n", abs)
exitCode = 1 exitCode = 1
@@ -618,7 +628,7 @@ func installPackages() {
// Create installation operation // Create installation operation
operation, err := bpmlib.InstallPackages(rootDir, ir, reinstallPackages, installRuntime, 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{}) { if errors.As(err, &bpmlib.PackageNotResolvedErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
return return
@@ -662,6 +672,9 @@ func installPackages() {
return return
} }
// Get files that will be modifie during this operation
operation.GetModifiedFiles()
if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" || bpmlib.MainBPMConfig.ShowSourcePackageContents == "install-only" { if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" || bpmlib.MainBPMConfig.ShowSourcePackageContents == "install-only" {
// Show source package contents // Show source package contents
sourcePackagesShown, err := operation.ShowSourcePackageContent() sourcePackagesShown, err := operation.ShowSourcePackageContent()
@@ -684,6 +697,15 @@ func installPackages() {
// Get optional dependencies // Get optional dependencies
optionalDepends := operation.GetOptionalDependencies() 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 // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -692,15 +714,17 @@ func installPackages() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { 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 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
// Show optional dependencies // Show optional dependencies
if len(optionalDepends) != 0 { if len(optionalDepends) != 0 {
// List optional dependencies // List optional dependencies
@@ -759,12 +783,13 @@ func removePackages() {
// Create remove operation // Create remove operation
operation, err := bpmlib.RemovePackages(rootDir, force, cleanupPackages, packages...) operation, err := bpmlib.RemovePackages(rootDir, force, cleanupPackages, packages...)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotResolvedErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
return return
} else if errors.As(err, &bpmlib.PackageRemovalDependencyErr{}) { } else if errors.As(err, &bpmlib.PackageRemovalDependencyErr{}) {
for pkg, dependants := range err.(bpmlib.PackageRemovalDependencyErr).RequiredPackages { 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, ", ")) fmt.Printf("The following packages depend on package (%s): %s\n", pkg, strings.Join(dependants, ", "))
} }
@@ -800,6 +825,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 // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -808,14 +845,16 @@ func removePackages() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { 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 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
} }
func doCleanup() { func doCleanup() {
@@ -888,7 +927,7 @@ func doCleanup() {
// Create cleanup operation // Create cleanup operation
operation, err := bpmlib.CleanupPackages(cleanupMakeDepends, rootDir) operation, err := bpmlib.CleanupPackages(cleanupMakeDepends, rootDir)
if errors.As(err, &bpmlib.PackageNotFoundErr{}) || errors.As(err, &bpmlib.DependencyNotFoundErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) { if errors.As(err, &bpmlib.PackageNotResolvedErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
return return
@@ -921,6 +960,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 // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -929,14 +980,16 @@ func doCleanup() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { 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 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
} }
} }
@@ -1038,7 +1091,7 @@ func updatePackages() {
// Create update operation // Create update operation
operation, err := bpmlib.UpdatePackages(rootDir, !noSync, allowDowngrades, 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{}) { if errors.As(err, &bpmlib.PackageNotResolvedErr{}) || errors.As(err, &bpmlib.PackageConflictErr{}) {
log.Printf("Error: %s", err) log.Printf("Error: %s", err)
exitCode = 1 exitCode = 1
return return
@@ -1082,6 +1135,9 @@ func updatePackages() {
return return
} }
// Get files that will be modifie during this operation
operation.GetModifiedFiles()
if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" { if bpmlib.MainBPMConfig.ShowSourcePackageContents == "always" {
// Show source package contents // Show source package contents
sourcePackagesShown, err := operation.ShowSourcePackageContent() sourcePackagesShown, err := operation.ShowSourcePackageContent()
@@ -1104,6 +1160,15 @@ func updatePackages() {
// Get optional dependencies // Get optional dependencies
optionalDepends := operation.GetOptionalDependencies() 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 // Execute operation
err = operation.Execute(verbose, force) err = operation.Execute(verbose, force)
if err != nil { if err != nil {
@@ -1112,15 +1177,17 @@ func updatePackages() {
return return
} }
// Executing hooks // Executing post-operation hooks
fmt.Println("Running hooks...") fmt.Println("Running post-operation hooks...")
err = operation.RunHooks(verbose) err = operation.RunPostHooks(verbose)
if err != nil { 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 exitCode = 1
return return
} }
fmt.Println("Operation complete!")
// Show optional dependencies // Show optional dependencies
if len(optionalDepends) != 0 { if len(optionalDepends) != 0 {
// List optional dependencies // List optional dependencies
@@ -1196,6 +1263,7 @@ func compilePackage() {
rootDir, _ := currentFlagSet.GetString("root") rootDir, _ := currentFlagSet.GetString("root")
verbose, _ := currentFlagSet.GetBool("verbose") verbose, _ := currentFlagSet.GetBool("verbose")
yesAll, _ := currentFlagSet.GetBool("yes") yesAll, _ := currentFlagSet.GetBool("yes")
force, _ := currentFlagSet.GetBool("force")
keepCompilationFiles, _ := currentFlagSet.GetBool("keep") keepCompilationFiles, _ := currentFlagSet.GetBool("keep")
installSrcPkgDepends, _ := currentFlagSet.GetBool("depends") installSrcPkgDepends, _ := currentFlagSet.GetBool("depends")
skipChecks, _ := currentFlagSet.GetBool("skip-checks") skipChecks, _ := currentFlagSet.GetBool("skip-checks")
@@ -1228,8 +1296,16 @@ func compilePackage() {
// Compile packages // Compile packages
for _, sourcePackage := range sourcePackages { for _, sourcePackage := range sourcePackages {
if _, err := os.Stat(sourcePackage); os.IsNotExist(err) { // Deconstruct package string
log.Printf("Error: file (%s) does not exist!", sourcePackage) d, err := bpmlib.DeconstructPackageString(sourcePackage)
if err != nil {
log.Printf("Error: could not deconstruct package string: %s\n", err)
exitCode = 1
return
}
if _, err := os.Stat(d.PkgName); os.IsNotExist(err) {
log.Printf("Error: file (%s) does not exist!", d.PkgName)
exitCode = 1 exitCode = 1
return return
} }
@@ -1249,6 +1325,12 @@ func compilePackage() {
return return
} }
if !bpmlib.EvaluatePackageString(bpmpkg.PkgInfo, d) {
log.Printf("Error: could not evaluate package: %s\n", sourcePackage)
exitCode = 1
return
}
// Get common, make and check dependencies // Get common, make and check dependencies
totalDepends := make([]string, 0) totalDepends := make([]string, 0)
totalDepends = append(totalDepends, bpmpkg.PkgInfo.Depends...) totalDepends = append(totalDepends, bpmpkg.PkgInfo.Depends...)
@@ -1275,6 +1357,19 @@ func compilePackage() {
// Install missing source package dependencies // Install missing source package dependencies
if installSrcPkgDepends && len(unmetDepends) > 0 { if installSrcPkgDepends && len(unmetDepends) > 0 {
// Ignore packages that can't be found in any databases
if force {
unmetDepends = slices.DeleteFunc(unmetDepends, func(s string) bool {
// Deconstruct package string
d, err := bpmlib.DeconstructPackageString(s)
if err != nil {
return false
}
return bpmlib.ResolveDatabaseEntry(d, rootDir) == nil
})
}
// Get path to current executable // Get path to current executable
executable, err := os.Executable() executable, err := os.Executable()
if err != nil { if err != nil {
@@ -1403,7 +1498,7 @@ func compilePackage() {
return return
} }
outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, compilationJobs, skipChecks, keepCompilationFiles, verbose) outputBpmPackages, err := bpmlib.CompileSourcePackage(sourcePackage, outputDirectory, d.Flags, compilationJobs, skipChecks, keepCompilationFiles, verbose)
if err != nil { if err != nil {
// Remove unused packages // Remove unused packages
cleanupFunc() cleanupFunc()
+73 -26
View File
@@ -24,7 +24,7 @@ import (
var rootCompilationUID = "65534" var rootCompilationUID = "65534"
var rootCompilationGID = "65534" var rootCompilationGID = "65534"
func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJobs int, skipChecks, keepCompilationFiles, verbose bool) (outputBpmPackages map[string]string, err error) { func CompileSourcePackage(archiveFilename, outputDirectory string, flags map[string]string, compilationJobs int, skipChecks, keepCompilationFiles, verbose bool) (outputBpmPackages map[string]string, err error) {
// Set compilation jobs // Set compilation jobs
if compilationJobs <= 0 || compilationJobs > runtime.NumCPU() { if compilationJobs <= 0 || compilationJobs > runtime.NumCPU() {
compilationJobs = runtime.NumCPU() compilationJobs = runtime.NumCPU()
@@ -96,8 +96,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
return nil, err return nil, err
} }
// Extract source.sh file // Extract recipe.sh file
err = extractTarballFile(archiveFilename, "source.sh", tempDirectory, uid, gid) err = extractTarballFile(archiveFilename, "recipe.sh", tempDirectory, uid, gid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -146,6 +146,15 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
env = append(env, "BPM_PKG_URL="+bpmpkg.PkgInfo.Url) env = append(env, "BPM_PKG_URL="+bpmpkg.PkgInfo.Url)
env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch) env = append(env, "BPM_PKG_ARCH="+bpmpkg.PkgInfo.OutputArch)
env = append(env, "BPM_JOBS="+strconv.Itoa(compilationJobs)) env = append(env, "BPM_JOBS="+strconv.Itoa(compilationJobs))
for _, flag := range bpmpkg.PkgInfo.Flags {
if value, ok := flags[flag.Name]; ok {
env = append(env, "BPM_PKG_FLAG_"+strings.ToUpper(flag.Name)+"="+value)
fmt.Printf("Package flag: %s=%s\n", flag.Name, value)
} else {
env = append(env, "BPM_PKG_FLAG_"+strings.ToUpper(flag.Name)+"="+flag.DefaultValue)
fmt.Printf("Package flag: %s=%s\n", flag.Name, flag.DefaultValue)
}
}
env = append(env, CompilationBPMConfig.CompilationEnvironment...) env = append(env, CompilationBPMConfig.CompilationEnvironment...)
// Set common flags used for limiting job count // Set common flags used for limiting job count
@@ -166,10 +175,10 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
env = append(env, "CMAKE_BUILD_PARALLEL_LEVEL="+strconv.Itoa(compilationJobs)) env = append(env, "CMAKE_BUILD_PARALLEL_LEVEL="+strconv.Itoa(compilationJobs))
env = append(env, "CARGO_BUILD_JOBS="+strconv.Itoa(compilationJobs)) env = append(env, "CARGO_BUILD_JOBS="+strconv.Itoa(compilationJobs))
// Execute prepare and build functions in source.sh script // Execute prepare and build functions in recipe.sh script
cmd := exec.Command("bash", "-c", cmd := exec.Command("bash", "-c",
"set -a\n"+ // Source and export functions and variables in source.sh script "set -a\n"+ // Source and export functions and variables in recipe.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+ ". \"${BPM_WORKDIR}\"/recipe.sh\n"+
"set +a\n"+ "set +a\n"+
"[[ $(type -t prepare) == \"function\" ]] && { echo \"Running prepare() function...\"; bash -e -c 'cd \"$BPM_WORKDIR\" && prepare' || exit 1; }\n"+ // Run prepare() function if it exists "[[ $(type -t prepare) == \"function\" ]] && { echo \"Running prepare() function...\"; bash -e -c 'cd \"$BPM_WORKDIR\" && prepare' || exit 1; }\n"+ // Run prepare() function if it exists
"[[ $(type -t build) == \"function\" ]] && { echo \"Running build() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists "[[ $(type -t build) == \"function\" ]] && { echo \"Running build() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && build' || exit 1; }\n"+ // Run build() function if it exists
@@ -187,11 +196,11 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
return nil, err return nil, err
} }
// Execute check function in source.sh script if not skipping checks // Execute check function in recipe.sh script if not skipping checks
if !skipChecks { if !skipChecks {
cmd = exec.Command("bash", "-c", cmd = exec.Command("bash", "-c",
"set -a\n"+ // Source and export functions and variables in source.sh script "set -a\n"+ // Source and export functions and variables in recipe.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+ ". \"${BPM_WORKDIR}\"/recipe.sh\n"+
"set +a\n"+ "set +a\n"+
"[[ $(type -t check) == \"function\" ]] && { echo \"Running check() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && check' || exit 1; }\n"+ // Run check() function if it exists "[[ $(type -t check) == \"function\" ]] && { echo \"Running check() function...\"; bash -e -c 'cd \"$BPM_SOURCE\" && check' || exit 1; }\n"+ // Run check() function if it exists
"exit 0") "exit 0")
@@ -238,10 +247,10 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
packageFunctionName = "package_" + pkg.Name packageFunctionName = "package_" + pkg.Name
} }
// Execute package function in source.sh script and generate package file list // Execute package function in recipe.sh script and generate package file list
cmd = exec.Command("bash", "-c", cmd = exec.Command("bash", "-c",
"set -a\n"+ // Source and export functions and variables in source.sh script "set -a\n"+ // Source and export functions and variables in recipe.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+ ". \"${BPM_WORKDIR}\"/recipe.sh\n"+
"set +a\n"+ "set +a\n"+
"echo \"Running "+packageFunctionName+"() function...\"\n"+ "echo \"Running "+packageFunctionName+"() function...\"\n"+
"( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n") // Run package() function "( cd \"$BPM_SOURCE\" && fakeroot -s \"$BPM_WORKDIR\"/fakeroot_file bash -e -c '"+packageFunctionName+"' ) || exit 1\n") // Run package() function
@@ -329,7 +338,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
// Generate package file list // Generate package file list
fmt.Println("Generating package file list...") fmt.Println("Generating package file list...")
cmd = exec.Command("bash", "-c", "fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/pkg.files") cmd = exec.Command("bash", "-c", "fakeroot -i \"$BPM_WORKDIR\"/fakeroot_file find \"$BPM_OUTPUT\" -mindepth 1 -printf \"%P %#m %U %G %s\\n\" > \"$BPM_WORKDIR\"/files.txt")
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -345,7 +354,15 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
// Create gzip-compressed archive for the package files // Create gzip-compressed archive for the package files
fmt.Println("Generating compressed file archive...") fmt.Println("Generating compressed file archive...")
cmd = exec.Command("bash", "-c", fmt.Sprintf("find %s -printf \"%%P\\n\" | fakeroot -i %s/fakeroot_file tar -czf files.tar.gz --no-recursion -C %s -T -", "output_"+pkg.Name, tempDirectory, "output_"+pkg.Name)) cmd = exec.Command("bash", "-c", fmt.Sprintf(`find %s -printf "%%P\n" | fakeroot -i %s/fakeroot_file tar czf files.tar.gz \
--sort=name \
--pax-option=exthdr.name=%%d/PaxHeaders/%%f,delete=atime,delete=ctime \
--mtime="UTC 1970-01-01" \
--numeric-owner \
--no-recursion \
-C %s \
-T -`,
"output_"+pkg.Name, tempDirectory, "output_"+pkg.Name))
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -373,6 +390,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
pkgInfo.SplitPackages = nil pkgInfo.SplitPackages = nil
pkgInfo.Downloads = nil pkgInfo.Downloads = nil
// Set built flag values and add their dependencies
for i, flag := range pkgInfo.Flags {
if value, ok := flags[flag.Name]; ok {
flag.BuiltValue = value
} else {
flag.BuiltValue = flag.DefaultValue
}
acceptedValueIndex := slices.IndexFunc(flag.AcceptedValues, func(acceptedValue PackageAcceptedValue) bool {
return acceptedValue.Value == flag.BuiltValue
})
if acceptedValueIndex < 0 {
return nil, fmt.Errorf("flag value not accepted: %s=%s", flag.Name, flag.BuiltValue)
}
acceptedValue := flag.AcceptedValues[acceptedValueIndex]
pkgInfo.Flags[i] = flag
pkgInfo.Depends = append(pkgInfo.Depends, acceptedValue.Depends...)
pkgInfo.RuntimeDepends = append(pkgInfo.RuntimeDepends, acceptedValue.RuntimeDepends...)
pkgInfo.OptionalDepends = append(pkgInfo.OptionalDepends, acceptedValue.OptionalDepends...)
pkgInfo.MakeDepends = append(pkgInfo.MakeDepends, acceptedValue.MakeDepends...)
pkgInfo.CheckDepends = append(pkgInfo.CheckDepends, acceptedValue.CheckDepends...)
}
// Marshal package info // Marshal package info
pkgInfoBytes, err := yaml.Marshal(pkgInfo) pkgInfoBytes, err := yaml.Marshal(pkgInfo)
if err != nil { if err != nil {
@@ -380,26 +422,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
} }
pkgInfoBytes = append(pkgInfoBytes, '\n') pkgInfoBytes = append(pkgInfoBytes, '\n')
// Create pkg.info file // Create info.yml file
err = os.WriteFile(path.Join(tempDirectory, "pkg.info"), pkgInfoBytes, 0644) err = os.WriteFile(path.Join(tempDirectory, "info.yml"), pkgInfoBytes, 0644)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Change pkg.info file owner // Change info.yml file owner
err = os.Chown(path.Join(tempDirectory, "pkg.info"), uid, gid) err = os.Chown(path.Join(tempDirectory, "info.yml"), uid, gid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Get files to include in BPM archive // Get files to include in BPM archive
bpmArchiveFiles := make([]string, 0) bpmArchiveFiles := make([]string, 0)
bpmArchiveFiles = append(bpmArchiveFiles, "pkg.info", "pkg.files", "files.tar.gz") // Base files bpmArchiveFiles = append(bpmArchiveFiles, "info.yml", "files.txt", "files.tar.gz") // Base files
bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts bpmArchiveFiles = append(bpmArchiveFiles, packageScripts...) // Package scripts
// Create final BPM archive // Create final BPM archive
fmt.Println("Generating final BPM archive...") fmt.Println("Generating final BPM archive...")
cmd = exec.Command("bash", "-c", "tar -cf final-archive.bpm --owner=0 --group=0 -C \"$BPM_WORKDIR\" "+strings.Join(bpmArchiveFiles, " ")) cmd = exec.Command("tar", "cf", "final-archive.bpm",
"--sort=name",
"--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime",
"--mtime=UTC 1970-01-01",
"--owner=0", "--group=0", "--numeric-owner")
cmd.Args = append(cmd.Args, bpmArchiveFiles...)
cmd.Dir = tempDirectory cmd.Dir = tempDirectory
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -417,8 +464,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
return nil, fmt.Errorf("BPM archive could not be created: %s", err) return nil, fmt.Errorf("BPM archive could not be created: %s", err)
} }
// Remove pkg.info file // Remove info.yml file
err = os.Remove(path.Join(tempDirectory, "pkg.info")) err = os.Remove(path.Join(tempDirectory, "info.yml"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -725,8 +772,8 @@ func showPackageFiles(archiveFilename string) error {
return nil return nil
} }
// Print pkg.info content // Print info.yml content
err = printTarballContent("pkg.info") err = printTarballContent("info.yml")
if err != nil { if err != nil {
return err return err
} }
@@ -748,8 +795,8 @@ func showPackageFiles(archiveFilename string) error {
} }
} }
// Print source.sh content // Print recipe.sh content
err = printTarballContent("source.sh") err = printTarballContent("recipe.sh")
if err != nil { if err != nil {
return err return err
} }
+1
View File
@@ -8,6 +8,7 @@ import (
type MainBPMConfigStruct struct { type MainBPMConfigStruct struct {
IgnorePackages []string `yaml:"ignore_packages"` IgnorePackages []string `yaml:"ignore_packages"`
IgnorePaths []string `yaml:"ignore_paths"`
ShowSourcePackageContents string `yaml:"show_source_package_contents"` ShowSourcePackageContents string `yaml:"show_source_package_contents"`
CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"` CleanupMakeDependencies bool `yaml:"cleanup_make_dependencies"`
Databases []configDatabase `yaml:"databases"` Databases []configDatabase `yaml:"databases"`
+202 -32
View File
@@ -42,7 +42,7 @@ type BPMDatabaseEntry struct {
Database *BPMDatabase Database *BPMDatabase
} }
var BPMDatabases = make(map[string]*BPMDatabase) var BPMDatabases = make([]*BPMDatabase, 0)
func (db *BPMDatabase) ContainsPackage(pkg string) bool { func (db *BPMDatabase) ContainsPackage(pkg string) bool {
_, ok := db.Entries[pkg] _, ok := db.Entries[pkg]
@@ -135,7 +135,7 @@ func (db *configDatabase) ReadLocalDatabase() error {
} }
} }
BPMDatabases[db.Name] = database BPMDatabases = append(BPMDatabases, database)
return nil return nil
} }
@@ -207,33 +207,85 @@ func ReadLocalDatabaseFiles() (err error) {
return nil return nil
} }
func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) { func ResolveDatabaseEntry(d DeconstructedPackageString, rootDir string) *BPMDatabaseEntry {
split := strings.Split(str, "/") results := SearchDatabaseEntries(d.PkgName)
for _, result := range results {
if EvaluatePackageString(result.Info, d) {
return result
}
}
installedProviders := GetVirtualPackageInfo(d.PkgName, rootDir)
for _, provider := range installedProviders {
results := SearchDatabaseEntries(provider.Name)
for _, result := range results {
if EvaluatePackageString(result.Info, d) {
return result
}
}
}
databaseProviders := SearchDatabaseVirtualPackageProviders(d.PkgName)
for _, provider := range databaseProviders {
results := SearchDatabaseEntries(provider.Info.Name)
for _, result := range results {
if EvaluatePackageString(result.Info, d) {
return result
}
}
}
return nil
}
func SearchDatabaseEntries(pkg string) (results []*BPMDatabaseEntry) {
split := strings.Split(pkg, "/")
if len(split) == 1 { if len(split) == 1 {
pkgName := strings.TrimSpace(split[0]) pkgName := strings.TrimSpace(split[0])
if pkgName == "" { if pkgName == "" {
return nil, nil, errors.New("could not find database entry for this package") return results
} }
for _, db := range BPMDatabases { for _, db := range BPMDatabases {
if db.ContainsPackage(pkgName) { if db.ContainsPackage(pkgName) {
return db.Entries[pkgName], db, nil results = append(results, db.Entries[pkgName])
} }
} }
return nil, nil, errors.New("could not find database entry for this package") return results
} else if len(split) == 2 { } else if len(split) == 2 {
dbName := strings.TrimSpace(split[0]) dbName := strings.TrimSpace(split[0])
pkgName := strings.TrimSpace(split[1]) pkgName := strings.TrimSpace(split[1])
if dbName == "" || pkgName == "" { if dbName == "" || pkgName == "" {
return nil, nil, errors.New("could not find database entry for this package") return results
} }
db := BPMDatabases[dbName] dbIndex := slices.IndexFunc(BPMDatabases, func(db *BPMDatabase) bool {
if db == nil || !db.ContainsPackage(pkgName) { return db.Name == dbName
return nil, nil, errors.New("could not find database entry for this package") })
if dbIndex < 0 {
return results
} }
return db.Entries[pkgName], db, nil
} else { db := BPMDatabases[dbIndex]
return nil, nil, errors.New("could not find database entry for this package") if !db.ContainsPackage(pkgName) {
return results
} }
results = append(results, db.Entries[pkgName])
}
return results
}
func SearchDatabaseVirtualPackageProviders(vpkg string) (providers []*BPMDatabaseEntry) {
for _, db := range BPMDatabases {
providers = append(providers, db.VirtualPackages[vpkg]...)
}
slices.SortFunc(providers, func(a, b *BPMDatabaseEntry) int {
return strings.Compare(a.Info.Name, b.Info.Name)
})
return providers
} }
func FindReplacement(pkg string) *BPMDatabaseEntry { func FindReplacement(pkg string) *BPMDatabaseEntry {
@@ -250,18 +302,6 @@ func FindReplacement(pkg string) *BPMDatabaseEntry {
return nil return nil
} }
func GetDatabaseVirtualPackageEntry(vpkg string) (providers []*BPMDatabaseEntry) {
for _, db := range BPMDatabases {
providers = append(providers, db.VirtualPackages[vpkg]...)
}
slices.SortFunc(providers, func(a, b *BPMDatabaseEntry) int {
return strings.Compare(a.Info.Name, b.Info.Name)
})
return providers
}
func (db *BPMDatabase) FetchPackage(pkg string) (string, error) { func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
// Check if package exists in database // Check if package exists in database
if !db.ContainsPackage(pkg) { if !db.ContainsPackage(pkg) {
@@ -300,10 +340,68 @@ func (db *BPMDatabase) FetchPackage(pkg string) (string, error) {
func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) { func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
dependantsMap := make(map[string][]string) dependantsMap := make(map[string][]string)
// Loop through all entries
for _, db := range BPMDatabases { for _, db := range BPMDatabases {
for _, e := range db.Entries { for _, e := range db.Entries {
if slices.Contains(e.Info.Depends, entry.Info.Name) { // Skip iteration if comparing the same packages
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name) 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 {
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == 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 {
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == 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 {
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == 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 {
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name)
break
}
} }
} }
} }
@@ -332,7 +430,13 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
for _, db := range BPMDatabases { for _, db := range BPMDatabases {
for _, e := range db.Entries { for _, e := range db.Entries {
if slices.ContainsFunc(e.Info.OptionalDepends, func(n string) bool { if slices.ContainsFunc(e.Info.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == entry.Info.Name // Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == entry.Info.Name
}) { }) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name) dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
} }
@@ -362,7 +466,15 @@ func (entry *BPMDatabaseEntry) GetEntryMakeDependants() (dependants []string) {
dependantsMap := make(map[string][]string) dependantsMap := make(map[string][]string)
for _, db := range BPMDatabases { for _, db := range BPMDatabases {
for _, e := range db.Entries { for _, e := range db.Entries {
if slices.Contains(e.Info.MakeDepends, entry.Info.Name) { if slices.ContainsFunc(e.Info.MakeDepends, func(n string) bool {
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name) dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
} }
} }
@@ -422,7 +534,7 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
builder.WriteString(" - " + val) builder.WriteString(" - " + val)
// Show virtual package providers // Show virtual package providers
if providers := GetDatabaseVirtualPackageEntry(val); len(providers) > 0 { if providers := SearchDatabaseVirtualPackageProviders(val); len(providers) > 0 {
builder.WriteString(" (") builder.WriteString(" (")
for i, vpkg := range providers { for i, vpkg := range providers {
if i == len(providers)-1 { if i == len(providers)-1 {
@@ -452,6 +564,64 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
} }
builder.WriteString("Type: " + entry.Info.Type + "\n") builder.WriteString("Type: " + entry.Info.Type + "\n")
// Flags
if entry.Info.Type == "binary" {
var flags []string
for _, flag := range entry.Info.Flags {
flags = append(flags, fmt.Sprintf("%s=%s", flag.Name, flag.BuiltValue))
}
builderWriteArray("Built with flags:", flags, false)
} else {
if len(entry.Info.Flags) > 0 {
builder.WriteString("Available flags (")
builder.WriteString(strconv.Itoa(len(entry.Info.Flags)))
builder.WriteString("):\n")
for _, flag := range entry.Info.Flags {
builder.WriteString(" - Flag: ")
builder.WriteString(flag.Name)
builder.WriteRune('\n')
if flag.DefaultValue != "" {
builder.WriteString(" Default value: ")
builder.WriteString(flag.DefaultValue)
builder.WriteRune('\n')
}
if len(flag.AcceptedValues) > 0 {
builder.WriteString(" Accepted values (")
builder.WriteString(strconv.Itoa(len(flag.AcceptedValues)))
builder.WriteString("):\n")
for i, acceptedValue := range flag.AcceptedValues {
writeValueDepends := func(text string, depends []string) {
if len(depends) > 0 {
builder.WriteString(" ")
builder.WriteString(text)
builder.WriteString(" (")
builder.WriteString(strconv.Itoa(len(depends)))
builder.WriteString("): ")
for _, depend := range depends {
if i != 0 {
builder.WriteString(", ")
}
builder.WriteString(depend)
}
builder.WriteRune('\n')
}
}
builder.WriteString(" - Value: ")
builder.WriteString(acceptedValue.Value)
builder.WriteRune('\n')
writeValueDepends("Dependencies", acceptedValue.Depends)
writeValueDepends("Make Dependencies", acceptedValue.MakeDepends)
writeValueDepends("Check Dependencies", acceptedValue.CheckDepends)
writeValueDepends("Runtime Dependencies", acceptedValue.RuntimeDepends)
writeValueDepends("Optional Dependencies", acceptedValue.OptionalDepends)
}
}
}
}
}
// Dependencies // Dependencies
builderWriteDependencyArray("Dependencies", entry.Info.Depends) builderWriteDependencyArray("Dependencies", entry.Info.Depends)
if entry.Info.Type == "source" { if entry.Info.Type == "source" {
@@ -470,7 +640,7 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
} }
// Show virtual package providers // Show virtual package providers
if providers := GetDatabaseVirtualPackageEntry(dependSplit[0]); len(providers) > 0 { if providers := SearchDatabaseVirtualPackageProviders(dependSplit[0]); len(providers) > 0 {
builder.WriteString(" (") builder.WriteString(" (")
for i, vpkg := range providers { for i, vpkg := range providers {
if i == len(providers)-1 { if i == len(providers)-1 {
+299 -27
View File
@@ -1,11 +1,13 @@
package bpmlib package bpmlib
import ( import (
"fmt"
"maps"
"slices" "slices"
"strings" "strings"
) )
func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []string) { func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultipleProviders bool) (dependants []string) {
// Get installed package names // Get installed package names
pkgs, ok := localPackageInformation[rootDir] pkgs, ok := localPackageInformation[rootDir]
if !ok { if !ok {
@@ -21,7 +23,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its dependencies include pkgName // Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
return n == pkgInfo.Name // Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == pkgInfo.Name
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
continue continue
@@ -29,7 +37,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its runtime dependencies include pkgName // Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool { if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
return n == pkgInfo.Name // Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == pkgInfo.Name
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
continue continue
@@ -37,9 +51,19 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Loop through each virtual package // Loop through each virtual package
for _, vpkg := range pkgInfo.Provides { 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 // Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool { if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
return n == vpkg // Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
break break
@@ -47,7 +71,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string) (dependants []s
// Add installed package to list if its runtime dependencies contain a provided virtual package // Add installed package to list if its runtime dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool { if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
return n == vpkg // Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
break break
@@ -74,7 +104,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
// Add installed package to list if its optional dependencies include pkgName // Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool { if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == pkgInfo.Name // Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == pkgInfo.Name
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
continue continue
@@ -84,7 +120,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
for _, vpkg := range pkgInfo.Provides { for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its optional dependencies contain a provided virtual package // Add installed package to list if its optional dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool { if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
return strings.SplitN(n, ":", 2)[0] == vpkg // Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) { }) {
dependants = append(dependants, installedPkg.Name) dependants = append(dependants, installedPkg.Name)
break break
@@ -97,41 +139,85 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
type ResolvedPackage struct { type ResolvedPackage struct {
DatabaseEntry *BPMDatabaseEntry DatabaseEntry *BPMDatabaseEntry
Flags map[string]string
InstallationReason InstallationReason InstallationReason InstallationReason
} }
func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[string]string, includeRuntimeDepends bool, rootDir string) (resolved []ResolvedPackage, unresolved []string) { func ResolveDependencies(pkgInfo *PackageInfo, flags, resolvedVirtualPackages map[string]string, includeRuntimeDepends bool, rootDir string) (resolved []ResolvedPackage, unresolved map[string]string) {
unresolved = make(map[string]string)
visited := make([]string, 0) visited := make([]string, 0)
var dfs func(resolvedPkg *PackageInfo) var dfs func(resolvedPkg *PackageInfo, flags map[string]string)
dfs = func(pkgInfo *PackageInfo) { dfs = func(pkgInfo *PackageInfo, flags map[string]string) {
checkDependencies := func(dependencies []string, installationReason InstallationReason) { checkDependencies := func(dependencies []string, installationReason InstallationReason) {
for _, depend := range dependencies { for _, depend := range dependencies {
// Ignore if package is already installed // Deconstruct package string
if IsPackageInstalled(depend, rootDir) { d, err := DeconstructPackageString(depend)
continue if err != nil {
} else if providers := GetVirtualPackageInfo(depend, rootDir); len(providers) > 0 { unresolved[depend] = "could not deconstruct package string: " + err.Error()
continue continue
} }
// Check resolved virtual packages
if resolvedPkg, ok := resolvedVirtualPackages[d.PkgName]; ok {
d.PkgName = resolvedPkg
}
// Find database entry for dependency // Find database entry for dependency
var dependEntry *BPMDatabaseEntry dependEntry := ResolveDatabaseEntry(d, rootDir)
if resolvedVpkg, ok := resolvedVirtualPackages[depend]; ok {
dependEntry, _, _ = GetDatabaseEntry(resolvedVpkg)
} else if entry, _, _ := GetDatabaseEntry(depend); entry != nil {
dependEntry = entry
} else if providers := GetDatabaseVirtualPackageEntry(depend); len(providers) > 0 {
dependEntry = providers[0]
}
if dependEntry == nil { if dependEntry == nil {
unresolved = append(unresolved, depend) unresolved[depend] = "could not find in any database"
continue
}
d.PkgName = dependEntry.Info.Name
// Skip ignored packages in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue continue
} }
// Add user defined package flags and resolve entry again if package is already installed
if installedInfo := GetPackageInfo(dependEntry.Info.Name, rootDir); installedInfo != nil {
d.Flags, err = CombineFlags(getPackageLocalInfo(dependEntry.Info.Name, rootDir).Flags, d.Flags)
if err != nil {
unresolved[depend] = "could not combine flags: " + err.Error()
continue
}
dependEntry = ResolveDatabaseEntry(d, rootDir)
if dependEntry == nil {
unresolved[depend] = "could not find in any database"
continue
}
shouldIgnore := true
// Skip if no update/downgrade is available
if installedInfo.GetFullVersion() != dependEntry.Info.GetFullVersion() {
shouldIgnore = false
}
// Skip if no new package flags
if !maps.Equal(getPackageLocalInfo(dependEntry.Info.Name, rootDir).Flags, d.Flags) {
shouldIgnore = false
}
if shouldIgnore {
continue
}
}
// Resolve virtual packages
for _, vpkg := range dependEntry.Info.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = dependEntry.Info.Name
}
}
// Resolve entry dependencies
if !slices.Contains(visited, dependEntry.Info.Name) { if !slices.Contains(visited, dependEntry.Info.Name) {
dfs(dependEntry.Info) dfs(dependEntry.Info, d.Flags)
resolved = append(resolved, ResolvedPackage{DatabaseEntry: dependEntry, InstallationReason: installationReason}) resolved = append(resolved, ResolvedPackage{DatabaseEntry: dependEntry, Flags: d.Flags, InstallationReason: installationReason})
} }
} }
} }
@@ -145,10 +231,196 @@ func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[strin
if pkgInfo.Type == "source" { if pkgInfo.Type == "source" {
checkDependencies(pkgInfo.MakeDepends, InstallationReasonMakeDependency) checkDependencies(pkgInfo.MakeDepends, InstallationReasonMakeDependency)
checkDependencies(pkgInfo.CheckDepends, InstallationReasonMakeDependency) checkDependencies(pkgInfo.CheckDepends, InstallationReasonMakeDependency)
// Resolve flag-specific dependencies
for _, flag := range pkgInfo.Flags {
acceptedValueIndex := slices.IndexFunc(flag.AcceptedValues, func(acceptedValue PackageAcceptedValue) bool {
if value, ok := flags[flag.Name]; ok {
return acceptedValue.Value == value
} else {
return acceptedValue.Value == flag.DefaultValue
}
})
if acceptedValueIndex < 0 {
continue
}
acceptedValue := flag.AcceptedValues[acceptedValueIndex]
checkDependencies(acceptedValue.Depends, InstallationReasonDependency)
checkDependencies(acceptedValue.RuntimeDepends, InstallationReasonDependency)
checkDependencies(acceptedValue.MakeDepends, InstallationReasonMakeDependency)
checkDependencies(acceptedValue.CheckDepends, InstallationReasonMakeDependency)
}
} }
} }
dfs(pkgInfo) dfs(pkgInfo, flags)
return resolved, unresolved return resolved, unresolved
} }
type DeconstructedPackageString struct {
PkgName string
Description string
Flags map[string]string
RequiredVersionOperator string
RequiredVersion string
}
func DeconstructPackageString(pkg string) (ret DeconstructedPackageString, err error) {
// Initialize values
ret.Flags = make(map[string]string)
// Get dependency description
if i := strings.IndexRune(pkg, ':'); i > 0 {
ret.Description = pkg[i+1:]
pkg = pkg[0:i]
}
if left := strings.IndexRune(pkg, '['); left > 0 {
if right := strings.IndexRune(pkg, ']'); right > left {
flagsStr := pkg[left+1 : right]
pkg = pkg[0:left] + pkg[right+1:]
if flagsStr != "" {
for flag := range strings.SplitSeq(flagsStr, ",") {
flagSplit := strings.SplitN(flag, "=", 2)
if len(flagSplit) != 2 {
return ret, fmt.Errorf("could not parse flag: %s", flag)
}
ret.Flags[flagSplit[0]] = flagSplit[1]
}
}
} else {
return ret, fmt.Errorf("could not find closing square bracket for set flags")
}
}
if strings.Contains(pkg, ">=") {
ret.RequiredVersionOperator = ">="
} else if strings.Contains(pkg, ">") {
ret.RequiredVersionOperator = ">"
} else if strings.Contains(pkg, "<=") {
ret.RequiredVersionOperator = "<="
} else if strings.Contains(pkg, "<") {
ret.RequiredVersionOperator = "<"
} else if strings.Contains(pkg, "=") {
ret.RequiredVersionOperator = "="
}
if ret.RequiredVersionOperator != "" {
pkgSplit := strings.SplitN(pkg, ret.RequiredVersionOperator, 2)
if len(pkgSplit) != 2 {
return ret, fmt.Errorf("could not parse required version: %s", pkg)
}
pkg = pkgSplit[0]
ret.RequiredVersion = pkgSplit[1]
}
ret.PkgName = pkg
return ret, nil
}
func EvaluatePackageString(pkgInfo *PackageInfo, match DeconstructedPackageString) bool {
// Validate flags
for flag, value := range match.Flags {
pkgFlagIndex := slices.IndexFunc(pkgInfo.Flags, func(f PackageFlag) bool {
return f.Name == flag
})
if pkgFlagIndex < 0 {
return false
}
pkgFlag := pkgInfo.Flags[pkgFlagIndex]
if pkgInfo.Type == "binary" {
if pkgFlag.BuiltValue != value {
return false
}
}
if len(pkgFlag.AcceptedValues) == 0 {
continue
}
if !slices.ContainsFunc(pkgFlag.AcceptedValues, func(acceptedFlag PackageAcceptedValue) bool {
return acceptedFlag.Value == value
}) {
return false
}
}
// Validate version
switch match.RequiredVersionOperator {
case ">=":
return CompareVersions(match.RequiredVersion, pkgInfo.Version) >= 0
case ">":
return CompareVersions(match.RequiredVersion, pkgInfo.Version) > 0
case "<=":
return CompareVersions(match.RequiredVersion, pkgInfo.Version) <= 0
case "<":
return CompareVersions(match.RequiredVersion, pkgInfo.Version) < 0
case "=":
if cutPkgVersion, ok := strings.CutSuffix(match.RequiredVersion, "*"); ok {
return strings.HasPrefix(pkgInfo.Version, cutPkgVersion)
} else {
return CompareVersions(pkgInfo.Version, match.RequiredVersion) == 0
}
default:
return true
}
}
func GetBuiltFlags(pkg, rootDir string) map[string]string {
builtFlags := make(map[string]string)
pkgInfo := GetPackageInfo(pkg, rootDir)
if pkgInfo == nil {
return nil
}
for _, flag := range pkgInfo.Flags {
builtFlags[flag.Name] = flag.BuiltValue
}
return builtFlags
}
func RemoveInvalidFlags(pkgInfo *PackageInfo, userFlags map[string]string) (ret map[string]string) {
ret = make(map[string]string)
for userFlag, userValue := range userFlags {
flagIndex := slices.IndexFunc(pkgInfo.Flags, func(flag PackageFlag) bool {
return flag.Name == userFlag
})
if flagIndex < 0 {
continue
}
flag := pkgInfo.Flags[flagIndex]
if len(flag.AcceptedValues) > 0 && !slices.ContainsFunc(flag.AcceptedValues, func(acceptedValue PackageAcceptedValue) bool {
return acceptedValue.Value == userValue
}) {
continue
}
ret[userFlag] = userValue
}
return ret
}
func CombineFlags(flags, newFlags map[string]string) (map[string]string, error) {
ret := make(map[string]string)
maps.Copy(ret, flags)
for flag, value := range newFlags {
if oldValue, ok := flags[flag]; ok && value != oldValue {
return ret, fmt.Errorf("flag already defined: %s=%s/%s", flag, oldValue, value)
}
ret[flag] = value
}
return ret, nil
}
+13 -10
View File
@@ -2,23 +2,25 @@ package bpmlib
import ( import (
"fmt" "fmt"
"maps"
"slices"
"strings" "strings"
) )
type PackageNotFoundErr struct { type PackageNotResolvedErr struct {
packages []string packages map[string]string
} }
func (e PackageNotFoundErr) Error() string { func (e PackageNotResolvedErr) Error() (ret string) {
return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ") ret = "The following packages could not be resolved:"
keys := slices.Collect(maps.Keys(e.packages))
slices.Sort(keys)
for _, key := range keys {
ret += "\n " + key + ": " + e.packages[key]
} }
type DependencyNotFoundErr struct { return ret
dependencies []string
}
func (e DependencyNotFoundErr) Error() string {
return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ")
} }
type PackageConflictErr struct { type PackageConflictErr struct {
@@ -27,6 +29,7 @@ type PackageConflictErr struct {
} }
func (e PackageConflictErr) Error() string { 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, ", ")) return fmt.Sprintf("Package (%s) is in conflict with the following packages: %s", e.pkg, strings.Join(e.conflicts, ", "))
} }
+166 -46
View File
@@ -16,8 +16,8 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
// Setup operation struct // Setup operation struct
operation = &BPMOperation{ operation = &BPMOperation{
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make(map[string]string, 0),
Changes: make(map[string]string), ModifiedFiles: make(map[string]string),
RunChecks: runChecks, RunChecks: runChecks,
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
@@ -27,20 +27,58 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
packages = removeDuplicates(packages) packages = removeDuplicates(packages)
// Resolve packages // Resolve packages
pkgsNotFound := make([]string, 0) unresolvedPackages := make(map[string]string)
resolvedVirtualPackages := make(map[string]string)
for _, pkg := range packages { for _, pkg := range packages {
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() { d, err := DeconstructPackageString(pkg)
bpmpkg, err := ReadPackage(pkg) if err != nil {
return nil, fmt.Errorf("could not deconstruct package string: %s", err)
}
if stat, err := os.Stat(d.PkgName); err == nil && !stat.IsDir() {
bpmpkg, err := ReadPackage(d.PkgName)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read package: %s", err) return nil, fmt.Errorf("could not read package: %s", err)
} }
if bpmpkg.PkgInfo.Type == "source" && bpmpkg.PkgInfo.IsSplitPackage() { // Add user defined package flags
if !reinstallPackages && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
d.Flags, err = CombineFlags(getPackageLocalInfo(bpmpkg.PkgInfo.Name, rootDir).Flags, d.Flags)
if err != nil {
return nil, fmt.Errorf("could not combine flags for package (%s): %s", bpmpkg.PkgInfo.Name, err)
}
}
if !EvaluatePackageString(bpmpkg.PkgInfo, d) {
unresolvedPackages[pkg] = "could not evaluate package"
continue
}
if bpmpkg.PkgInfo.IsSplitPackage() {
for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages { for _, splitPkg := range bpmpkg.PkgInfo.SplitPackages {
if !reinstallPackages && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() { if !reinstallPackages && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() {
continue continue
} }
// Check if installation can be ignored
if !reinstallPackages && IsPackageInstalled(splitPkg.Name, rootDir) {
shouldIgnore := true
// Skip if no update/downgrade is available
if GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() != splitPkg.GetFullVersion() {
shouldIgnore = false
}
// Skip if no new package flags
if !maps.Equal(getPackageLocalInfo(splitPkg.Name, rootDir).Flags, d.Flags) {
shouldIgnore = false
}
if shouldIgnore {
continue
}
}
// Set package installation reason // Set package installation reason
installationReason := forceInstallationReason installationReason := forceInstallationReason
if installationReason == InstallationReasonUnknown { if installationReason == InstallationReasonUnknown {
@@ -51,9 +89,17 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
} }
} }
operation.AppendAction(&InstallPackageAction{ // Resolve virtual packages
for _, vpkg := range splitPkg.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = splitPkg.Name
}
}
operation.Actions = append(operation.Actions, &InstallPackageAction{
File: pkg, File: pkg,
InstallationReason: installationReason, InstallationReason: installationReason,
Flags: d.Flags,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
SplitPackageToInstall: splitPkg.Name, SplitPackageToInstall: splitPkg.Name,
}) })
@@ -61,9 +107,24 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
continue continue
} }
if !reinstallPackages && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() { // Check if installation can be ignored
if !reinstallPackages && IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) {
shouldIgnore := true
// Skip if no update/downgrade is available
if GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() != bpmpkg.PkgInfo.GetFullVersion() {
shouldIgnore = false
}
// Skip if no new package flags
if !maps.Equal(getPackageLocalInfo(bpmpkg.PkgInfo.Name, rootDir).Flags, d.Flags) {
shouldIgnore = false
}
if shouldIgnore {
continue continue
} }
}
// Set package installation reason // Set package installation reason
installationReason := forceInstallationReason installationReason := forceInstallationReason
@@ -75,30 +136,64 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
} }
} }
operation.AppendAction(&InstallPackageAction{ // Resolve virtual packages
for _, vpkg := range bpmpkg.PkgInfo.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = bpmpkg.PkgInfo.Name
}
}
operation.Actions = append(operation.Actions, &InstallPackageAction{
File: pkg, File: pkg,
InstallationReason: installationReason, InstallationReason: installationReason,
Flags: d.Flags,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
}) })
} else { } else {
var entry *BPMDatabaseEntry // Check resolved virtual packages
if resolvedPkg, ok := resolvedVirtualPackages[d.PkgName]; ok {
d.PkgName = resolvedPkg
}
if e, _, err := GetDatabaseEntry(pkg); err == nil { entry := ResolveDatabaseEntry(d, rootDir)
entry = e if entry == nil {
} else if providers := GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 { unresolvedPackages[pkg] = "could not find in any database"
entry, _, err = GetDatabaseEntry(providers[0].Name) continue
}
pkgInfo := GetPackageInfo(entry.Info.Name, rootDir)
// Add user defined package flags and resolve entry again if package is already installed
if !reinstallPackages && pkgInfo != nil {
d.Flags, err = CombineFlags(getPackageLocalInfo(entry.Info.Name, rootDir).Flags, d.Flags)
if err != nil { if err != nil {
pkgsNotFound = append(pkgsNotFound, pkg) return nil, fmt.Errorf("could not combine flags for package (%s): %s", entry.Info.Name, err)
}
entry = ResolveDatabaseEntry(d, rootDir)
if entry == nil {
unresolvedPackages[pkg] = "could not find in any database"
continue continue
} }
} else if providers := GetDatabaseVirtualPackageEntry(pkg); len(providers) > 0 { }
entry = providers[0]
} else { // Check if installation can be ignored
pkgsNotFound = append(pkgsNotFound, pkg) if !reinstallPackages && pkgInfo != nil {
shouldIgnore := true
// Skip if no update/downgrade is available
if GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() != entry.Info.GetFullVersion() {
shouldIgnore = false
}
// Skip if no new package flags
if !maps.Equal(getPackageLocalInfo(entry.Info.Name, rootDir).Flags, d.Flags) {
shouldIgnore = false
}
if shouldIgnore {
continue continue
} }
if !reinstallPackages && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
continue
} }
// Set package installation reason // Set package installation reason
@@ -111,25 +206,33 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
} }
} }
operation.AppendAction(&FetchPackageAction{ // Resolve virtual packages
for _, vpkg := range entry.Info.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = entry.Info.Name
}
}
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: installationReason, InstallationReason: installationReason,
Flags: d.Flags,
DatabaseEntry: entry, DatabaseEntry: entry,
}) })
} }
} }
// Return error if not all packages are found // Return error if not all packages are found
if len(pkgsNotFound) != 0 { if len(unresolvedPackages) != 0 {
return nil, PackageNotFoundErr{pkgsNotFound} return nil, PackageNotResolvedErr{unresolvedPackages}
} }
// Resolve dependencies // Resolve dependencies
operation.ResolveDependencies(installRuntimeDependencies) operation.ResolveDependencies(installRuntimeDependencies)
if len(operation.UnresolvedDepends) != 0 { if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation { if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends} return nil, PackageNotResolvedErr{operation.UnresolvedDepends}
} else if verbose { } else if verbose {
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends}) log.Printf("Warning: %s", PackageNotResolvedErr{operation.UnresolvedDepends})
} }
} }
@@ -179,8 +282,8 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ...string) (operation *BPMOperation, err error) { func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ...string) (operation *BPMOperation, err error) {
operation = &BPMOperation{ operation = &BPMOperation{
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make(map[string]string),
Changes: make(map[string]string), ModifiedFiles: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
@@ -197,7 +300,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
if bpmpkg == nil { if bpmpkg == nil {
continue continue
} }
operation.AppendAction(&RemovePackageAction{BpmPackage: bpmpkg}) operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
} }
// Do package cleanup // Do package cleanup
@@ -213,12 +316,12 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Get packages and their dependants // Get packages and their dependants
packageDepndants := make(map[string][]string, 0) packageDepndants := make(map[string][]string, 0)
for _, action := range operation.Actions { for _, action := range operation.Actions {
// Skip package if ignored // Skip package if ignored in config
if slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) { if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, action.(*RemovePackageAction).BpmPackage.PkgInfo.Name) {
continue continue
} }
dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir) dependants := action.(*RemovePackageAction).BpmPackage.PkgInfo.GetPackageDependants(rootDir, true)
packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants packageDepndants[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = dependants
} }
@@ -234,7 +337,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
// Remove dependant packages if ignored // Remove dependant packages if ignored
for pkg, required := range packageDepndants { for pkg, required := range packageDepndants {
required = slices.DeleteFunc(required, func(pkgName string) bool { required = slices.DeleteFunc(required, func(pkgName string) bool {
return slices.Contains(MainBPMConfig.IgnorePackages, pkgName) return rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkgName)
}) })
packageDepndants[pkg] = required packageDepndants[pkg] = required
} }
@@ -257,8 +360,8 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOperation, err error) { func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOperation, err error) {
operation = &BPMOperation{ operation = &BPMOperation{
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make(map[string]string),
Changes: make(map[string]string), ModifiedFiles: make(map[string]string),
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
@@ -385,47 +488,64 @@ func UpdatePackages(rootDir string, syncDatabase, allowDowngrades, forceInstalla
operation = &BPMOperation{ operation = &BPMOperation{
Actions: make([]OperationAction, 0), Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0), UnresolvedDepends: make(map[string]string),
Changes: make(map[string]string), ModifiedFiles: make(map[string]string),
RunChecks: runChecks, RunChecks: runChecks,
RootDir: rootDir, RootDir: rootDir,
compiledPackages: make(map[string]string), compiledPackages: make(map[string]string),
} }
// Search for packages // Search for packages
resolvedVirtualPackages := make(map[string]string)
for _, pkg := range pkgs { for _, pkg := range pkgs {
if slices.Contains(MainBPMConfig.IgnorePackages, pkg) { if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
continue continue
} }
// Get installed package built flags
flags := getPackageLocalInfo(pkg, rootDir).Flags
var entry *BPMDatabaseEntry var entry *BPMDatabaseEntry
// Check if installed package can be replaced and install that instead // Check if installed package can be replaced and install that instead
if e := FindReplacement(pkg); e != nil { if e := FindReplacement(pkg); e != nil {
entry = e entry = e
} else if entry, _, err = GetDatabaseEntry(pkg); err != nil { } else if entry = ResolveDatabaseEntry(DeconstructedPackageString{PkgName: pkg, Flags: flags}, rootDir); entry == nil {
continue continue
} }
installedInfo := GetPackageInfo(pkg, rootDir) // Remove invalid flags
if installedInfo == nil { flags = RemoveInvalidFlags(entry.Info, flags)
if installedInfo := GetPackageInfo(pkg, rootDir); installedInfo == nil {
return nil, fmt.Errorf("could not get package info for package (%s)", pkg) return nil, fmt.Errorf("could not get package info for package (%s)", pkg)
} else { } else {
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion()) comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) { if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
operation.AppendAction(&FetchPackageAction{ // Resolve virtual packages
InstallationReason: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(), for _, vpkg := range entry.Info.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok {
resolvedVirtualPackages[vpkg] = entry.Info.Name
}
}
bpmpkg := GetPackage(pkg, rootDir)
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: bpmpkg.LocalInfo.GetInstallationReason(),
Flags: flags,
DatabaseEntry: entry, DatabaseEntry: entry,
}) })
} }
} }
} }
// Check for new dependencies in updated packages // Resolve dependencies
operation.ResolveDependencies(true) operation.ResolveDependencies(true)
if len(operation.UnresolvedDepends) != 0 { if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation { if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends} return nil, PackageNotResolvedErr{operation.UnresolvedDepends}
} else if verbose { } else if verbose {
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends}) log.Printf("Warning: %s", PackageNotResolvedErr{operation.UnresolvedDepends})
} }
} }
+40 -61
View File
@@ -1,26 +1,27 @@
package bpmlib package bpmlib
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"gopkg.in/yaml.v3"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"slices" "slices"
"strings" "strings"
"syscall" "syscall"
"gopkg.in/yaml.v3"
) )
type BPMHook struct { type BPMHook struct {
SourcePath string SourcePath string
SourceContent string SourceContent string
TriggerOperations []string `yaml:"trigger_operations"` TriggerActions []string `yaml:"trigger_actions"`
TargetType string `yaml:"target_type"` TriggerPreOperation bool `yaml:"trigger_pre_operation"`
Targets []string `yaml:"targets"` Targets []string `yaml:"targets"`
Depends []string `yaml:"depends"`
Run string `yaml:"run"` Run string `yaml:"run"`
PassTargets bool `yaml:"pass_targets"`
} }
// createHook returns a BPMHook instance based on the content of the given string // createHook returns a BPMHook instance based on the content of the given string
@@ -35,10 +36,8 @@ func createHook(sourcePath string) (*BPMHook, error) {
hook := &BPMHook{ hook := &BPMHook{
SourcePath: sourcePath, SourcePath: sourcePath,
SourceContent: string(bytes), SourceContent: string(bytes),
TriggerOperations: nil, TriggerActions: nil,
TargetType: "",
Targets: nil, Targets: nil,
Depends: nil,
Run: "", Run: "",
} }
@@ -61,19 +60,15 @@ func (hook *BPMHook) IsValid() error {
ValidOperations := []string{"install", "upgrade", "remove"} ValidOperations := []string{"install", "upgrade", "remove"}
// Return error if any trigger operation is not valid or none are given // 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") return errors.New("no trigger operations specified")
} }
for _, operation := range hook.TriggerOperations { for _, operation := range hook.TriggerActions {
if !slices.Contains(ValidOperations, operation) { if !slices.Contains(ValidOperations, operation) {
return errors.New("trigger operation '" + operation + "' is not valid") 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 { if len(hook.Run) == 0 {
return errors.New("command to run is empty") return errors.New("command to run is empty")
} }
@@ -83,55 +78,30 @@ func (hook *BPMHook) IsValid() error {
} }
// Execute hook if all conditions are met // Execute hook if all conditions are met
func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, rootDir string) error { func (hook *BPMHook) Execute(modifiedFiles map[string]string, preOperation bool, 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...)
}
}
// Check if any targets are met // Check if any targets are met
targetMet := false targetsMet := make([]string, 0)
for _, target := range hook.Targets { for _, target := range hook.Targets {
if targetMet { for modifiedFile, action := range modifiedFiles {
break // Check if this hook is triggered by this file's action
if !slices.Contains(hook.TriggerActions, action) {
continue
} }
if hook.TargetType == "package" {
for change, operation := range packageChanges { // Check if file has already been checked
if target == change && slices.Contains(hook.TriggerOperations, operation) { if slices.Contains(targetsMet, modifiedFile) {
targetMet = true continue
break }
if matched, _ := filepath.Match(target, modifiedFile); !matched {
continue
}
targetsMet = append(targetsMet, modifiedFile)
} }
} }
} else {
glob, err := filepath.Glob(path.Join(rootDir, target)) if len(targetsMet) == 0 {
if err != nil {
return err
}
for _, change := range modifiedFiles {
if slices.Contains(glob, path.Join(rootDir, change.Path)) {
targetMet = true
break
}
}
}
}
if !targetMet {
return nil return nil
} }
@@ -140,16 +110,25 @@ func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, roo
cmd := exec.Command(splitCommand[0], splitCommand[1:]...) cmd := exec.Command(splitCommand[0], splitCommand[1:]...)
// Setup subprocess environment // Setup subprocess environment
cmd.Dir = "/" 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 // Run hook in chroot if using the -R flag
if rootDir != "/" { if rootDir != "/" {
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir} cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir}
} }
if verbose { if !verbose {
fmt.Printf("Running hook (%s) with run command: %s\n", hook.SourcePath, strings.Join(splitCommand, " ")) 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 { if err != nil {
return err return err
} }
+37 -10
View File
@@ -63,7 +63,7 @@ func InitializeLocalPackageInformation(rootDir string) (err error) {
} }
// Read package info // Read package info
infoData, err := os.ReadFile(path.Join(installedDir, item.Name(), "info")) infoData, err := os.ReadFile(path.Join(installedDir, item.Name(), "info.yml"))
if err != nil { if err != nil {
return err return err
} }
@@ -220,7 +220,7 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
var pkgFiles []*PackageFileEntry var pkgFiles []*PackageFileEntry
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg) pkgDir := path.Join(installedDir, pkg)
files := path.Join(pkgDir, "files") files := path.Join(pkgDir, "files.txt")
if _, err := os.Stat(installedDir); os.IsNotExist(err) { if _, err := os.Stat(installedDir); os.IsNotExist(err) {
return nil return nil
} }
@@ -280,7 +280,7 @@ func getPackageLocalInfo(pkg, rootDir string) PackageLocalInfo {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg) pkgDir := path.Join(installedDir, pkg)
localInfoFile := path.Join(path.Join(pkgDir, "local")) localInfoFile := path.Join(path.Join(pkgDir, "local.yml"))
if _, err := os.Stat(localInfoFile); os.IsNotExist(err) { if _, err := os.Stat(localInfoFile); os.IsNotExist(err) {
return localInfo return localInfo
@@ -304,7 +304,7 @@ func SetPackageLocalInfo(pkg string, localInfo PackageLocalInfo, rootDir string)
installedDir := path.Join(rootDir, "var/lib/bpm/installed/") installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg) pkgDir := path.Join(installedDir, pkg)
localFile, err := os.OpenFile(path.Join(pkgDir, "local"), os.O_WRONLY|os.O_CREATE, 0644) localFile, err := os.OpenFile(path.Join(pkgDir, "local.yml"), os.O_WRONLY|os.O_CREATE, 0644)
if err != nil { if err != nil {
return err return err
} }
@@ -332,10 +332,35 @@ func UpgradePersistentData(rootDir string) error {
for _, entry := range dirEntries { for _, entry := range dirEntries {
pkgDir := path.Join(persistentDataDir, "installed", entry.Name()) pkgDir := path.Join(persistentDataDir, "installed", entry.Name())
// Generate default local package information file // Rename 'info' file to 'info.yml'
if _, err := os.Stat(path.Join(pkgDir, "local")); err != nil && !os.IsNotExist(err) { if _, err := os.Stat(path.Join(pkgDir, "info")); err == nil {
fmt.Printf("Moving 'info' to 'info.yml' for package (%s)\n", entry.Name())
err := os.Rename(path.Join(pkgDir, "info"), path.Join(pkgDir, "info.yml"))
if err != nil {
return err return err
} else if os.IsNotExist(err) { }
}
// Rename 'files' file to 'files.txt'
if _, err := os.Stat(path.Join(pkgDir, "files")); err == nil {
fmt.Printf("Moving 'files' to 'files.txt' for package (%s)\n", entry.Name())
err := os.Rename(path.Join(pkgDir, "files"), path.Join(pkgDir, "files.txt"))
if err != nil {
return err
}
}
// Rename 'local' file to 'local.yml'
if _, err := os.Stat(path.Join(pkgDir, "local")); err == nil {
fmt.Printf("Moving 'local' to 'local.yml' for package (%s)\n", entry.Name())
err := os.Rename(path.Join(pkgDir, "local"), path.Join(pkgDir, "local.yml"))
if err != nil {
return err
}
}
// Generate default local package information file
if _, err := os.Stat(path.Join(pkgDir, "local.yml")); os.IsNotExist(err) {
fmt.Printf("Generating local package information for package (%s)\n", entry.Name()) fmt.Printf("Generating local package information for package (%s)\n", entry.Name())
out, err := yaml.Marshal(PackageLocalInfo{ out, err := yaml.Marshal(PackageLocalInfo{
@@ -347,10 +372,12 @@ func UpgradePersistentData(rootDir string) error {
return err return err
} }
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644) err = os.WriteFile(path.Join(pkgDir, "local.yml"), out, 0644)
if err != nil { if err != nil {
return err return err
} }
} else if err != nil {
return err
} }
// Move installation reason to local package information file // Move installation reason to local package information file
@@ -359,7 +386,7 @@ func UpgradePersistentData(rootDir string) error {
} else if err == nil { } else if err == nil {
fmt.Printf("Moving installation reason to local package information for package (%s)\n", entry.Name()) fmt.Printf("Moving installation reason to local package information for package (%s)\n", entry.Name())
data, err := os.ReadFile(path.Join(pkgDir, "local")) data, err := os.ReadFile(path.Join(pkgDir, "local.yml"))
if err != nil { if err != nil {
return err return err
} }
@@ -377,7 +404,7 @@ func UpgradePersistentData(rootDir string) error {
return err return err
} }
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644) err = os.WriteFile(path.Join(pkgDir, "local.yml"), out, 0644)
if err != nil { if err != nil {
return err return err
} }
+214 -124
View File
@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"maps"
"os" "os"
"path" "path"
"slices" "slices"
@@ -13,8 +14,8 @@ import (
type BPMOperation struct { type BPMOperation struct {
Actions []OperationAction Actions []OperationAction
UnresolvedDepends []string UnresolvedDepends map[string]string
Changes map[string]string ModifiedFiles map[string]string
CompilationJobs int CompilationJobs int
RunChecks bool RunChecks bool
RootDir string RootDir string
@@ -23,72 +24,6 @@ type BPMOperation struct {
hasFetchedPackages bool 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 { func (operation *BPMOperation) GetTotalDownloadSize() int64 {
var ret int64 = 0 var ret int64 = 0
for _, action := range operation.Actions { for _, action := range operation.Actions {
@@ -154,31 +89,35 @@ func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends bool) {
} }
// Discover all dependencies // Discover all dependencies
pos := 0 newActions := make([]OperationAction, 0)
for _, value := range slices.Clone(operation.Actions) { for _, value := range operation.Actions {
var pkgInfo *PackageInfo var pkgInfo *PackageInfo
var flags map[string]string
if value.GetActionType() == "install" { if value.GetActionType() == "install" {
action := value.(*InstallPackageAction) action := value.(*InstallPackageAction)
pkgInfo = action.BpmPackage.PkgInfo pkgInfo = action.BpmPackage.PkgInfo
flags = action.Flags
} else if value.GetActionType() == "fetch" { } else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction) action := value.(*FetchPackageAction)
pkgInfo = action.DatabaseEntry.Info pkgInfo = action.DatabaseEntry.Info
flags = action.Flags
} else { } else {
continue continue
} }
resolved, unresolved := ResolveDependencies(pkgInfo, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir) resolved, unresolved := ResolveDependencies(pkgInfo, flags, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir)
// Append unresolved dependencies // Copy unresolved dependencies
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...) maps.Copy(operation.UnresolvedDepends, unresolved)
operation.UnresolvedDepends = removeDuplicates(operation.UnresolvedDepends)
for _, resolvedPkg := range resolved { for _, resolvedPkg := range resolved {
if !operation.ActionsContainPackage(resolvedPkg.DatabaseEntry.Info.Name) && resolvedPkg.DatabaseEntry.Info.Name != pkgInfo.Name { if ActionSliceIndex(newActions, resolvedPkg.DatabaseEntry.Info.Name) == -1 { // Dependency not in actions slice
operation.InsertActionAt(pos, &FetchPackageAction{ var action OperationAction = &FetchPackageAction{
InstallationReason: resolvedPkg.InstallationReason, InstallationReason: resolvedPkg.InstallationReason,
Flags: resolvedPkg.Flags,
DatabaseEntry: resolvedPkg.DatabaseEntry, DatabaseEntry: resolvedPkg.DatabaseEntry,
}) }
newActions = append(newActions, action)
for _, vpkg := range resolvedPkg.DatabaseEntry.Info.Provides { for _, vpkg := range resolvedPkg.DatabaseEntry.Info.Provides {
if _, ok := resolvedVirtualPackages[vpkg]; !ok { if _, ok := resolvedVirtualPackages[vpkg]; !ok {
@@ -186,36 +125,19 @@ func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends bool) {
} }
} }
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++ if ActionSliceIndex(newActions, pkgInfo.Name) == -1 {
newActions = append(newActions, value)
} }
} }
func (operation *BPMOperation) RemoveNeededPackages() error { operation.Actions = newActions
removeActions := make(map[string]*RemovePackageAction)
for _, action := range slices.Clone(operation.Actions) {
if action.GetActionType() == "remove" {
removeActions[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = action.(*RemovePackageAction)
}
}
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
} }
func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error { func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
@@ -274,12 +196,18 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
// Loop through all dependencies // Loop through all dependencies
for _, depend := range depends { for _, depend := range depends {
// Deconstruct package string
d, err := DeconstructPackageString(depend)
if err != nil {
return fmt.Errorf("could not deconstruct package string: %s", err)
}
// Resolve dependency // Resolve dependency
var dependPkgInfo *PackageInfo var dependPkgInfo *PackageInfo
if providers := GetVirtualPackageInfo(depend, operation.RootDir); len(providers) > 0 { if providers := GetVirtualPackageInfo(d.PkgName, operation.RootDir); len(providers) > 0 {
dependPkgInfo = providers[0] dependPkgInfo = providers[0]
} else { } else {
dependPkgInfo = GetPackageInfo(depend, operation.RootDir) dependPkgInfo = GetPackageInfo(d.PkgName, operation.RootDir)
} }
if dependPkgInfo == nil { if dependPkgInfo == nil {
continue continue
@@ -304,7 +232,7 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
if !slices.Contains(visited, pkg) { if !slices.Contains(visited, pkg) {
bpmpkg := GetPackage(pkg, operation.RootDir) bpmpkg := GetPackage(pkg, operation.RootDir)
if bpmpkg == nil { if bpmpkg == nil {
return errors.New("Error: could not find installed package (" + pkg + ")") return fmt.Errorf("could not find installed package (%s)", pkg)
} }
operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg}) operation.Actions = append(operation.Actions, &RemovePackageAction{BpmPackage: bpmpkg})
} }
@@ -328,10 +256,11 @@ func (operation *BPMOperation) ReplaceObsoletePackages() {
} }
for _, r := range pkgInfo.Replaces { for _, r := range pkgInfo.Replaces {
if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && !operation.ActionsContainPackage(bpmpkg.PkgInfo.Name) { if bpmpkg := GetPackage(r, operation.RootDir); bpmpkg != nil && ActionSliceIndex(operation.Actions, bpmpkg.PkgInfo.Name) == -1 {
operation.InsertActionAt(0, &RemovePackageAction{ var action OperationAction = &RemovePackageAction{
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
}) }
operation.Actions = slices.Insert(operation.Actions, 0, action)
} }
} }
} }
@@ -436,15 +365,30 @@ func (operation *BPMOperation) ShowOperationSummary() {
for _, value := range operation.Actions { for _, value := range operation.Actions {
var pkgInfo *PackageInfo var pkgInfo *PackageInfo
var flagsStr string
var installationReason = InstallationReasonUnknown var installationReason = InstallationReasonUnknown
if value.GetActionType() == "install" { if value.GetActionType() == "install" {
installationReason = value.(*InstallPackageAction).InstallationReason installationReason = value.(*InstallPackageAction).InstallationReason
pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo pkgInfo = value.(*InstallPackageAction).BpmPackage.PkgInfo
if len(value.(*InstallPackageAction).Flags) > 0 {
flagsSlice := make([]string, 0)
for flag, value := range value.(*InstallPackageAction).Flags {
flagsSlice = append(flagsSlice, flag+"="+value)
}
flagsStr = "[" + strings.Join(flagsSlice, ",") + "]"
}
if value.(*InstallPackageAction).SplitPackageToInstall != "" { if value.(*InstallPackageAction).SplitPackageToInstall != "" {
pkgInfo = pkgInfo.GetSplitPackageInfo(value.(*InstallPackageAction).SplitPackageToInstall) pkgInfo = pkgInfo.GetSplitPackageInfo(value.(*InstallPackageAction).SplitPackageToInstall)
} }
} else if value.GetActionType() == "fetch" { } else if value.GetActionType() == "fetch" {
installationReason = value.(*FetchPackageAction).InstallationReason installationReason = value.(*FetchPackageAction).InstallationReason
if len(value.(*FetchPackageAction).Flags) > 0 {
flagsSlice := make([]string, 0)
for flag, value := range value.(*FetchPackageAction).Flags {
flagsSlice = append(flagsSlice, flag+"="+value)
}
flagsStr = "[" + strings.Join(flagsSlice, ",") + "]"
}
pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info pkgInfo = value.(*FetchPackageAction).DatabaseEntry.Info
} else { } else {
pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo
@@ -466,15 +410,15 @@ func (operation *BPMOperation) ShowOperationSummary() {
installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir) installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir)
if installedInfo == nil { if installedInfo == nil {
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Install", installationReasonStr, pkgInfo.Type == "source") fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name+flagsStr, pkgInfo.GetFullVersion(), "Install", installationReasonStr, pkgInfo.Type == "source")
} else { } else {
comparison := CompareVersions(pkgInfo.GetFullVersion(), installedInfo.GetFullVersion()) comparison := CompareVersions(pkgInfo.GetFullVersion(), installedInfo.GetFullVersion())
if comparison < 0 { if comparison < 0 {
fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Downgrade", installationReasonStr, pkgInfo.Type == "source") fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name+flagsStr, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Downgrade", installationReasonStr, pkgInfo.Type == "source")
} else if comparison > 0 { } else if comparison > 0 {
fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Upgrade", installationReasonStr, pkgInfo.Type == "source") fmt.Fprintf(writer, "%s\t%s -> %s\t%s\t%s\t%t\n", pkgInfo.Name+flagsStr, installedInfo.GetFullVersion(), pkgInfo.GetFullVersion(), "Upgrade", installationReasonStr, pkgInfo.Type == "source")
} else { } else {
fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name, pkgInfo.GetFullVersion(), "Reinstall", installationReasonStr, pkgInfo.Type == "source") fmt.Fprintf(writer, "%s\t%s\t%s\t%s\t%t\n", pkgInfo.Name+flagsStr, pkgInfo.GetFullVersion(), "Reinstall", installationReasonStr, pkgInfo.Type == "source")
} }
} }
} }
@@ -527,7 +471,7 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
optionalDepends = make(map[string][]string) optionalDepends = make(map[string][]string)
// Find all optional dependencies // Find all optional dependencies
for _, value := range slices.Clone(operation.Actions) { for _, value := range operation.Actions {
var pkgInfo *PackageInfo var pkgInfo *PackageInfo
if value.GetActionType() == "install" { if value.GetActionType() == "install" {
action := value.(*InstallPackageAction) action := value.(*InstallPackageAction)
@@ -540,24 +484,58 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
} }
for _, depend := range pkgInfo.OptionalDepends { for _, depend := range pkgInfo.OptionalDepends {
dependSplit := strings.SplitN(depend, ":", 2) // Deconstruct package string
d, err := DeconstructPackageString(depend)
if err != nil {
continue
}
// Skip if dependency is already installed // Skip if dependency is already installed
if IsPackageInstalled(dependSplit[0], operation.RootDir) { if IsPackageInstalled(d.PkgName, operation.RootDir) || len(GetVirtualPackageInfo(d.PkgName, operation.RootDir)) > 0 {
continue
}
// Skip if dependency is going to be installed
if slices.IndexFunc(operation.Actions, func(action OperationAction) bool {
var pkgInfo *PackageInfo
if action.GetActionType() == "install" {
action := action.(*InstallPackageAction)
pkgInfo = action.BpmPackage.PkgInfo
} else if action.GetActionType() == "fetch" {
action := action.(*FetchPackageAction)
pkgInfo = action.DatabaseEntry.Info
} else {
return false
}
if pkgInfo.Name == d.PkgName {
return true
} else if slices.Contains(pkgInfo.Provides, d.PkgName) {
return true
}
return false
}) != -1 {
continue continue
} }
// Skip if not a new dependency of the package // 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 { 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] // Deconstruct package string
d2, err := DeconstructPackageString(depend)
if err != nil {
return false
}
return d2.PkgName == d.PkgName
}) { }) {
continue continue
} }
if len(dependSplit) == 2 { if d.Description != "" {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", dependSplit[0], dependSplit[1])) optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", d.PkgName, d.Description))
} else { } else {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependSplit[0]) optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], d.PkgName)
} }
} }
} }
@@ -565,7 +543,7 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
return return
} }
func (operation *BPMOperation) RunHooks(verbose bool) error { func (operation *BPMOperation) RunPreHooks(verbose bool) error {
// Return if hooks directory does not exist // Return if hooks directory does not exist
if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() { if stat, err := os.Stat(path.Join(operation.RootDir, "var/lib/bpm/hooks")); err != nil || !stat.IsDir() {
return nil return nil
@@ -585,7 +563,46 @@ func (operation *BPMOperation) RunHooks(verbose bool) error {
log.Printf("Error while reading hook (%s): %s", entry.Name(), err) 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 { if err != nil {
log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err) log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err)
continue continue
@@ -655,6 +672,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
operation.Actions[i] = &InstallPackageAction{ operation.Actions[i] = &InstallPackageAction{
File: fetchedPackages[entry.Filepath], File: fetchedPackages[entry.Filepath],
InstallationReason: action.(*FetchPackageAction).InstallationReason, InstallationReason: action.(*FetchPackageAction).InstallationReason,
Flags: action.(*FetchPackageAction).Flags,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
SplitPackageToInstall: entry.Info.Name, SplitPackageToInstall: entry.Info.Name,
} }
@@ -662,6 +680,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
operation.Actions[i] = &InstallPackageAction{ operation.Actions[i] = &InstallPackageAction{
File: fetchedPackages[entry.Filepath], File: fetchedPackages[entry.Filepath],
InstallationReason: action.(*FetchPackageAction).InstallationReason, InstallationReason: action.(*FetchPackageAction).InstallationReason,
Flags: action.(*FetchPackageAction).Flags,
BpmPackage: bpmpkg, BpmPackage: bpmpkg,
} }
} }
@@ -669,9 +688,40 @@ func (operation *BPMOperation) FetchPackages() (err error) {
} }
operation.hasFetchedPackages = true operation.hasFetchedPackages = true
return nil 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)
if isUpgrade {
for _, pkgFile := range installAction.BpmPackage.PkgFiles {
operation.ModifiedFiles[pkgFile.Path] = "upgrade"
}
for _, pkgFile := range GetPackage(installAction.BpmPackage.PkgInfo.Name, operation.RootDir).PkgFiles {
operation.ModifiedFiles[pkgFile.Path] = "upgrade"
}
} else {
for _, pkgFile := range installAction.BpmPackage.PkgFiles {
operation.ModifiedFiles[pkgFile.Path] = "install"
}
}
}
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) { func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Fetch packages // Fetch packages
if !operation.hasFetchedPackages { if !operation.hasFetchedPackages {
@@ -711,6 +761,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
} else if action.GetActionType() == "install" { } else if action.GetActionType() == "install" {
value := action.(*InstallPackageAction) value := action.(*InstallPackageAction)
fileToInstall := value.File fileToInstall := value.File
compilationFlags := value.Flags
bpmpkg := value.BpmPackage bpmpkg := value.BpmPackage
var err error var err error
@@ -735,7 +786,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Compile source package if not compiled already // Compile source package if not compiled already
if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok { if _, ok := operation.compiledPackages[pkgNameToInstall]; !ok {
outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, operation.CompilationJobs, !operation.RunChecks, false, verbose) outputBpmPackages, err := CompileSourcePackage(value.File, compiledDir, compilationFlags, operation.CompilationJobs, !operation.RunChecks, false, verbose)
if err != nil { if err != nil {
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err) return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
} }
@@ -755,16 +806,15 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
} }
if value.InstallationReason != InstallationReasonManual { if value.InstallationReason != InstallationReasonManual {
err = installPackage(fileToInstall, value.InstallationReason, operation.RootDir, verbose, true) err = installPackage(fileToInstall, value.InstallationReason, value.Flags, operation.RootDir, verbose, true)
} else { } else {
err = installPackage(fileToInstall, value.InstallationReason, operation.RootDir, verbose, force) err = installPackage(fileToInstall, value.InstallationReason, value.Flags, operation.RootDir, verbose, force)
} }
if err != nil { if err != nil {
return fmt.Errorf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err) return fmt.Errorf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err)
} }
} }
} }
fmt.Println("Operation complete!")
return nil return nil
} }
@@ -776,6 +826,7 @@ type OperationAction interface {
type InstallPackageAction struct { type InstallPackageAction struct {
File string File string
InstallationReason InstallationReason InstallationReason InstallationReason
Flags map[string]string
SplitPackageToInstall string SplitPackageToInstall string
BpmPackage *BPMPackage BpmPackage *BPMPackage
} }
@@ -786,6 +837,7 @@ func (action *InstallPackageAction) GetActionType() string {
type FetchPackageAction struct { type FetchPackageAction struct {
InstallationReason InstallationReason InstallationReason InstallationReason
Flags map[string]string
DatabaseEntry *BPMDatabaseEntry DatabaseEntry *BPMDatabaseEntry
} }
@@ -800,3 +852,41 @@ type RemovePackageAction struct {
func (action *RemovePackageAction) GetActionType() string { func (action *RemovePackageAction) GetActionType() string {
return "remove" 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
}
+195 -41
View File
@@ -10,6 +10,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"regexp" "regexp"
"slices" "slices"
"sort" "sort"
@@ -48,10 +49,27 @@ type PackageInfo struct {
Replaces []string `yaml:"replaces,omitempty"` Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"` Provides []string `yaml:"provides,omitempty"`
Options []string `yaml:"options,omitempty"` Options []string `yaml:"options,omitempty"`
Flags []PackageFlag `yaml:"flags,omitempty"`
Downloads []PackageDownload `yaml:"downloads,omitempty"` Downloads []PackageDownload `yaml:"downloads,omitempty"`
SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"` SplitPackages []*PackageInfo `yaml:"split_packages,omitempty"`
} }
type PackageFlag struct {
Name string `yaml:"name"`
DefaultValue string `yaml:"default_value"`
BuiltValue string `yaml:"built_value"`
AcceptedValues []PackageAcceptedValue `yaml:"accepted_values,omitempty"`
}
type PackageAcceptedValue struct {
Value string `yaml:"value"`
Depends []string `yaml:"depends,omitempty"`
RuntimeDepends []string `yaml:"runtime_depends,omitempty"`
OptionalDepends []string `yaml:"optional_depends,omitempty"`
MakeDepends []string `yaml:"make_depends,omitempty"`
CheckDepends []string `yaml:"check_depends,omitempty"`
}
type PackageDownload struct { type PackageDownload struct {
Url string `yaml:"url"` Url string `yaml:"url"`
Type string `yaml:"type,omitempty"` Type string `yaml:"type,omitempty"`
@@ -79,6 +97,7 @@ type PackageFileEntry struct {
type PackageLocalInfo struct { type PackageLocalInfo struct {
InstallationReason string `yaml:"installation_reason"` InstallationReason string `yaml:"installation_reason"`
Flags map[string]string `yaml:"flags"`
InstalledOn int64 `yaml:"installed_on"` InstalledOn int64 `yaml:"installed_on"`
LastUpdatedOn int64 `yaml:"last_updated_on"` LastUpdatedOn int64 `yaml:"last_updated_on"`
} }
@@ -162,7 +181,7 @@ func GetPackageInfoRaw(filename string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
if header.Name == "pkg.info" { if header.Name == "info.yml" {
bs, _ := io.ReadAll(tr) bs, _ := io.ReadAll(tr)
err := file.Close() err := file.Close()
if err != nil { if err != nil {
@@ -171,7 +190,7 @@ func GetPackageInfoRaw(filename string) (string, error) {
return string(bs), nil return string(bs), nil
} }
} }
return "", errors.New("pkg.info not found in archive") return "", errors.New("info.yml not found in archive")
} }
func ReadPackage(filename string) (*BPMPackage, error) { func ReadPackage(filename string) (*BPMPackage, error) {
@@ -197,13 +216,13 @@ func ReadPackage(filename string) (*BPMPackage, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if header.Name == "pkg.info" { if header.Name == "info.yml" {
bs, _ := io.ReadAll(tr) bs, _ := io.ReadAll(tr)
pkgInfo, err = ReadPackageInfo(string(bs)) pkgInfo, err = ReadPackageInfo(string(bs))
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if header.Name == "pkg.files" { } else if header.Name == "files.txt" {
bs, _ := io.ReadAll(tr) bs, _ := io.ReadAll(tr)
for _, line := range strings.Split(string(bs), "\n") { for _, line := range strings.Split(string(bs), "\n") {
if strings.TrimSpace(line) == "" { if strings.TrimSpace(line) == "" {
@@ -211,7 +230,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
} }
stringEntry := strings.Split(strings.TrimSpace(line), " ") stringEntry := strings.Split(strings.TrimSpace(line), " ")
if len(stringEntry) < 5 { if len(stringEntry) < 5 {
return nil, errors.New("pkg.files is not formatted correctly") return nil, errors.New("files.txt is not formatted correctly")
} }
octalPerms, err := strconv.ParseUint(stringEntry[len(stringEntry)-4], 8, 32) octalPerms, err := strconv.ParseUint(stringEntry[len(stringEntry)-4], 8, 32)
if err != nil { if err != nil {
@@ -241,7 +260,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
} }
if pkgInfo == nil { if pkgInfo == nil {
return nil, errors.New("pkg.info not found in archive") return nil, errors.New("info.yml not found in archive")
} }
return &BPMPackage{ return &BPMPackage{
PkgInfo: pkgInfo, PkgInfo: pkgInfo,
@@ -594,6 +613,70 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
} }
builder.WriteString("Type: " + pkgInfo.Type + "\n") builder.WriteString("Type: " + pkgInfo.Type + "\n")
// Flags
if pkgInfo.Type == "binary" {
var flags []string
for _, flag := range pkgInfo.Flags {
if pkgInfo != GetPackageInfo(pkgInfo.Name, rootDir) {
flags = append(flags, fmt.Sprintf("%s=%s", flag.Name, flag.BuiltValue))
} else if _, ok := getPackageLocalInfo(pkgInfo.Name, rootDir).Flags[flag.Name]; ok {
flags = append(flags, fmt.Sprintf("%s=%s (User defined)", flag.Name, flag.BuiltValue))
} else {
flags = append(flags, fmt.Sprintf("%s=%s (Default value)", flag.Name, flag.BuiltValue))
}
}
builderWriteArray("Built with flags:", flags, false)
} else {
if len(pkgInfo.Flags) > 0 {
builder.WriteString("Available flags (")
builder.WriteString(strconv.Itoa(len(pkgInfo.Flags)))
builder.WriteString("):\n")
for _, flag := range pkgInfo.Flags {
builder.WriteString(" - Flag: ")
builder.WriteString(flag.Name)
builder.WriteRune('\n')
if flag.DefaultValue != "" {
builder.WriteString(" Default value: ")
builder.WriteString(flag.DefaultValue)
builder.WriteRune('\n')
}
if len(flag.AcceptedValues) > 0 {
builder.WriteString(" Accepted values (")
builder.WriteString(strconv.Itoa(len(flag.AcceptedValues)))
builder.WriteString("):\n")
for i, acceptedValue := range flag.AcceptedValues {
writeValueDepends := func(text string, depends []string) {
if len(depends) > 0 {
builder.WriteString(" ")
builder.WriteString(text)
builder.WriteString(" (")
builder.WriteString(strconv.Itoa(len(depends)))
builder.WriteString("): ")
for _, depend := range depends {
if i != 0 {
builder.WriteString(", ")
}
builder.WriteString(depend)
}
builder.WriteRune('\n')
}
}
builder.WriteString(" - Value: ")
builder.WriteString(acceptedValue.Value)
builder.WriteRune('\n')
writeValueDepends("Dependencies", acceptedValue.Depends)
writeValueDepends("Make Dependencies", acceptedValue.MakeDepends)
writeValueDepends("Check Dependencies", acceptedValue.CheckDepends)
writeValueDepends("Runtime Dependencies", acceptedValue.RuntimeDepends)
writeValueDepends("Optional Dependencies", acceptedValue.OptionalDepends)
}
}
}
}
}
// Dependencies // Dependencies
builderWriteDependencyArray("Dependencies", pkgInfo.Depends) builderWriteDependencyArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" { if pkgInfo.Type == "source" {
@@ -627,7 +710,7 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
builder.WriteString("\n") 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) builderWriteArray("Optionally dependant packages", pkgInfo.GetPackageOptionalDependants(rootDir), true)
// Other package relations // Other package relations
@@ -698,6 +781,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
extractFilename := path.Join(rootDir, header.Name) extractFilename := path.Join(rootDir, header.Name)
switch header.Typeflag { switch header.Typeflag {
case tar.TypeDir: 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 _, err := os.Stat(extractFilename); err == nil {
if verbose { if verbose {
fmt.Printf("Skipping Directory: %s (Directory already exists)\n", extractFilename) fmt.Printf("Skipping Directory: %s (Directory already exists)\n", extractFilename)
@@ -725,6 +819,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
} }
bar.Add64(header.Size) bar.Add64(header.Size)
case tar.TypeReg: 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 skip := false
if _, err := os.Stat(extractFilename); err == nil { if _, err := os.Stat(extractFilename); err == nil {
for _, k := range bpmpkg.PkgInfo.Keep { for _, k := range bpmpkg.PkgInfo.Keep {
@@ -782,6 +887,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
} }
bar.Add64(header.Size) bar.Add64(header.Size)
case tar.TypeSymlink: 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) err := os.Remove(extractFilename)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
@@ -797,6 +913,17 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
} }
bar.Add64(header.Size) bar.Add64(header.Size)
case tar.TypeLink: 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 { if verbose {
fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/"))) fmt.Println("Detected Hard Link: " + extractFilename + " -> " + path.Join(rootDir, strings.TrimPrefix(header.Linkname, "files/")))
} }
@@ -825,7 +952,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
return nil return nil
} }
func installPackage(filename string, installationReason InstallationReason, rootDir string, verbose, force bool) error { func installPackage(filename string, installationReason InstallationReason, flags map[string]string, rootDir string, verbose, force bool) error {
if _, err := os.Stat(filename); os.IsNotExist(err) { if _, err := os.Stat(filename); os.IsNotExist(err) {
return err return err
} }
@@ -872,26 +999,39 @@ func installPackage(filename string, installationReason InstallationReason, root
fmt.Printf("Removing old files for package (%s)...\n", bpmpkg.PkgInfo.Name) fmt.Printf("Removing old files for package (%s)...\n", bpmpkg.PkgInfo.Name)
} }
for _, entry := range fileEntries { for _, entry := range fileEntries {
file := path.Join(rootDir, entry.Path) finalPath := path.Join(rootDir, entry.Path)
stat, err := os.Lstat(file)
stat, err := os.Lstat(finalPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} } else if err != nil {
if err != nil {
return err 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 { if verbose {
fmt.Println("Skipping path: " + file + " (Path is managed by multiple packages)") fmt.Printf("Skipping path: %s (Path was ignored)\n", finalPath)
} }
continue continue
} }
if len(files[entry.Path]) != 0 {
if verbose {
fmt.Println("Skipping path: " + finalPath + " (Path is managed by multiple packages)")
}
continue
}
shouldContinue := false shouldContinue := false
for _, value := range bpmpkg.PkgInfo.Keep { for _, value := range bpmpkg.PkgInfo.Keep {
if strings.HasSuffix(value, "/") { if strings.HasSuffix(value, "/") {
if strings.HasPrefix(entry.Path, value) || entry.Path == strings.TrimSuffix(value, "/") { if strings.HasPrefix(entry.Path, value) || entry.Path == strings.TrimSuffix(value, "/") {
if verbose { 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 shouldContinue = true
continue continue
@@ -899,7 +1039,7 @@ func installPackage(filename string, installationReason InstallationReason, root
} else { } else {
if entry.Path == value { if entry.Path == value {
if verbose { 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 shouldContinue = true
continue continue
@@ -911,37 +1051,37 @@ func installPackage(filename string, installationReason InstallationReason, root
} }
if stat.Mode()&os.ModeSymlink != 0 { if stat.Mode()&os.ModeSymlink != 0 {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
continue continue
} }
if stat.IsDir() { if stat.IsDir() {
dir, err := os.ReadDir(file) dir, err := os.ReadDir(finalPath)
if err != nil { if err != nil {
return err return err
} }
if len(dir) != 0 { if len(dir) != 0 {
if verbose { if verbose {
fmt.Println("Skipping non-empty directory: " + file) fmt.Println("Skipping non-empty directory: " + finalPath)
} }
continue continue
} }
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err = os.Remove(file) err = os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
@@ -976,12 +1116,12 @@ func installPackage(filename string, installationReason InstallationReason, root
return err return err
} }
f, err := os.Create(path.Join(pkgDir, "files")) f, err := os.Create(path.Join(pkgDir, "files.txt"))
if err != nil { if err != nil {
return err return err
} }
tarballFile, err := readTarballFile(filename, "pkg.files") tarballFile, err := readTarballFile(filename, "files.txt")
if err != nil { if err != nil {
return err return err
} }
@@ -992,7 +1132,7 @@ func installPackage(filename string, installationReason InstallationReason, root
return err return err
} }
f, err = os.Create(path.Join(pkgDir, "info")) f, err = os.Create(path.Join(pkgDir, "info.yml"))
if err != nil { if err != nil {
return err return err
} }
@@ -1016,6 +1156,7 @@ func installPackage(filename string, installationReason InstallationReason, root
} }
localInfo.LastUpdatedOn = time.Now().Unix() localInfo.LastUpdatedOn = time.Now().Unix()
localInfo.InstallationReason = string(installationReason) localInfo.InstallationReason = string(installationReason)
localInfo.Flags = flags
SetPackageLocalInfo(bpmpkg.PkgInfo.Name, localInfo, rootDir) SetPackageLocalInfo(bpmpkg.PkgInfo.Name, localInfo, rootDir)
@@ -1111,31 +1252,44 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
// Removing package files // Removing package files
for _, entry := range fileEntries { for _, entry := range fileEntries {
bar.Add64(entry.SizeInBytes) 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) { if os.IsNotExist(err) {
continue continue
} } else if err != nil {
if err != nil {
return err 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 len(files[entry.Path]) != 0 {
if verbose { 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 continue
} }
if lstat.Mode()&os.ModeSymlink != 0 { if lstat.Mode()&os.ModeSymlink != 0 {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
continue continue
} }
stat, err := os.Stat(file) stat, err := os.Stat(finalPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
continue continue
} }
@@ -1143,28 +1297,28 @@ func removePackage(pkg string, verbose bool, rootDir string) error {
return err return err
} }
if stat.IsDir() { if stat.IsDir() {
dir, err := os.ReadDir(file) dir, err := os.ReadDir(finalPath)
if err != nil { if err != nil {
return err return err
} }
if len(dir) != 0 { if len(dir) != 0 {
if verbose { if verbose {
fmt.Println("Skipping non-empty directory: " + file) fmt.Println("Skipping non-empty directory: " + finalPath)
} }
continue continue
} }
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err = os.Remove(file) err = os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
if verbose { if verbose {
fmt.Println("Removing: " + file) fmt.Println("Removing: " + finalPath)
} }
err := os.Remove(file) err := os.Remove(finalPath)
if err != nil { if err != nil {
return err return err
} }