2022-04-14 01:27:39 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-05-02 20:07:15 +00:00
|
|
|
"fmt"
|
2023-12-06 03:01:49 +00:00
|
|
|
"slices"
|
2022-04-14 01:27:39 +00:00
|
|
|
|
|
|
|
"github.com/Khan/genqlient/graphql"
|
2023-12-06 03:01:49 +00:00
|
|
|
"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
|
|
|
)
|
|
|
|
|
2023-12-06 03:01:49 +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}
|
|
|
|
}
|
|
|
|
|
2023-12-06 03:01:49 +00:00
|
|
|
var (
|
|
|
|
rootPkg = "$$root$$"
|
|
|
|
smlPkg = "SML"
|
|
|
|
factoryGamePkg = "FactoryGame"
|
|
|
|
)
|
2022-05-02 20:07:15 +00:00
|
|
|
|
2023-12-06 03:01:49 +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
|
|
|
|
}
|
2022-06-03 22:17:02 +00:00
|
|
|
|
2023-12-06 03:01:49 +00:00
|
|
|
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
|
|
|
}
|
2023-12-06 03:01:49 +00:00
|
|
|
if pkg == factoryGamePkg {
|
|
|
|
return []pubgrub.PackageVersion{{Version: f.gameVersion}}, nil
|
2022-05-02 20:07:15 +00:00
|
|
|
}
|
2023-12-06 03:01:49 +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
|
|
|
}
|
2023-12-06 03:01:49 +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 {
|
2023-12-06 03:01:49 +00:00
|
|
|
return nil, errors.Wrapf(err, "failed to parse version %s", modVersion.Version)
|
2022-04-14 01:27:39 +00:00
|
|
|
}
|
2023-12-06 03:01:49 +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
|
|
|
}
|
2023-12-06 03:01:49 +00:00
|
|
|
if dependency.Optional {
|
|
|
|
optionalDependencies[dependency.Mod_id] = c
|
|
|
|
} else {
|
|
|
|
dependencies[dependency.Mod_id] = c
|
2022-04-14 01:27:39 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-06 03:01:49 +00:00
|
|
|
versions[i] = pubgrub.PackageVersion{
|
|
|
|
Version: v,
|
|
|
|
Dependencies: dependencies,
|
|
|
|
OptionalDependencies: optionalDependencies,
|
2022-05-02 20:07:15 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-06 03:01:49 +00:00
|
|
|
return versions, nil
|
|
|
|
}
|
2022-05-02 20:07:15 +00:00
|
|
|
|
2023-12-06 03:01:49 +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
|
|
|
}
|
|
|
|
}
|
2023-12-06 03:01:49 +00:00
|
|
|
return helpers.StandardVersionPriority(versions)
|
2022-05-02 20:07:15 +00:00
|
|
|
}
|
|
|
|
|
2023-12-06 03:01:49 +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")
|
|
|
|
}
|
2022-06-03 22:17:02 +00:00
|
|
|
|
2023-12-06 03:01:49 +00:00
|
|
|
gameVersionSemver, err := semver.NewVersion(fmt.Sprintf("%d", gameVersion))
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed parsing game version")
|
|
|
|
}
|
2022-06-03 22:17:02 +00:00
|
|
|
|
2023-12-06 03:01:49 +00:00
|
|
|
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
|
|
|
|
2023-12-06 03:01:49 +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
|
|
|
|
2023-12-06 03:01:49 +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
|
|
|
}
|
2023-12-06 03:01:49 +00:00
|
|
|
return nil, errors.Wrap(finalError, "failed to solve dependencies")
|
2022-04-14 01:27:39 +00:00
|
|
|
}
|
2023-12-06 03:01:49 +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
|
|
|
}
|
|
|
|
}
|
2023-12-06 03:01:49 +00:00
|
|
|
|
|
|
|
return outputLock, nil
|
2022-04-14 01:27:39 +00:00
|
|
|
}
|