ficsit-cli-flake/cli/dependency_resolver.go

188 lines
5.7 KiB
Go
Raw Normal View History

2022-04-14 01:27:39 +00:00
package cli
import (
"context"
2022-05-02 20:07:15 +00:00
"fmt"
"slices"
2022-04-14 01:27:39 +00:00
"github.com/Khan/genqlient/graphql"
"github.com/mircearoata/pubgrub-go/pubgrub"
"github.com/mircearoata/pubgrub-go/pubgrub/helpers"
"github.com/mircearoata/pubgrub-go/pubgrub/semver"
2022-04-14 01:27:39 +00:00
"github.com/pkg/errors"
2022-05-02 20:07:15 +00:00
"github.com/spf13/viper"
2022-10-14 16:11:16 +00:00
"github.com/satisfactorymodding/ficsit-cli/ficsit"
2022-04-14 01:27:39 +00:00
)
const smlDownloadTemplate = `https://github.com/satisfactorymodding/SatisfactoryModLoader/releases/download/v%s/SML.zip`
2022-05-02 20:07:15 +00:00
2022-04-14 01:27:39 +00:00
type DependencyResolver struct {
apiClient graphql.Client
}
func NewDependencyResolver(apiClient graphql.Client) DependencyResolver {
return DependencyResolver{apiClient: apiClient}
}
var (
rootPkg = "$$root$$"
smlPkg = "SML"
factoryGamePkg = "FactoryGame"
)
2022-05-02 20:07:15 +00:00
type ficsitAPISource struct {
apiClient graphql.Client
lockfile *LockFile
toInstall map[string]semver.Constraint
modVersionInfo map[string]ficsit.ModVersionsWithDependenciesResponse
gameVersion semver.Version
smlVersions []ficsit.SMLVersionsSmlVersionsGetSMLVersionsSml_versionsSMLVersion
}
func (f *ficsitAPISource) GetPackageVersions(pkg string) ([]pubgrub.PackageVersion, error) {
if pkg == rootPkg {
return []pubgrub.PackageVersion{{Version: semver.Version{}, Dependencies: f.toInstall}}, nil
2022-05-02 20:07:15 +00:00
}
if pkg == factoryGamePkg {
return []pubgrub.PackageVersion{{Version: f.gameVersion}}, nil
2022-05-02 20:07:15 +00:00
}
if pkg == smlPkg {
versions := make([]pubgrub.PackageVersion, len(f.smlVersions))
for i, smlVersion := range f.smlVersions {
v, err := semver.NewVersion(smlVersion.Version)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse version %s", smlVersion.Version)
}
gameConstraint, err := semver.NewConstraint(fmt.Sprintf(">=%d", smlVersion.Satisfactory_version))
if err != nil {
return nil, errors.Wrapf(err, "failed to parse constraint %s", fmt.Sprintf(">=%d", smlVersion.Satisfactory_version))
}
versions[i] = pubgrub.PackageVersion{
Version: v,
Dependencies: map[string]semver.Constraint{
factoryGamePkg: gameConstraint,
},
2022-05-02 20:07:15 +00:00
}
2022-04-14 01:27:39 +00:00
}
return versions, nil
}
response, err := ficsit.ModVersionsWithDependencies(context.TODO(), f.apiClient, pkg)
if err != nil {
return nil, errors.Wrapf(err, "failed to fetch mod %s", pkg)
}
if response.Mod.Id == "" {
return nil, errors.Errorf("mod %s not found", pkg)
}
f.modVersionInfo[pkg] = *response
versions := make([]pubgrub.PackageVersion, len(response.Mod.Versions))
for i, modVersion := range response.Mod.Versions {
v, err := semver.NewVersion(modVersion.Version)
2022-04-14 01:27:39 +00:00
if err != nil {
return nil, errors.Wrapf(err, "failed to parse version %s", modVersion.Version)
2022-04-14 01:27:39 +00:00
}
dependencies := make(map[string]semver.Constraint)
optionalDependencies := make(map[string]semver.Constraint)
for _, dependency := range modVersion.Dependencies {
c, err := semver.NewConstraint(dependency.Condition)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse constraint %s", dependency.Condition)
2022-04-14 01:27:39 +00:00
}
if dependency.Optional {
optionalDependencies[dependency.Mod_id] = c
} else {
dependencies[dependency.Mod_id] = c
2022-04-14 01:27:39 +00:00
}
}
versions[i] = pubgrub.PackageVersion{
Version: v,
Dependencies: dependencies,
OptionalDependencies: optionalDependencies,
2022-05-02 20:07:15 +00:00
}
}
return versions, nil
}
2022-05-02 20:07:15 +00:00
func (f *ficsitAPISource) PickVersion(pkg string, versions []semver.Version) semver.Version {
if f.lockfile != nil {
if existing, ok := (*f.lockfile)[pkg]; ok {
v, err := semver.NewVersion(existing.Version)
if err == nil {
if slices.ContainsFunc(versions, func(version semver.Version) bool {
return v.Compare(version) == 0
}) {
return v
}
}
2022-05-02 20:07:15 +00:00
}
}
return helpers.StandardVersionPriority(versions)
2022-05-02 20:07:15 +00:00
}
func (d DependencyResolver) ResolveModDependencies(constraints map[string]string, lockFile *LockFile, gameVersion int) (LockFile, error) {
smlVersionsDB, err := ficsit.SMLVersions(context.TODO(), d.apiClient)
if err != nil {
return nil, errors.Wrap(err, "failed fetching SML versions")
}
gameVersionSemver, err := semver.NewVersion(fmt.Sprintf("%d", gameVersion))
if err != nil {
return nil, errors.Wrap(err, "failed parsing game version")
}
toInstall := make(map[string]semver.Constraint, len(constraints))
for k, v := range constraints {
c, err := semver.NewConstraint(v)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse constraint %s", v)
}
toInstall[k] = c
}
2022-05-02 20:07:15 +00:00
ficsitSource := &ficsitAPISource{
apiClient: d.apiClient,
smlVersions: smlVersionsDB.SmlVersions.Sml_versions,
gameVersion: gameVersionSemver,
lockfile: lockFile,
toInstall: toInstall,
modVersionInfo: make(map[string]ficsit.ModVersionsWithDependenciesResponse),
}
2022-05-02 20:07:15 +00:00
result, err := pubgrub.Solve(helpers.NewCachingSource(ficsitSource), rootPkg)
if err != nil {
finalError := err
var solverErr pubgrub.SolvingError
if errors.As(err, &solverErr) {
finalError = DependencyResolverError{SolvingError: solverErr, apiClient: d.apiClient, smlVersions: smlVersionsDB.SmlVersions.Sml_versions, gameVersion: gameVersion}
2022-04-14 01:27:39 +00:00
}
return nil, errors.Wrap(finalError, "failed to solve dependencies")
2022-04-14 01:27:39 +00:00
}
delete(result, rootPkg)
delete(result, factoryGamePkg)
outputLock := make(LockFile, len(result))
for k, v := range result {
if k == smlPkg {
outputLock[k] = LockedMod{
Version: v.String(),
Hash: "",
Link: fmt.Sprintf(smlDownloadTemplate, v.String()),
}
continue
}
versions := ficsitSource.modVersionInfo[k].Mod.Versions
for _, ver := range versions {
if ver.Version == v.RawString() {
outputLock[k] = LockedMod{
Version: v.String(),
Hash: ver.Hash,
Link: viper.GetString("api-base") + ver.Link,
}
break
}
2022-05-02 20:07:15 +00:00
}
}
return outputLock, nil
2022-04-14 01:27:39 +00:00
}