Improved dependency resolution and improved the 'install' subcommand

This commit is contained in:
EnumDev 2024-09-08 11:49:27 +03:00
parent 2fd01a3fc2
commit c24b7c85e3
2 changed files with 119 additions and 103 deletions

156
main.go
View File

@ -35,6 +35,7 @@ var showInstalled = false
var pkgListNumbers = false
var pkgListNames = false
var reinstall = false
var reinstallAll = false
var noOptional = false
var nosync = true
@ -208,13 +209,13 @@ func resolveCommand() {
}]()
for _, pkg := range clone.Keys() {
value := clone.GetElement(pkg).Value
resolved, u, err := utils.ResolveAll(value.pkgInfo, false, !noOptional, !reinstall, rootDir)
if err != nil {
log.Fatalf("Could not resolve dependencies for package (%s). Error: %s\n", pkg, err)
}
unresolvedDepends = append(unresolvedDepends, u...)
resolved, unresolved := value.pkgInfo.ResolveAll(&[]string{}, &[]string{}, false, !noOptional, !reinstall, rootDir)
unresolvedDepends = append(unresolvedDepends, unresolved...)
for _, depend := range resolved {
if _, ok := pkgsToFetch.Get(depend); !ok && depend != value.pkgInfo.Name {
if !reinstallAll && utils.IsPackageInstalled(depend, rootDir) {
continue
}
entry, _, err := utils.GetRepositoryEntry(depend)
if err != nil {
log.Fatalf("Could not find package (%s) in any repository\n", pkg)
@ -228,38 +229,86 @@ func resolveCommand() {
pkgsToFetch.Set(pkg, value)
}
clone = pkgsToFetch.Copy()
pkgsToFetch = orderedmap.NewOrderedMap[string, *struct {
for _, pkg := range pkgsToInstall.Keys() {
value := clone.GetElement(pkg).Value
resolved, unresolved := value.pkgInfo.ResolveAll(&[]string{}, &[]string{}, false, !noOptional, !reinstall, rootDir)
unresolvedDepends = append(unresolvedDepends, unresolved...)
for _, depend := range resolved {
if _, ok := clone.Get(depend); !ok {
if !reinstallAll && utils.IsPackageInstalled(depend, rootDir) {
continue
}
entry, _, err := utils.GetRepositoryEntry(depend)
if err != nil {
log.Fatalf("Could not find package (%s) in any repository\n", pkg)
}
pkgsToFetch.Set(depend, &struct {
isDependency bool
pkgInfo *utils.PackageInfo
}]()
for _, pkg := range clone.Keys() {
value := clone.GetElement(pkg).Value
entry, _, err := utils.GetRepositoryEntry(pkg)
}{isDependency: true, pkgInfo: entry.Info})
}
}
pkgsToFetch.Set(pkg, value)
}
// Show summary
if len(unresolvedDepends) != 0 {
if force {
log.Fatalf("The following dependencies could not be found in any repositories: %s\n", strings.Join(unresolvedDepends, ", "))
} else {
log.Println("Warning: The following dependencies could not be found in any repositories: " + strings.Join(unresolvedDepends, ", "))
}
}
if pkgsToInstall.Len()+pkgsToFetch.Len() == 0 {
fmt.Println("All packages are up to date!")
os.Exit(0)
}
for _, pkg := range pkgsToInstall.Keys() {
pkgInfo, err := utils.ReadPackage(pkg)
if err != nil {
if err != nil {
log.Fatalf("Could not read package. Error: %s\n", err)
}
resolved, u, err := utils.ResolveAll(entry.Info, false, !noOptional, !reinstall, rootDir)
if err != nil {
log.Fatalf("Could not resolve dependencies for package (%s). Error: %s\n", pkg, err)
}
unresolvedDepends = append(unresolvedDepends, u...)
for _, depend := range resolved {
if _, ok := clone.Get(depend); !ok {
entry, _, err := utils.GetRepositoryEntry(depend)
if err != nil {
log.Fatalf("Could not find package (%s) in any repository\n", pkg)
}
pkgsToFetch.Set(depend, &struct {
isDependency bool
pkgInfo *utils.PackageInfo
}{isDependency: true, pkgInfo: entry.Info})
installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false)
if installedInfo == nil {
fmt.Printf("%s: %s (Install)\n", pkgInfo.Name, pkgInfo.Version)
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 {
fmt.Printf("%s: %s -> %s (Downgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version)
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 {
fmt.Printf("%s: %s -> %s (Upgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version)
} else {
fmt.Printf("%s: %s (Reinstall)\n", pkgInfo.Name, pkgInfo.Version)
}
}
pkgsToFetch.Set(pkg, value)
for _, pkg := range pkgsToFetch.Keys() {
pkgInfo := pkgsToFetch.GetElement(pkg).Value.pkgInfo
installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false)
if installedInfo == nil {
fmt.Printf("%s: %s (Install)\n", pkgInfo.Name, pkgInfo.Version)
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 {
fmt.Printf("%s: %s -> %s (Downgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version)
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 {
fmt.Printf("%s: %s -> %s (Upgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version)
} else {
fmt.Printf("%s: %s (Reinstall)\n", pkgInfo.Name, pkgInfo.Version)
}
}
if rootDir != "/" {
fmt.Println("Warning: Operating in " + rootDir)
}
if !yesAll {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Do you wish to install these packages? [y\\N] ")
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling...")
os.Exit(1)
}
}
// Fetch packages from repositories
fmt.Println("Fetching packages from available repositories...")
for _, pkg := range pkgsToFetch.Keys() {
isDependency, _ := pkgsToFetch.Get(pkg)
entry, repo, err := utils.GetRepositoryEntry(pkg)
@ -273,40 +322,16 @@ func resolveCommand() {
pkgsToInstall.Set(fetchedPackage, isDependency)
}
// Install Packages
if rootDir != "/" {
fmt.Println("Warning: Operating in " + rootDir)
}
if pkgsToInstall.Len() == 0 {
fmt.Println("All packages are up to date!")
os.Exit(0)
}
for _, pkg := range pkgsToInstall.Keys() {
pkgInfo := pkgsToInstall.GetElement(pkg).Value.pkgInfo
installedInfo := utils.GetPackageInfo(pkgInfo.Name, rootDir, false)
if installedInfo == nil {
fmt.Printf("%s: %s (Install)\n", pkgInfo.Name, pkgInfo.Version)
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) < 0 {
fmt.Printf("%s: %s -> %s (Downgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version)
} else if strings.Compare(pkgInfo.Version, installedInfo.Version) > 0 {
fmt.Printf("%s: %s -> %s (Upgrade)\n", pkgInfo.Name, installedInfo.Version, pkgInfo.Version)
} else {
fmt.Printf("%s: %s (Reinstall)\n", pkgInfo.Name, pkgInfo.Version)
}
}
if !yesAll {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Do you wish to install these packages? [y\\N] ")
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling...")
os.Exit(1)
}
}
for _, pkg := range pkgsToInstall.Keys() {
value, _ := pkgsToInstall.Get(pkg)
pkgInfo := value.pkgInfo
err := utils.InstallPackage(pkg, rootDir, verbose, force, buildSource, skipCheck, keepTempDir)
var err error
if value.isDependency {
err = utils.InstallPackage(pkg, rootDir, verbose, true, buildSource, skipCheck, keepTempDir)
} else {
err = utils.InstallPackage(pkg, rootDir, verbose, force, buildSource, skipCheck, keepTempDir)
}
if err != nil {
if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name)
@ -441,14 +466,14 @@ func printHelp() {
fmt.Println("-> flags will be read if passed right after the subcommand otherwise they will be read as subcommand arguments")
fmt.Println("\033[1m---- Command List ----\033[0m")
fmt.Println("-> bpm version | shows information on the installed version of bpm")
fmt.Println("-> bpm info [-R] | shows information on an installed package")
fmt.Println("-> bpm info [-R, -i] | shows information on an installed package")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -i shows information about the currently installed package")
fmt.Println("-> bpm list [-R, -c, -n] | lists all installed packages")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -c lists the amount of installed packages")
fmt.Println(" -n lists only the names of installed packages")
fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --no-optional] <files...> | installs the following files")
fmt.Println("-> bpm install [-R, -v, -y, -f, -o, -c, -b, -k, --reinstall, --reinstall-all, --no-optional] <packages...> | installs the following files")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
@ -458,15 +483,16 @@ func printHelp() {
fmt.Println(" -b creates a binary package from a source package after compilation and saves it in the binary package output directory")
fmt.Println(" -k keeps the compilation directory created by BPM after source package installation")
fmt.Println(" --reinstall Reinstalls packages even if they do not have a newer version available")
fmt.Println(" --reinstall-all Same as --reinstall but also reinstalls dependencies")
fmt.Println(" --no-optional Prevents installation of optional dependencies")
fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --nosync] | updates all packages that are available in the repositories")
fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --no-sync] | updates all packages that are available in the repositories")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" -f skips dependency, conflict and architecture checking")
fmt.Println(" --reinstall Fetches and reinstalls all packages even if they do not have a newer version available")
fmt.Println(" --nosync Skips package database syncing")
fmt.Println("-> bpm sync [-R, -v] | Syncs package databases without updating packages")
fmt.Println(" --no-sync Skips package database syncing")
fmt.Println("-> bpm sync [-R, -v, -y] | Syncs package databases without updating packages")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
@ -503,6 +529,7 @@ func resolveFlags() {
installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation")
installFlagSet.BoolVar(&force, "f", false, "Force installation by skipping architecture and dependency resolution")
installFlagSet.BoolVar(&reinstall, "reinstall", false, "Reinstalls packages even if they do not have a newer version available")
installFlagSet.BoolVar(&reinstallAll, "reinstall-all", false, "Same as --reinstall but also reinstalls dependencies")
installFlagSet.BoolVar(&noOptional, "no-optional", false, "Prevents installation of optional dependencies")
installFlagSet.Usage = printHelp
// Update flags
@ -512,7 +539,7 @@ func resolveFlags() {
updateFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
updateFlagSet.BoolVar(&force, "f", false, "Force update by skipping architecture and dependency resolution")
updateFlagSet.BoolVar(&reinstall, "reinstall", false, "Fetches and reinstalls all packages even if they do not have a newer version available")
updateFlagSet.BoolVar(&nosync, "nosync", false, "Skips package database syncing")
updateFlagSet.BoolVar(&nosync, "no-sync", false, "Skips package database syncing")
updateFlagSet.Usage = printHelp
// Sync flags
syncFlagSet := flag.NewFlagSet("Sync flags", flag.ExitOnError)
@ -578,5 +605,8 @@ func resolveFlags() {
}
subcommandArgs = fileFlagSet.Args()
}
if reinstallAll {
reinstall = true
}
}
}

View File

@ -1002,7 +1002,7 @@ func InstallPackage(filename, rootDir string, verbose, force, binaryPkgFromSrc,
if pkgInfo.Arch != "any" && pkgInfo.Arch != GetArch() {
return errors.New("cannot install a package with a different architecture")
}
if unresolved := CheckDependencies(pkgInfo, pkgInfo.Type == "source", true, rootDir); len(unresolved) != 0 {
if unresolved := pkgInfo.CheckDependencies(pkgInfo.Type == "source", true, rootDir); len(unresolved) != 0 {
return errors.New("Could not resolve all dependencies. Missing " + strings.Join(unresolved, ", "))
}
}
@ -1225,7 +1225,19 @@ func GetSourceScript(filename string) (string, error) {
return "", errors.New("package does not contain a source.sh file")
}
func CheckDependencies(pkgInfo *PackageInfo, checkMake, checkOptional bool, rootDir string) []string {
func (pkgInfo *PackageInfo) GetAllDependencies(checkMake, checkOptional bool) []string {
allDepends := make([]string, 0)
allDepends = append(allDepends, pkgInfo.Depends...)
if checkMake {
allDepends = append(allDepends, pkgInfo.MakeDepends...)
}
if checkOptional {
allDepends = append(allDepends, pkgInfo.OptionalDepends...)
}
return allDepends
}
func (pkgInfo *PackageInfo) CheckDependencies(checkMake, checkOptional bool, rootDir string) []string {
var ret []string
for _, dependency := range pkgInfo.Depends {
if !IsPackageProvided(dependency, rootDir) {
@ -1250,7 +1262,7 @@ func CheckDependencies(pkgInfo *PackageInfo, checkMake, checkOptional bool, root
return ret
}
func CheckConflicts(pkgInfo *PackageInfo, checkConditional bool, rootDir string) []string {
func (pkgInfo *PackageInfo) CheckConflicts(rootDir string) []string {
var ret []string
for _, conflict := range pkgInfo.Conflicts {
if IsPackageInstalled(conflict, rootDir) {
@ -1260,52 +1272,26 @@ func CheckConflicts(pkgInfo *PackageInfo, checkConditional bool, rootDir string)
return ret
}
func ResolveAll(pkgInfo *PackageInfo, checkMake, checkOptional, ignoreInstalled bool, rootDir string) ([]string, []string, error) {
resolved := make([]string, 0)
unresolved := make([]string, 0)
toResolve := make([]string, 0)
toResolve = append(toResolve, pkgInfo.Depends...)
if checkMake {
toResolve = append(toResolve, pkgInfo.MakeDepends...)
}
if checkOptional {
toResolve = append(toResolve, pkgInfo.OptionalDepends...)
}
resolve := func(depend string) {
if slices.Contains(resolved, depend) {
return
}
if ignoreInstalled && IsPackageInstalled(depend, rootDir) {
return
func (pkgInfo *PackageInfo) ResolveAll(resolved, unresolved *[]string, checkMake, checkOptional, ignoreInstalled bool, rootDir string) ([]string, []string) {
*unresolved = append(*unresolved, pkgInfo.Name)
for _, depend := range pkgInfo.GetAllDependencies(checkMake, checkOptional) {
if !slices.Contains(*resolved, depend) {
if slices.Contains(*unresolved, depend) || (ignoreInstalled && IsPackageInstalled(depend, rootDir)) {
continue
}
entry, _, err := GetRepositoryEntry(depend)
if err != nil {
if !slices.Contains(unresolved, depend) {
unresolved = append(unresolved, depend)
if !slices.Contains(*unresolved, depend) {
*unresolved = append(*unresolved, depend)
}
return
continue
}
resolved = append(resolved, depend)
toResolve = append(toResolve, entry.Info.Depends...)
if checkMake {
toResolve = append(toResolve, entry.Info.MakeDepends...)
}
if checkOptional {
toResolve = append(toResolve, entry.Info.OptionalDepends...)
entry.Info.ResolveAll(resolved, unresolved, checkMake, checkOptional, ignoreInstalled, rootDir)
}
}
for len(toResolve) > 0 {
clone := slices.Clone(toResolve)
toResolve = make([]string, 0)
for _, depend := range clone {
resolve(depend)
}
}
return resolved, unresolved, nil
*resolved = append(*resolved, pkgInfo.Name)
*unresolved = stringSliceRemove(*unresolved, pkgInfo.Name)
return *resolved, *unresolved
}
func IsPackageInstalled(pkg, rootDir string) bool {