From a4e20b7d88a28085c6a1b9e7dcb5ba70b105dc27 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Sat, 9 May 2026 19:06:20 +0300 Subject: [PATCH] Add dependency version requirement resolution --- src/bpmlib/databases.go | 17 ++++++- src/bpmlib/dependencies.go | 98 +++++++++++++++++++++++++++++++++++--- src/bpmlib/general.go | 72 +++++++++++++++++++--------- src/bpmlib/hooks.go | 3 +- src/bpmlib/operations.go | 17 +++++-- 5 files changed, 172 insertions(+), 35 deletions(-) diff --git a/src/bpmlib/databases.go b/src/bpmlib/databases.go index 0080a29..58853c8 100644 --- a/src/bpmlib/databases.go +++ b/src/bpmlib/databases.go @@ -311,6 +311,7 @@ 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 }) { dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name) @@ -319,6 +320,7 @@ 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 }) { dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name) @@ -329,6 +331,7 @@ 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 }) { dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name) @@ -337,6 +340,7 @@ 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 }) { dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], db.Name) @@ -370,7 +374,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 { - return strings.SplitN(n, ":", 2)[0] == entry.Info.Name + // Remove optional dependency comment + n = strings.SplitN(n, ":", 2)[0] + + // Remove required version + n, _, _ = SplitPkgNameAndVersion(n) + + return n == entry.Info.Name }) { dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name) } @@ -400,7 +410,10 @@ func (entry *BPMDatabaseEntry) GetEntryMakeDependants() (dependants []string) { dependantsMap := make(map[string][]string) for _, db := range BPMDatabases { for _, e := range db.Entries { - if slices.Contains(e.Info.MakeDepends, entry.Info.Name) { + if slices.ContainsFunc(e.Info.MakeDepends, func(n string) bool { + n, _, _ = SplitPkgNameAndVersion(n) + return n == entry.Info.Name + }) { dependantsMap[e.Info.Name] = append(dependantsMap[e.Info.Name], e.Database.Name) } } diff --git a/src/bpmlib/dependencies.go b/src/bpmlib/dependencies.go index edca714..5807dae 100644 --- a/src/bpmlib/dependencies.go +++ b/src/bpmlib/dependencies.go @@ -21,6 +21,7 @@ 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 }) { dependants = append(dependants, installedPkg.Name) @@ -29,6 +30,7 @@ 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 }) { dependants = append(dependants, installedPkg.Name) @@ -43,6 +45,7 @@ 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 }) { dependants = append(dependants, installedPkg.Name) @@ -51,6 +54,7 @@ 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 }) { dependants = append(dependants, installedPkg.Name) @@ -78,7 +82,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend // Add installed package to list if its optional dependencies include pkgName if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool { - return strings.SplitN(n, ":", 2)[0] == pkgInfo.Name + // Remove optional dependency comment + n = strings.SplitN(n, ":", 2)[0] + + // Remove required version + n, _, _ = SplitPkgNameAndVersion(n) + + return n == pkgInfo.Name }) { dependants = append(dependants, installedPkg.Name) continue @@ -88,7 +98,13 @@ func (pkgInfo *PackageInfo) GetPackageOptionalDependants(rootDir string) (depend for _, vpkg := range pkgInfo.Provides { // Add installed package to list if its optional dependencies contain a provided virtual package if slices.ContainsFunc(installedPkg.OptionalDepends, func(n string) bool { - return strings.SplitN(n, ":", 2)[0] == vpkg + // Remove optional dependency comment + n = strings.SplitN(n, ":", 2)[0] + + // Remove required version + n, _, _ = SplitPkgNameAndVersion(n) + + return n == vpkg }) { dependants = append(dependants, installedPkg.Name) break @@ -111,20 +127,23 @@ func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[strin dfs = func(pkgInfo *PackageInfo) { checkDependencies := func(dependencies []string, installationReason InstallationReason) { for _, depend := range dependencies { + // Split dependency name and required version + dependName, _, _ := SplitPkgNameAndVersion(depend) + // Ignore if package is already installed - if IsPackageInstalled(depend, rootDir) { + if IsPackageInstalled(dependName, rootDir) && EvaluateDependency(depend, GetPackageInfo(dependName, rootDir).Version) { continue - } else if providers := GetVirtualPackageInfo(depend, rootDir); len(providers) > 0 { + } else if providers := GetVirtualPackageInfo(dependName, rootDir); len(providers) > 0 { continue } // Find database entry for dependency var dependEntry *BPMDatabaseEntry - if resolvedVpkg, ok := resolvedVirtualPackages[depend]; ok { + if resolvedVpkg, ok := resolvedVirtualPackages[dependName]; ok { dependEntry, _, _ = GetDatabaseEntry(resolvedVpkg) - } else if entry, _, _ := GetDatabaseEntry(depend); entry != nil { + } else if entry, _, _ := GetDatabaseEntry(dependName); entry != nil { dependEntry = entry - } else if providers := GetDatabaseVirtualPackageEntry(depend); len(providers) > 0 { + } else if providers := GetDatabaseVirtualPackageEntry(dependName); len(providers) > 0 { dependEntry = providers[0] } @@ -133,6 +152,12 @@ func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[strin continue } + // Ensure entry has required version + if !EvaluateDependency(depend, dependEntry.Info.Version) { + unresolved = append(unresolved, depend) + continue + } + // Skip ignored packages in config if slices.Contains(MainBPMConfig.IgnorePackages, dependEntry.Info.Name) { continue @@ -161,3 +186,62 @@ func ResolveDependencies(pkgInfo *PackageInfo, resolvedVirtualPackages map[strin return resolved, unresolved } + +func SplitPkgNameAndVersion(pkg string) (string, string, string) { + if strings.Contains(pkg, ">=") { + pkgSplit := strings.SplitN(pkg, ">=", 2) + pkgName := pkgSplit[0] + pkgVersion := pkgSplit[1] + + return pkgName, ">=", pkgVersion + } else if strings.Contains(pkg, ">") { + pkgSplit := strings.SplitN(pkg, ">", 2) + pkgName := pkgSplit[0] + pkgVersion := pkgSplit[1] + + return pkgName, ">", pkgVersion + } else if strings.Contains(pkg, "<=") { + pkgSplit := strings.SplitN(pkg, "<=", 2) + pkgName := pkgSplit[0] + pkgVersion := pkgSplit[1] + + return pkgName, "<=", pkgVersion + } else if strings.Contains(pkg, "<") { + pkgSplit := strings.SplitN(pkg, "<", 2) + pkgName := pkgSplit[0] + pkgVersion := pkgSplit[1] + + return pkgName, "<", pkgVersion + } else if strings.Contains(pkg, "=") { + pkgSplit := strings.SplitN(pkg, "=", 2) + pkgName := pkgSplit[0] + pkgVersion := pkgSplit[1] + + return pkgName, "=", pkgVersion + } + + return pkg, "", "" +} + +func EvaluateDependency(pkg, matchVersion string) bool { + _, comparisonSymbol, pkgVersion := SplitPkgNameAndVersion(pkg) + + switch comparisonSymbol { + case ">=": + return CompareVersions(matchVersion, pkgVersion) >= 0 + case ">": + return CompareVersions(matchVersion, pkgVersion) > 0 + case "<=": + return CompareVersions(matchVersion, pkgVersion) <= 0 + case "<": + return CompareVersions(matchVersion, pkgVersion) < 0 + case "=": + if cutPkgVersion, ok := strings.CutSuffix(pkgVersion, "*"); ok { + return strings.HasPrefix(matchVersion, cutPkgVersion) + } else { + return CompareVersions(matchVersion, pkgVersion) == 0 + } + default: + return true + } +} diff --git a/src/bpmlib/general.go b/src/bpmlib/general.go index dcbf948..07a8be3 100644 --- a/src/bpmlib/general.go +++ b/src/bpmlib/general.go @@ -81,22 +81,31 @@ func InstallPackages(rootDir string, forceInstallationReason InstallationReason, BpmPackage: bpmpkg, }) } else { + // Split package name and required version + pkgName, _, _ := SplitPkgNameAndVersion(pkg) + var entry *BPMDatabaseEntry - if e, _, err := GetDatabaseEntry(pkg); err == nil { + if e, _, err := GetDatabaseEntry(pkgName); err == nil { entry = e - } else if providers := GetVirtualPackageInfo(pkg, rootDir); len(providers) > 0 { + } else if providers := GetVirtualPackageInfo(pkgName, rootDir); len(providers) > 0 { entry, _, err = GetDatabaseEntry(providers[0].Name) if err != nil { pkgsNotFound = append(pkgsNotFound, pkg) continue } - } else if providers := GetDatabaseVirtualPackageEntry(pkg); len(providers) > 0 { + } else if providers := GetDatabaseVirtualPackageEntry(pkgName); len(providers) > 0 { entry = providers[0] } else { pkgsNotFound = append(pkgsNotFound, pkg) continue } + + if !EvaluateDependency(pkg, entry.Info.Version) { + pkgsNotFound = append(pkgsNotFound, pkg) + continue + } + if !reinstallPackages && IsPackageInstalled(entry.Info.Name, rootDir) && GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() { continue } @@ -419,72 +428,91 @@ func UpdatePackages(rootDir string, syncDatabase, allowDowngrades, forceInstalla } // Check for missing dependencies - for _, dep := range entry.Info.Depends { - if IsPackageInstalled(dep, rootDir) { + 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(dep, rootDir)) > 0 { + if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 { continue } // Find database entry for missing dependency - depEntry, _, err := GetDatabaseEntry(dep) + dependEntry, _, err := GetDatabaseEntry(dependName) if err != nil { - providers := GetDatabaseVirtualPackageEntry(dep) + providers := GetDatabaseVirtualPackageEntry(dependName) if len(providers) == 0 { - pkgsNotFound = append(pkgsNotFound, dep) + pkgsNotFound = append(pkgsNotFound, depend) continue } - depEntry = providers[0] + dependEntry = providers[0] } // Skip dependency if action already exists - if operation.ActionsContainPackage(depEntry.Info.Name) { + if operation.ActionsContainPackage(dependEntry.Info.Name) { continue } // Skip dependency if ignored in config - if slices.Contains(MainBPMConfig.IgnorePackages, depEntry.Info.Name) { + if 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.AppendAction(&FetchPackageAction{ InstallationReason: InstallationReasonDependency, - DatabaseEntry: depEntry, + DatabaseEntry: dependEntry, }) } + // Check for missing runtime dependencies - for _, dep := range entry.Info.RuntimeDepends { - if IsPackageInstalled(dep, rootDir) { + 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(dep, rootDir)) > 0 { + if len(GetVirtualPackageInfo(dependName, rootDir)) > 0 { continue } // Find database entry for missing dependency - depEntry, _, err := GetDatabaseEntry(dep) + dependEntry, _, err := GetDatabaseEntry(dependName) if err != nil { - providers := GetDatabaseVirtualPackageEntry(dep) + providers := GetDatabaseVirtualPackageEntry(dependName) if len(providers) == 0 { - pkgsNotFound = append(pkgsNotFound, dep) + pkgsNotFound = append(pkgsNotFound, depend) continue } - depEntry = providers[0] + dependEntry = providers[0] } // Skip dependency if ignored in config - if slices.Contains(MainBPMConfig.IgnorePackages, depEntry.Info.Name) { + if 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.AppendAction(&FetchPackageAction{ InstallationReason: InstallationReasonDependency, - DatabaseEntry: depEntry, + DatabaseEntry: dependEntry, }) } } diff --git a/src/bpmlib/hooks.go b/src/bpmlib/hooks.go index d6e6d23..9d60136 100644 --- a/src/bpmlib/hooks.go +++ b/src/bpmlib/hooks.go @@ -3,7 +3,6 @@ package bpmlib import ( "errors" "fmt" - "gopkg.in/yaml.v3" "os" "os/exec" "path" @@ -11,6 +10,8 @@ import ( "slices" "strings" "syscall" + + "gopkg.in/yaml.v3" ) type BPMHook struct { diff --git a/src/bpmlib/operations.go b/src/bpmlib/operations.go index 65565c2..7df342d 100644 --- a/src/bpmlib/operations.go +++ b/src/bpmlib/operations.go @@ -250,6 +250,9 @@ func (operation *BPMOperation) Cleanup(cleanupMakeDepends bool) error { // Loop through all dependencies for _, depend := range depends { + // Remove required version + depend, _, _ = SplitPkgNameAndVersion(depend) + // Resolve dependency var dependPkgInfo *PackageInfo if providers := GetVirtualPackageInfo(depend, operation.RootDir); len(providers) > 0 { @@ -516,16 +519,24 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st } for _, depend := range pkgInfo.OptionalDepends { + // Get optional dependency name dependSplit := strings.SplitN(depend, ":", 2) + dependName, _, _ := SplitPkgNameAndVersion(dependSplit[0]) // Skip if dependency is already installed - if IsPackageInstalled(dependSplit[0], operation.RootDir) { + if IsPackageInstalled(dependName, operation.RootDir) { continue } // Skip if not a new dependency of the package if installedPkg := GetPackage(pkgInfo.Name, operation.RootDir); installedPkg != nil && slices.ContainsFunc(installedPkg.PkgInfo.OptionalDepends, func(n string) bool { - return strings.SplitN(n, ":", 2)[0] == dependSplit[0] + // Remove optional dependency comment + n = strings.SplitN(n, ":", 2)[0] + + // Remove required version + n, _, _ = SplitPkgNameAndVersion(n) + + return n == dependName }) { continue } @@ -533,7 +544,7 @@ func (operation *BPMOperation) GetOptionalDependencies() (optionalDepends map[st if len(dependSplit) == 2 { optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], fmt.Sprintf("%s (%s)", dependSplit[0], dependSplit[1])) } else { - optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependSplit[0]) + optionalDepends[pkgInfo.Name] = append(optionalDepends[pkgInfo.Name], dependName) } } }