9 Commits
9 changed files with 970 additions and 393 deletions
+61 -23
View File
@@ -92,7 +92,7 @@ func main() {
currentFlagSet.String("installation-reason", "", "Specify the installation reason to use for the specified packages")
currentFlagSet.BoolP("reinstall", "r", false, "Reinstall the specified packages")
currentFlagSet.IntP("jobs", "j", bpmlib.CompilationBPMConfig.CompilationJobs, "Set the amount of concurrent processes to use for source package compilation")
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:])
installPackages()
@@ -141,7 +141,7 @@ func main() {
currentFlagSet.BoolP("yes", "y", false, "Enter 'yes' in all prompts")
currentFlagSet.BoolP("no-sync", "n", false, "Do not sync databases")
currentFlagSet.Bool("allow-downgrades", false, "Allow package downgrades")
currentFlagSet.BoolP("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")
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("yes", "y", false, "Enter 'yes' in all prompts")
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("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")
@@ -239,18 +239,20 @@ func showPackageInfo() {
}
for n, pkg := range packages {
if showDatabaseInfo {
var err error
var entry *bpmlib.BPMDatabaseEntry
entry, _, err = bpmlib.GetDatabaseEntry(pkg)
// Deconstruct package string
d, err := bpmlib.DeconstructPackageString(pkg)
if err != nil {
if providers := bpmlib.GetDatabaseVirtualPackageEntry(pkg); len(providers) > 0 {
entry = providers[0]
} else {
log.Printf("Error: could not find package (%s) in any database\n", pkg)
log.Printf("Error: could not deconstruct package string: %s\n", err)
exitCode = 1
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 {
@@ -263,8 +265,8 @@ func showPackageInfo() {
var bpmpkg *bpmlib.BPMPackage
isFile := false
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err = bpmlib.ReadPackage(pkg)
if stat, err := os.Stat(d.PkgName); err == nil && !stat.IsDir() {
bpmpkg, err = bpmlib.ReadPackage(d.PkgName)
if err != nil {
log.Printf("Error: could not read package: %s\n", err)
exitCode = 1
@@ -272,22 +274,30 @@ func showPackageInfo() {
}
isFile = true
} 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)
} else {
bpmpkg = bpmlib.GetPackage(pkg, rootDir)
bpmpkg = bpmlib.GetPackage(d.PkgName, rootDir)
}
}
if bpmpkg == nil {
log.Printf("Error: package (%s) is not installed\n", pkg)
exitCode = 1
return
}
if !bpmlib.EvaluatePackageString(bpmpkg.PkgInfo, d) {
log.Printf("Error: package (%s) is not installed\n", pkg)
exitCode = 1
return
}
if n != 0 {
fmt.Println()
}
if isFile {
abs, err := filepath.Abs(pkg)
abs, err := filepath.Abs(d.PkgName)
if err != nil {
log.Printf("Error: could not get absolute path of file (%s)\n", abs)
exitCode = 1
@@ -618,7 +628,7 @@ func installPackages() {
// Create installation operation
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)
exitCode = 1
return
@@ -773,7 +783,7 @@ func removePackages() {
// Create remove operation
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)
exitCode = 1
return
@@ -917,7 +927,7 @@ func doCleanup() {
// Create cleanup operation
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)
exitCode = 1
return
@@ -1081,7 +1091,7 @@ func updatePackages() {
// Create update operation
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)
exitCode = 1
return
@@ -1253,6 +1263,7 @@ func compilePackage() {
rootDir, _ := currentFlagSet.GetString("root")
verbose, _ := currentFlagSet.GetBool("verbose")
yesAll, _ := currentFlagSet.GetBool("yes")
force, _ := currentFlagSet.GetBool("force")
keepCompilationFiles, _ := currentFlagSet.GetBool("keep")
installSrcPkgDepends, _ := currentFlagSet.GetBool("depends")
skipChecks, _ := currentFlagSet.GetBool("skip-checks")
@@ -1285,8 +1296,16 @@ func compilePackage() {
// Compile packages
for _, sourcePackage := range sourcePackages {
if _, err := os.Stat(sourcePackage); os.IsNotExist(err) {
log.Printf("Error: file (%s) does not exist!", sourcePackage)
// Deconstruct package string
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
return
}
@@ -1306,6 +1325,12 @@ func compilePackage() {
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
totalDepends := make([]string, 0)
totalDepends = append(totalDepends, bpmpkg.PkgInfo.Depends...)
@@ -1332,6 +1357,19 @@ func compilePackage() {
// Install missing source package dependencies
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
executable, err := os.Executable()
if err != nil {
@@ -1460,7 +1498,7 @@ func compilePackage() {
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 {
// Remove unused packages
cleanupFunc()
+73 -26
View File
@@ -24,7 +24,7 @@ import (
var rootCompilationUID = "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
if compilationJobs <= 0 || compilationJobs > runtime.NumCPU() {
compilationJobs = runtime.NumCPU()
@@ -96,8 +96,8 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
return nil, err
}
// Extract source.sh file
err = extractTarballFile(archiveFilename, "source.sh", tempDirectory, uid, gid)
// Extract recipe.sh file
err = extractTarballFile(archiveFilename, "recipe.sh", tempDirectory, uid, gid)
if err != nil {
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_ARCH="+bpmpkg.PkgInfo.OutputArch)
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...)
// 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, "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",
"set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+
"set -a\n"+ // Source and export functions and variables in recipe.sh script
". \"${BPM_WORKDIR}\"/recipe.sh\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 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
}
// Execute check function in source.sh script if not skipping checks
// Execute check function in recipe.sh script if not skipping checks
if !skipChecks {
cmd = exec.Command("bash", "-c",
"set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+
"set -a\n"+ // Source and export functions and variables in recipe.sh script
". \"${BPM_WORKDIR}\"/recipe.sh\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
"exit 0")
@@ -238,10 +247,10 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
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",
"set -a\n"+ // Source and export functions and variables in source.sh script
". \"${BPM_WORKDIR}\"/source.sh\n"+
"set -a\n"+ // Source and export functions and variables in recipe.sh script
". \"${BPM_WORKDIR}\"/recipe.sh\n"+
"set +a\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
@@ -329,7 +338,7 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
// Generate 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.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -345,7 +354,15 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
// Create gzip-compressed archive for the package files
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.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -373,6 +390,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
pkgInfo.SplitPackages = 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
pkgInfoBytes, err := yaml.Marshal(pkgInfo)
if err != nil {
@@ -380,26 +422,31 @@ func CompileSourcePackage(archiveFilename, outputDirectory string, compilationJo
}
pkgInfoBytes = append(pkgInfoBytes, '\n')
// Create pkg.info file
err = os.WriteFile(path.Join(tempDirectory, "pkg.info"), pkgInfoBytes, 0644)
// Create info.yml file
err = os.WriteFile(path.Join(tempDirectory, "info.yml"), pkgInfoBytes, 0644)
if err != nil {
return nil, err
}
// Change pkg.info file owner
err = os.Chown(path.Join(tempDirectory, "pkg.info"), uid, gid)
// Change info.yml file owner
err = os.Chown(path.Join(tempDirectory, "info.yml"), uid, gid)
if err != nil {
return nil, err
}
// Get files to include in BPM archive
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
// Create 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.Stdout = os.Stdout
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)
}
// Remove pkg.info file
err = os.Remove(path.Join(tempDirectory, "pkg.info"))
// Remove info.yml file
err = os.Remove(path.Join(tempDirectory, "info.yml"))
if err != nil {
return nil, err
}
@@ -725,8 +772,8 @@ func showPackageFiles(archiveFilename string) error {
return nil
}
// Print pkg.info content
err = printTarballContent("pkg.info")
// Print info.yml content
err = printTarballContent("info.yml")
if err != nil {
return err
}
@@ -748,8 +795,8 @@ func showPackageFiles(archiveFilename string) error {
}
}
// Print source.sh content
err = printTarballContent("source.sh")
// Print recipe.sh content
err = printTarballContent("recipe.sh")
if err != nil {
return err
}
+163 -44
View File
@@ -42,7 +42,7 @@ type BPMDatabaseEntry struct {
Database *BPMDatabase
}
var BPMDatabases = make(map[string]*BPMDatabase)
var BPMDatabases = make([]*BPMDatabase, 0)
func (db *BPMDatabase) ContainsPackage(pkg string) bool {
_, ok := db.Entries[pkg]
@@ -135,7 +135,7 @@ func (db *configDatabase) ReadLocalDatabase() error {
}
}
BPMDatabases[db.Name] = database
BPMDatabases = append(BPMDatabases, database)
return nil
}
@@ -207,33 +207,85 @@ func ReadLocalDatabaseFiles() (err error) {
return nil
}
func GetDatabaseEntry(str string) (*BPMDatabaseEntry, *BPMDatabase, error) {
split := strings.Split(str, "/")
func ResolveDatabaseEntry(d DeconstructedPackageString, rootDir string) *BPMDatabaseEntry {
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 {
pkgName := strings.TrimSpace(split[0])
if pkgName == "" {
return nil, nil, errors.New("could not find database entry for this package")
return results
}
for _, db := range BPMDatabases {
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 {
dbName := strings.TrimSpace(split[0])
pkgName := strings.TrimSpace(split[1])
if dbName == "" || pkgName == "" {
return nil, nil, errors.New("could not find database entry for this package")
return results
}
db := BPMDatabases[dbName]
if db == nil || !db.ContainsPackage(pkgName) {
return nil, nil, errors.New("could not find database entry for this package")
dbIndex := slices.IndexFunc(BPMDatabases, func(db *BPMDatabase) bool {
return db.Name == dbName
})
if dbIndex < 0 {
return results
}
return db.Entries[pkgName], db, nil
} else {
return nil, nil, errors.New("could not find database entry for this package")
db := BPMDatabases[dbIndex]
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 {
@@ -250,18 +302,6 @@ func FindReplacement(pkg string) *BPMDatabaseEntry {
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) {
// Check if package exists in database
if !db.ContainsPackage(pkg) {
@@ -311,8 +351,12 @@ func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
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
@@ -320,8 +364,12 @@ func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
// Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
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
@@ -331,8 +379,12 @@ func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
for _, vpkg := range entry.Info.Provides {
// Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(e.Info.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
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
@@ -340,8 +392,12 @@ func (entry *BPMDatabaseEntry) GetEntryDependants() (dependants []string) {
// Add installed package to list if its runtime dependencies contain a provided virtual package
if slices.ContainsFunc(e.Info.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
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
@@ -374,13 +430,13 @@ func (entry *BPMDatabaseEntry) GetEntryOptionalDependants() (dependants []string
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.ContainsFunc(e.Info.OptionalDepends, func(n string) bool {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
return d.PkgName == entry.Info.Name
}) {
dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name)
}
@@ -411,8 +467,13 @@ func (entry *BPMDatabaseEntry) GetEntryMakeDependants() (dependants []string) {
for _, db := range BPMDatabases {
for _, e := range db.Entries {
if slices.ContainsFunc(e.Info.MakeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == entry.Info.Name
// 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)
}
@@ -473,7 +534,7 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
builder.WriteString(" - " + val)
// Show virtual package providers
if providers := GetDatabaseVirtualPackageEntry(val); len(providers) > 0 {
if providers := SearchDatabaseVirtualPackageProviders(val); len(providers) > 0 {
builder.WriteString(" (")
for i, vpkg := range providers {
if i == len(providers)-1 {
@@ -503,6 +564,64 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
}
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
builderWriteDependencyArray("Dependencies", entry.Info.Depends)
if entry.Info.Type == "source" {
@@ -521,7 +640,7 @@ func (entry *BPMDatabaseEntry) CreateReadableInfo(rootDir string, showBytes bool
}
// Show virtual package providers
if providers := GetDatabaseVirtualPackageEntry(dependSplit[0]); len(providers) > 0 {
if providers := SearchDatabaseVirtualPackageProviders(dependSplit[0]); len(providers) > 0 {
builder.WriteString(" (")
for i, vpkg := range providers {
if i == len(providers)-1 {
+277 -98
View File
@@ -1,6 +1,8 @@
package bpmlib
import (
"fmt"
"maps"
"slices"
"strings"
)
@@ -21,8 +23,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultiplePro
// Add installed package to list if its dependencies include pkgName
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
@@ -30,8 +37,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultiplePro
// Add installed package to list if its runtime dependencies include pkgName
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
@@ -45,8 +57,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultiplePro
// Add installed package to list if its dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.Depends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
@@ -54,8 +71,13 @@ func (pkgInfo *PackageInfo) GetPackageDependants(rootDir string, skipMultiplePro
// Add installed package to list if its runtime dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.RuntimeDepends, func(n string) bool {
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
return d.PkgName == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
@@ -82,13 +104,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
// Add installed package to list if its optional dependencies include pkgName
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == pkgInfo.Name
return d.PkgName == pkgInfo.Name
}) {
dependants = append(dependants, installedPkg.Name)
continue
@@ -98,13 +120,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
for _, vpkg := range pkgInfo.Provides {
// Add installed package to list if its optional dependencies contain a provided virtual package
if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Deconstruct package string
d, err := DeconstructPackageString(n)
if err != nil {
return false
}
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == vpkg
return d.PkgName == vpkg
}) {
dependants = append(dependants, installedPkg.Name)
break
@@ -117,55 +139,85 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend
type ResolvedPackage struct {
DatabaseEntry *BPMDatabaseEntry
Flags map[string]string
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)
var dfs func(resolvedPkg *PackageInfo)
dfs = func(pkgInfo *PackageInfo) {
var dfs func(resolvedPkg *PackageInfo, flags map[string]string)
dfs = func(pkgInfo *PackageInfo, flags map[string]string) {
checkDependencies := func(dependencies []string, installationReason InstallationReason) {
for _, depend := range dependencies {
// Split dependency name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
// Deconstruct package string
d, err := DeconstructPackageString(depend)
if err != nil {
unresolved[depend] = "could not deconstruct package string: " + err.Error()
continue
}
// Ignore if package is already installed
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
} else if providers := GetVirtualPackageInfo(dependName, rootDir); len(providers) > 0 {
continue
// Check resolved virtual packages
if resolvedPkg, ok := resolvedVirtualPackages[d.PkgName]; ok {
d.PkgName = resolvedPkg
}
// Find database entry for dependency
var dependEntry *BPMDatabaseEntry
if resolvedVpkg, ok := resolvedVirtualPackages[dependName]; ok {
dependEntry, _, _ = GetDatabaseEntry(resolvedVpkg)
} else if entry, _, _ := GetDatabaseEntry(dependName); entry != nil {
dependEntry = entry
} else if providers := GetDatabaseVirtualPackageEntry(dependName); len(providers) > 0 {
dependEntry = providers[0]
}
dependEntry := ResolveDatabaseEntry(d, rootDir)
if dependEntry == nil {
unresolved = append(unresolved, depend)
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
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
}
// 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) {
dfs(dependEntry.Info)
resolved = append(resolved, ResolvedPackage{DatabaseEntry: dependEntry, InstallationReason: installationReason})
dfs(dependEntry.Info, d.Flags)
resolved = append(resolved, ResolvedPackage{DatabaseEntry: dependEntry, Flags: d.Flags, InstallationReason: installationReason})
}
}
}
@@ -179,69 +231,196 @@ func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[strin
if pkgInfo.Type == "source" {
checkDependencies(pkgInfo.MakeDepends, 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
}
func SplitPkgNameAndVersion(pkg string) (string, string, string) {
if strings.Contains(pkg, ">=") {
pkgSplit := strings.SplitN(pkg, ">=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">=", pkgVersion
} else if strings.Contains(pkg, ">") {
pkgSplit := strings.SplitN(pkg, ">", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, ">", pkgVersion
} else if strings.Contains(pkg, "<=") {
pkgSplit := strings.SplitN(pkg, "<=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<=", pkgVersion
} else if strings.Contains(pkg, "<") {
pkgSplit := strings.SplitN(pkg, "<", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "<", pkgVersion
} else if strings.Contains(pkg, "=") {
pkgSplit := strings.SplitN(pkg, "=", 2)
pkgName := pkgSplit[0]
pkgVersion := pkgSplit[1]
return pkgName, "=", pkgVersion
}
return pkg, "", ""
type DeconstructedPackageString struct {
PkgName string
Description string
Flags map[string]string
RequiredVersionOperator string
RequiredVersion string
}
func EvaluateDependency(pkg, matchVersion string) bool {
_, comparisonSymbol, pkgVersion := SplitPkgNameAndVersion(pkg)
func DeconstructPackageString(pkg string) (ret DeconstructedPackageString, err error) {
// Initialize values
ret.Flags = make(map[string]string)
switch comparisonSymbol {
case ">=":
return CompareVersions(matchVersion, pkgVersion) >= 0
case ">":
return CompareVersions(matchVersion, pkgVersion) > 0
case "<=":
return CompareVersions(matchVersion, pkgVersion) <= 0
case "<":
return CompareVersions(matchVersion, pkgVersion) < 0
case "=":
if cutPkgVersion, ok := strings.CutSuffix(pkgVersion, "*"); ok {
return strings.HasPrefix(matchVersion, cutPkgVersion)
// 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 CompareVersions(matchVersion, pkgVersion) == 0
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
}
+11 -12
View File
@@ -2,26 +2,25 @@ package bpmlib
import (
"fmt"
"maps"
"slices"
"strings"
)
type PackageNotFoundErr struct {
packages []string
type PackageNotResolvedErr struct {
packages map[string]string
}
func (e PackageNotFoundErr) Error() string {
slices.Sort(e.packages)
return "The following packages were not found in any databases: " + strings.Join(e.packages, ", ")
}
func (e PackageNotResolvedErr) Error() (ret string) {
ret = "The following packages could not be resolved:"
type DependencyNotFoundErr struct {
dependencies []string
}
keys := slices.Collect(maps.Keys(e.packages))
slices.Sort(keys)
for _, key := range keys {
ret += "\n " + key + ": " + e.packages[key]
}
func (e DependencyNotFoundErr) Error() string {
slices.Sort(e.dependencies)
return "The following dependencies were not found in any databases: " + strings.Join(e.dependencies, ", ")
return ret
}
type PackageConflictErr struct {
+154 -128
View File
@@ -16,7 +16,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
// Setup operation struct
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
UnresolvedDepends: make(map[string]string, 0),
ModifiedFiles: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir,
@@ -27,20 +27,58 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
packages = removeDuplicates(packages)
// Resolve packages
pkgsNotFound := make([]string, 0)
unresolvedPackages := make(map[string]string)
resolvedVirtualPackages := make(map[string]string)
for _, pkg := range packages {
if stat, err := os.Stat(pkg); err == nil && !stat.IsDir() {
bpmpkg, err := ReadPackage(pkg)
d, err := DeconstructPackageString(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 {
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 {
if !reinstallPackages && IsPackageInstalled(splitPkg.Name, rootDir) && GetPackageInfo(splitPkg.Name, rootDir).GetFullVersion() == splitPkg.GetFullVersion() {
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
installationReason := forceInstallationReason
if installationReason == InstallationReasonUnknown {
@@ -51,9 +89,17 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
}
// 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,
InstallationReason: installationReason,
Flags: d.Flags,
BpmPackage: bpmpkg,
SplitPackageToInstall: splitPkg.Name,
})
@@ -61,9 +107,24 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
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
}
}
// Set package installation reason
installationReason := forceInstallationReason
@@ -75,40 +136,65 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
}
// 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,
InstallationReason: installationReason,
Flags: d.Flags,
BpmPackage: bpmpkg,
})
} else {
// Split package name and required version
pkgName, _, _ := SplitPkgNameAndVersion(pkg)
// Check resolved virtual packages
if resolvedPkg, ok := resolvedVirtualPackages[d.PkgName]; ok {
d.PkgName = resolvedPkg
}
var entry *BPMDatabaseEntry
entry := ResolveDatabaseEntry(d, rootDir)
if entry == nil {
unresolvedPackages[pkg] = "could not find in any database"
continue
}
if e, _, err := GetDatabaseEntry(pkgName); err == nil {
entry = e
} else if providers := GetVirtualPackageInfo(pkgName, rootDir); len(providers) > 0 {
entry, _, err = GetDatabaseEntry(providers[0].Name)
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 {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
}
} else if providers := GetDatabaseVirtualPackageEntry(pkgName); len(providers) > 0 {
entry = providers[0]
} else {
pkgsNotFound = append(pkgsNotFound, pkg)
continue
return nil, fmt.Errorf("could not combine flags for package (%s): %s", entry.Info.Name, err)
}
if !EvaluateDependency(pkg, entry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, pkg)
entry = ResolveDatabaseEntry(d, rootDir)
if entry == nil {
unresolvedPackages[pkg] = "could not find in any database"
continue
}
}
if !reinstallPackages && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
// Check if installation can be ignored
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
}
}
// Set package installation reason
installationReason := forceInstallationReason
@@ -120,25 +206,33 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
}
}
// 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,
Flags: d.Flags,
DatabaseEntry: entry,
})
}
}
// Return error if not all packages are found
if len(pkgsNotFound) != 0 {
return nil, PackageNotFoundErr{pkgsNotFound}
if len(unresolvedPackages) != 0 {
return nil, PackageNotResolvedErr{unresolvedPackages}
}
// Resolve dependencies
operation.ResolveDependencies(installRuntimeDependencies)
if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation {
return nil, DependencyNotFoundErr{operation.UnresolvedDepends}
return nil, PackageNotResolvedErr{operation.UnresolvedDepends}
} else if verbose {
log.Printf("Warning: %s", DependencyNotFoundErr{operation.UnresolvedDepends})
log.Printf("Warning: %s", PackageNotResolvedErr{operation.UnresolvedDepends})
}
}
@@ -188,7 +282,7 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason,
func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ...string) (operation *BPMOperation, err error) {
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
UnresolvedDepends: make(map[string]string),
ModifiedFiles: make(map[string]string),
RootDir: rootDir,
compiledPackages: make(map[string]string),
@@ -266,7 +360,7 @@ func RemovePackages(rootDir string, force, cleanupDependencies bool, packages ..
func CleanupPackages(cleanupMakeDepends bool, rootDir string) (operation *BPMOperation, err error) {
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
UnresolvedDepends: make(map[string]string),
ModifiedFiles: make(map[string]string),
RootDir: rootDir,
compiledPackages: make(map[string]string),
@@ -394,7 +488,7 @@ func UpdatePackages(rootDir string, syncDatabase, allowDowngrades, forceInstalla
operation = &BPMOperation{
Actions: make([]OperationAction, 0),
UnresolvedDepends: make([]string, 0),
UnresolvedDepends: make(map[string]string),
ModifiedFiles: make(map[string]string),
RunChecks: runChecks,
RootDir: rootDir,
@@ -402,125 +496,57 @@ func UpdatePackages(rootDir string, syncDatabase, allowDowngrades, forceInstalla
}
// Search for packages
pkgsNotFound := make([]string, 0)
resolvedVirtualPackages := make(map[string]string)
for _, pkg := range pkgs {
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, pkg) {
continue
}
// Get installed package built flags
flags := getPackageLocalInfo(pkg, rootDir).Flags
var entry *BPMDatabaseEntry
// Check if installed package can be replaced and install that instead
if e := FindReplacement(pkg); e != nil {
entry = e
} else if entry, _, err = GetDatabaseEntry(pkg); err != nil {
} else if entry = ResolveDatabaseEntry(DeconstructedPackageString{PkgName: pkg, Flags: flags}, rootDir); entry == nil {
continue
}
installedInfo := GetPackageInfo(pkg, rootDir)
if installedInfo == nil {
// Remove invalid flags
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)
} else {
comparison := CompareVersions(entry.Info.GetFullVersion(), installedInfo.GetFullVersion())
if (!allowDowngrades && comparison > 0) || (allowDowngrades && comparison != 0) {
// Resolve virtual packages
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: GetPackage(pkg, rootDir).LocalInfo.GetInstallationReason(),
InstallationReason: bpmpkg.LocalInfo.GetInstallationReason(),
Flags: flags,
DatabaseEntry: entry,
})
}
// Check for missing dependencies
for _, depend := range entry.Info.Depends {
// Split package name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
}
if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 {
continue
}
// Find database entry for missing dependency
dependEntry, _, err := GetDatabaseEntry(dependName)
if err != nil {
providers := GetDatabaseVirtualPackageEntry(dependName)
if len(providers) == 0 {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
dependEntry = providers[0]
}
// Skip dependency if action already exists
if ActionSliceIndex(operation.Actions, dependEntry.Info.Name) != -1 {
continue
}
// Skip dependency if ignored in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
// Fetch dependency
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: InstallationReasonDependency,
DatabaseEntry: dependEntry,
})
}
// Check for missing runtime dependencies
for _, depend := range entry.Info.RuntimeDepends {
// Split package name and required version
dependName, _, _ := SplitPkgNameAndVersion(depend)
if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) {
continue
}
if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 {
continue
}
// Find database entry for missing dependency
dependEntry, _, err := GetDatabaseEntry(dependName)
if err != nil {
providers := GetDatabaseVirtualPackageEntry(dependName)
if len(providers) == 0 {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
dependEntry = providers[0]
}
// Skip dependency if ignored in config
if rootDir == "/" && slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) {
continue
}
// Ensure entry has required version
if !EvaluateDependency(depend, dependEntry.Info.Version) {
pkgsNotFound = append(pkgsNotFound, depend)
continue
}
// Fetch dependency
operation.Actions = append(operation.Actions, &FetchPackageAction{
InstallationReason: InstallationReasonDependency,
DatabaseEntry: dependEntry,
})
}
}
}
// Return error if not all packages are found
if len(pkgsNotFound) != 0 {
return nil, PackageNotFoundErr{pkgsNotFound}
// Resolve dependencies
operation.ResolveDependencies(true)
if len(operation.UnresolvedDepends) != 0 {
if !forceInstallation {
return nil, PackageNotResolvedErr{operation.UnresolvedDepends}
} else if verbose {
log.Printf("Warning: %s", PackageNotResolvedErr{operation.UnresolvedDepends})
}
}
// Replace obsolete packages
+37 -10
View File
@@ -63,7 +63,7 @@ func InitializeLocalPackageInformation(rootDir string) (err error) {
}
// 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 {
return err
}
@@ -220,7 +220,7 @@ func getPackageFiles(pkg, rootDir string) []*PackageFileEntry {
var pkgFiles []*PackageFileEntry
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
pkgDir := path.Join(installedDir, pkg)
files := path.Join(pkgDir, "files")
files := path.Join(pkgDir, "files.txt")
if _, err := os.Stat(installedDir); os.IsNotExist(err) {
return nil
}
@@ -280,7 +280,7 @@ func getPackageLocalInfo(pkg, rootDir string) PackageLocalInfo {
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
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) {
return localInfo
@@ -304,7 +304,7 @@ func SetPackageLocalInfo(pkg string, localInfo PackageLocalInfo, rootDir string)
installedDir := path.Join(rootDir, "var/lib/bpm/installed/")
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 {
return err
}
@@ -332,10 +332,35 @@ func UpgradePersistentData(rootDir string) error {
for _, entry := range dirEntries {
pkgDir := path.Join(persistentDataDir, "installed", entry.Name())
// Generate default local package information file
if _, err := os.Stat(path.Join(pkgDir, "local")); err != nil && !os.IsNotExist(err) {
// Rename 'info' file to 'info.yml'
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
} 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())
out, err := yaml.Marshal(PackageLocalInfo{
@@ -347,10 +372,12 @@ func UpgradePersistentData(rootDir string) error {
return err
}
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644)
err = os.WriteFile(path.Join(pkgDir, "local.yml"), out, 0644)
if err != nil {
return err
}
} else if err != nil {
return err
}
// Move installation reason to local package information file
@@ -359,7 +386,7 @@ func UpgradePersistentData(rootDir string) error {
} else if err == nil {
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 {
return err
}
@@ -377,7 +404,7 @@ func UpgradePersistentData(rootDir string) error {
return err
}
err = os.WriteFile(path.Join(pkgDir, "local"), out, 0644)
err = os.WriteFile(path.Join(pkgDir, "local.yml"), out, 0644)
if err != nil {
return err
}
+92 -33
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"log"
"maps"
"os"
"path"
"slices"
@@ -13,7 +14,7 @@ import (
type BPMOperation struct {
Actions []OperationAction
UnresolvedDepends []string
UnresolvedDepends map[string]string
ModifiedFiles map[string]string
CompilationJobs int
RunChecks bool
@@ -91,26 +92,29 @@ func (operation *BPMOperation) ResolveDependencies(installRuntimeDepends bool) {
newActions := make([]OperationAction, 0)
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
var flags map[string]string
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
pkgInfo = action.BpmPackage.PkgInfo
flags = action.Flags
} else if value.GetActionType() == "fetch" {
action := value.(*FetchPackageAction)
pkgInfo = action.DatabaseEntry.Info
flags = action.Flags
} else {
continue
}
resolved, unresolved := ResolveDependencies(pkgInfo, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir)
resolved, unresolved := ResolveDependencies(pkgInfo, flags, resolvedVirtualPackages, installRuntimeDepends, operation.RootDir)
// Append unresolved dependencies
operation.UnresolvedDepends = append(operation.UnresolvedDepends, unresolved...)
operation.UnresolvedDepends = removeDuplicates(operation.UnresolvedDepends)
// Copy unresolved dependencies
maps.Copy(operation.UnresolvedDepends, unresolved)
for _, resolvedPkg := range resolved {
if ActionSliceIndex(newActions, resolvedPkg.DatabaseEntry.Info.Name) == -1 { // Dependency not in actions slice
var action OperationAction = &FetchPackageAction{
InstallationReason: resolvedPkg.InstallationReason,
Flags: resolvedPkg.Flags,
DatabaseEntry: resolvedPkg.DatabaseEntry,
}
newActions = append(newActions, action)
@@ -192,15 +196,18 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
// Loop through all dependencies
for _, depend := range depends {
// Remove required version
depend, _, _ = SplitPkgNameAndVersion(depend)
// Deconstruct package string
d, err := DeconstructPackageString(depend)
if err != nil {
return fmt.Errorf("could not deconstruct package string: %s", err)
}
// Resolve dependency
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]
} else {
dependPkgInfo = GetPackageInfo(depend, operation.RootDir)
dependPkgInfo = GetPackageInfo(d.PkgName, operation.RootDir)
}
if dependPkgInfo == nil {
continue
@@ -225,7 +232,7 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error {
if !slices.Contains(visited, pkg) {
bpmpkg := GetPackage(pkg, operation.RootDir)
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})
}
@@ -358,15 +365,30 @@ func (operation *BPMOperation) ShowOperationSummary() {
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
var flagsStr string
var installationReason = InstallationReasonUnknown
if value.GetActionType() == "install" {
installationReason = value.(*InstallPackageAction).InstallationReason
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 != "" {
pkgInfo = pkgInfo.GetSplitPackageInfo(value.(*InstallPackageAction).SplitPackageToInstall)
}
} else if value.GetActionType() == "fetch" {
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
} else {
pkgInfo = value.(*RemovePackageAction).BpmPackage.PkgInfo
@@ -388,15 +410,15 @@ func (operation *BPMOperation) ShowOperationSummary() {
installedInfo := GetPackageInfo(pkgInfo.Name, operation.RootDir)
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 {
comparison := CompareVersions(pkgInfo.GetFullVersion(), installedInfo.GetFullVersion())
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 {
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 {
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")
}
}
}
@@ -449,7 +471,7 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
optionalDepends = make(map[string][]string)
// Find all optional dependencies
for _, value := range slices.Clone(operation.Actions) {
for _, value := range operation.Actions {
var pkgInfo *PackageInfo
if value.GetActionType() == "install" {
action := value.(*InstallPackageAction)
@@ -462,32 +484,58 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st
}
for _, depend := range pkgInfo.OptionalDepends {
// Get optional dependency name
dependSplit := strings.SplitN(depend, ":", 2)
dependName, _, _ := SplitPkgNameAndVersion(dependSplit[0])
// Deconstruct package string
d, err := DeconstructPackageString(depend)
if err != nil {
continue
}
// Skip if dependency is already installed
if IsPackageInstalled(dependName, 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
}
// 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 {
// Remove optional dependency comment
n = strings.SplitN(n, ":", 2)[0]
// Deconstruct package string
d2, err := DeconstructPackageString(depend)
if err != nil {
return false
}
// Remove required version
n, _, _ = SplitPkgNameAndVersion(n)
return n == dependName
return d2.PkgName == d.PkgName
}) {
continue
}
if len(dependSplit) == 2 {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", dependSplit[0], dependSplit[1]))
if d.Description != "" {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", d.PkgName, d.Description))
} else {
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependName)
optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], d.PkgName)
}
}
}
@@ -624,6 +672,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
operation.Actions[i] = &InstallPackageAction{
File: fetchedPackages[entry.Filepath],
InstallationReason: action.(*FetchPackageAction).InstallationReason,
Flags: action.(*FetchPackageAction).Flags,
BpmPackage: bpmpkg,
SplitPackageToInstall: entry.Info.Name,
}
@@ -631,6 +680,7 @@ func (operation *BPMOperation) FetchPackages() (err error) {
operation.Actions[i] = &InstallPackageAction{
File: fetchedPackages[entry.Filepath],
InstallationReason: action.(*FetchPackageAction).InstallationReason,
Flags: action.(*FetchPackageAction).Flags,
BpmPackage: bpmpkg,
}
}
@@ -649,10 +699,16 @@ func (operation *BPMOperation) GetModifiedFiles() {
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 isUpgrade {
operation.ModifiedFiles[pkgFile.Path] = "upgrade"
}
}
}
@@ -705,6 +761,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
} else if action.GetActionType() == "install" {
value := action.(*InstallPackageAction)
fileToInstall := value.File
compilationFlags := value.Flags
bpmpkg := value.BpmPackage
var err error
@@ -729,7 +786,7 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
// Compile source package if not compiled already
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 {
return fmt.Errorf("could not compile source package (%s): %s\n", value.File, err)
}
@@ -749,9 +806,9 @@ func (operation *BPMOperation) Execute(verbose, force bool) (err error) {
}
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 {
err = installPackage(fileToInstall, value.InstallationReason, operation.RootDir, verbose, force)
err = installPackage(fileToInstall, value.InstallationReason, value.Flags, operation.RootDir, verbose, force)
}
if err != nil {
return fmt.Errorf("could not install package (%s): %s\n", bpmpkg.PkgInfo.Name, err)
@@ -769,6 +826,7 @@ type OperationAction interface {
type InstallPackageAction struct {
File string
InstallationReason InstallationReason
Flags map[string]string
SplitPackageToInstall string
BpmPackage *BPMPackage
}
@@ -779,6 +837,7 @@ func (action *InstallPackageAction) GetActionType() string {
type FetchPackageAction struct {
InstallationReason InstallationReason
Flags map[string]string
DatabaseEntry *BPMDatabaseEntry
}
+93 -10
View File
@@ -49,10 +49,27 @@ type PackageInfo struct {
Replaces []string `yaml:"replaces,omitempty"`
Provides []string `yaml:"provides,omitempty"`
Options []string `yaml:"options,omitempty"`
Flags []PackageFlag `yaml:"flags,omitempty"`
Downloads []PackageDownload `yaml:"downloads,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 {
Url string `yaml:"url"`
Type string `yaml:"type,omitempty"`
@@ -80,6 +97,7 @@ type PackageFileEntry struct {
type PackageLocalInfo struct {
InstallationReason string `yaml:"installation_reason"`
Flags map[string]string `yaml:"flags"`
InstalledOn int64 `yaml:"installed_on"`
LastUpdatedOn int64 `yaml:"last_updated_on"`
}
@@ -163,7 +181,7 @@ func GetPackageInfoRaw(filename string) (string, error) {
if err != nil {
return "", err
}
if header.Name == "pkg.info" {
if header.Name == "info.yml" {
bs, _ := io.ReadAll(tr)
err := file.Close()
if err != nil {
@@ -172,7 +190,7 @@ func GetPackageInfoRaw(filename string) (string, error) {
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) {
@@ -198,13 +216,13 @@ func ReadPackage(filename string) (*BPMPackage, error) {
if err != nil {
return nil, err
}
if header.Name == "pkg.info" {
if header.Name == "info.yml" {
bs, _ := io.ReadAll(tr)
pkgInfo, err = ReadPackageInfo(string(bs))
if err != nil {
return nil, err
}
} else if header.Name == "pkg.files" {
} else if header.Name == "files.txt" {
bs, _ := io.ReadAll(tr)
for _, line := range strings.Split(string(bs), "\n") {
if strings.TrimSpace(line) == "" {
@@ -212,7 +230,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
}
stringEntry := strings.Split(strings.TrimSpace(line), " ")
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)
if err != nil {
@@ -242,7 +260,7 @@ func ReadPackage(filename string) (*BPMPackage, error) {
}
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{
PkgInfo: pkgInfo,
@@ -595,6 +613,70 @@ func (pkgInfo *PackageInfo) CreateReadableInfo(rootDir string) string {
}
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
builderWriteDependencyArray("Dependencies", pkgInfo.Depends)
if pkgInfo.Type == "source" {
@@ -870,7 +952,7 @@ func extractPackage(bpmpkg *BPMPackage, verbose bool, filename, rootDir string)
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) {
return err
}
@@ -1034,12 +1116,12 @@ func installPackage(filename string, installationReason InstallationReason, root
return err
}
f, err := os.Create(path.Join(pkgDir, "files"))
f, err := os.Create(path.Join(pkgDir, "files.txt"))
if err != nil {
return err
}
tarballFile, err := readTarballFile(filename, "pkg.files")
tarballFile, err := readTarballFile(filename, "files.txt")
if err != nil {
return err
}
@@ -1050,7 +1132,7 @@ func installPackage(filename string, installationReason InstallationReason, root
return err
}
f, err = os.Create(path.Join(pkgDir, "info"))
f, err = os.Create(path.Join(pkgDir, "info.yml"))
if err != nil {
return err
}
@@ -1074,6 +1156,7 @@ func installPackage(filename string, installationReason InstallationReason, root
}
localInfo.LastUpdatedOn = time.Now().Unix()
localInfo.InstallationReason = string(installationReason)
localInfo.Flags = flags
SetPackageLocalInfo(bpmpkg.PkgInfo.Name, localInfo, rootDir)