Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor relationship resolver to capture full resolved tree #4232

Merged
merged 5 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cmdline/Action/Install.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
user.RaiseMessage("");
done = true;
}
catch (DependencyNotSatisfiedKraken ex)
catch (DependenciesNotSatisfiedKraken ex)
{
user.RaiseError("{0}", ex.Message);
user.RaiseMessage(Properties.Resources.InstallTryAgain);
Expand Down
5 changes: 2 additions & 3 deletions Cmdline/Action/Replace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,9 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
regMgr);
user.RaiseMessage("");
}
catch (DependencyNotSatisfiedKraken ex)
catch (DependenciesNotSatisfiedKraken ex)
{
user.RaiseMessage(Properties.Resources.ReplaceDependencyNotSatisfied,
ex.parent, ex.module, ex.version ?? "", instance.game.ShortName);
user.RaiseMessage("{0}", ex.Message);
}
}
else
Expand Down
1 change: 0 additions & 1 deletion Cmdline/Properties/Resources.fr-FR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,6 @@ Soyez sûr d'avoir saisi au moins les valeurs de version majeure et mineure au f
</data>
<data name="InstallTryAgain" xml:space="preserve">
<value>Si vous êtes chanceux, vous pouvez taper `ckan update` et réessayer.
Essayez `ckan install --no-recommends` pour ignorer l'installation des modules recommandés.
Ou `ckan install --allow-incompatible` pour ignorer la compatibilité des modules.</value>
</data>
<data name="InstallUnversionedDependencyNotSatisfied" xml:space="preserve">
Expand Down
1 change: 0 additions & 1 deletion Cmdline/Properties/Resources.it-IT.resx
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,6 @@ Assicurati di inserire almeno i valori di versione maggiore e minore nella forma
</data>
<data name="InstallTryAgain" xml:space="preserve">
<value>Se sei fortunato, puoi fare un `ckan update` e riprovare.
Prova `ckan install --no-recommends` per saltare l'installazione dei moduli consigliati.
Oppure `ckan install --allow-incompatible` per ignorare la compatibilità dei moduli.</value>
</data>
<data name="InstallUnversionedDependencyNotSatisfied" xml:space="preserve">
Expand Down
1 change: 0 additions & 1 deletion Cmdline/Properties/Resources.pl-PL.resx
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,6 @@ Upewnij się, że wprowadziłeś wersję w postaci Maj.Min - np. 1.5</value>
</data>
<data name="InstallTryAgain" xml:space="preserve">
<value>Jeśli masz szczęście, możesz wykonać `ckan update` i spróbować ponownie.
Spróbuj `ckan install --no-recommends` aby pominąć instalację zalecanych modułów.
Lub `ckan install --allow-niecompatible` aby zignorować kompatybilność modułów.</value>
</data>
<data name="InstallUnversionedDependencyNotSatisfied" xml:space="preserve">
Expand Down
7 changes: 4 additions & 3 deletions Cmdline/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,10 @@ Make sure to enter at least the version major and minor values in the form Maj.M
<data name="ImportError" xml:space="preserve"><value>Import error: {0}</value></data>
<data name="ImportNotFound" xml:space="preserve"><value>File not found: {0}</value></data>
<data name="InstallNotFound" xml:space="preserve"><value>File not found, exiting: {0}</value></data>
<data name="InstallTryAgain" xml:space="preserve"><value>If you're lucky, you can do a `ckan update` and try again.
Try `ckan install --no-recommends` to skip installation of recommended modules.
Or `ckan install --allow-incompatible` to ignore module compatibility.</value></data>
<data name="InstallTryAgain" xml:space="preserve"><value>
You can use the `ckan compat` commands to change which game versions are treated as compatible.
If a newly compatible version was just released, you can do a `ckan update` and try again.
Or `ckan install --allow-incompatible` to ignore compatibility of the modules on the command line (but not dependencies).</value></data>
<data name="InstallUnversionedDependencyNotSatisfied" xml:space="preserve"><value>Module {0} required but it is not listed in the index, or not available for your version of {1}</value></data>
<data name="InstallVersionedDependencyNotSatisfied" xml:space="preserve"><value>Module {0} {1} required but it is not listed in the index, or not available for your version of {2}</value></data>
<data name="InstallBadMetadata" xml:space="preserve"><value>Bad metadata detected for module {0}: {1}</value></data>
Expand Down
1 change: 0 additions & 1 deletion Cmdline/Properties/Resources.ru-RU.resx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@
<data name="ImportNotFound" xml:space="preserve"><value>Файл не найден: {0}</value></data>
<data name="InstallNotFound" xml:space="preserve"><value>Файл не найден, выход: {0}</value></data>
<data name="InstallTryAgain" xml:space="preserve"><value>Попробуйте совершить `ckan update` и попытайтесь заново.
Используйте `ckan install --no-recommends`, чтобы пропустить установку рекомендуемых модулей.
Либо используйте `ckan install --allow-incompatible`, чтобы игнорировать проблемы совместимости.</value></data>
<data name="InstallUnversionedDependencyNotSatisfied" xml:space="preserve"><value>Необходим модуль {0}, но он отсутствует в указателе либо недоступен для вашей версии {1}</value></data>
<data name="InstallVersionedDependencyNotSatisfied" xml:space="preserve"><value>Необходим модуль {0} {1}, но он отсутствует в указателе либо недоступен для вашей версии {2}</value></data>
Expand Down
16 changes: 9 additions & 7 deletions ConsoleUI/DependencyScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,13 @@ public DependencyScreen(ConsoleTheme theme,

private void generateList(HashSet<CkanModule> inst)
{
if (ModuleInstaller.FindRecommendations(
manager.CurrentInstance,
inst, new List<CkanModule>(inst), registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters
if (manager.CurrentInstance is GameInstance instance
&& ModuleInstaller.FindRecommendations(
instance,
inst, new List<CkanModule>(inst), registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters
)) {
foreach ((CkanModule mod, Tuple<bool, List<string>> checkedAndDependents) in recommendations) {
dependencies.Add(mod, new Dependency(
Expand Down Expand Up @@ -215,11 +216,12 @@ private bool HasConflicts(IEnumerable<CkanModule> toAdd,
plan.Remove.Select(ident => registry.InstalledModule(ident)?.Module)
.OfType<CkanModule>(),
RelationshipResolverOptions.ConflictsOpts(), registry,
manager.CurrentInstance.game,
manager.CurrentInstance.VersionCriteria());
descriptions = resolver.ConflictDescriptions.ToList();
return descriptions.Count > 0;
}
catch (DependencyNotSatisfiedKraken k)
catch (DependenciesNotSatisfiedKraken k)
{
descriptions = new List<string>() { k.Message };
return true;
Expand Down
4 changes: 2 additions & 2 deletions ConsoleUI/InstallScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ public override void Run(Action? process = null)

} catch (BadMetadataKraken ex) {
RaiseError(Properties.Resources.InstallBadMetadata, ex.module?.ToString() ?? "", ex.Message);
} catch (DependencyNotSatisfiedKraken ex) {
RaiseError(Properties.Resources.InstallUnsatisfiedDependency, ex.parent, ex.module, ex.Message);
} catch (DependenciesNotSatisfiedKraken ex) {
RaiseError("{0}", ex.Message);
} catch (ModuleNotFoundKraken ex) {
RaiseError(Properties.Resources.InstallModuleNotFound, ex.module, ex.Message);
} catch (ModNotInstalledKraken ex) {
Expand Down
1 change: 1 addition & 0 deletions Core/CKAN-core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="all" />
<PackageReference Include="IndexRange" Version="1.0.3" />
<PackageReference Include="StringSyntaxAttribute" Version="1.0.0" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\_build\meta\GlobalAssemblyVersionInfo.cs">
Expand Down
14 changes: 14 additions & 0 deletions Core/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ public static IEnumerable<Match> WithMatches(this IEnumerable<string> source, Re
=> source.Select(item => Utilities.DefaultIfThrows(() => func(item),
exc => onThrow(item, exc)));

/// <summary>
/// Get a hash code for a sequence with a variable number of elements
/// </summary>
/// <typeparam name="T">Type of the elements in the sequence</typeparam>
/// <param name="source">The sequence</param>
/// <returns></returns>
public static int ToSequenceHashCode<T>(this IEnumerable<T> source)
=> source.Aggregate(new HashCode(),
(hc, item) =>
{
hc.Add(item);
return hc;
},
hc => hc.ToHashCode());
}

/// <summary>
Expand Down
26 changes: 16 additions & 10 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public void InstallList(ICollection<CkanModule> modules,
}
var resolver = new RelationshipResolver(modules, null, options,
registry_manager.registry,
instance.game,
instance.VersionCriteria());
var modsToInstall = resolver.ModList().ToList();
var downloads = new List<CkanModule>();
Expand Down Expand Up @@ -774,6 +775,7 @@ public void UninstallList(IEnumerable<string> mods,
.Where(im => !revdep.Contains(im.identifier))
.Concat(installing?.Select(m => new InstalledModule(null, m, Array.Empty<string>(), false)) ?? Array.Empty<InstalledModule>())
.ToList(),
instance.game,
instance.VersionCriteria())
.Select(im => im.identifier))
.ToList();
Expand Down Expand Up @@ -1185,6 +1187,7 @@ public void Upgrade(ICollection<CkanModule> modules,
.OfType<CkanModule>(),
RelationshipResolverOptions.DependsOnlyOpts(),
registry,
instance.game,
instance.VersionCriteria());
modules = resolver.ModList().ToArray();
autoInstalled = modules.ToDictionary(m => m, resolver.IsAutoInstalled);
Expand Down Expand Up @@ -1285,6 +1288,7 @@ public void Upgrade(ICollection<CkanModule> modules,
.Where(im => !removingIdents.Contains(im.identifier))
.Concat(modules.Select(m => new InstalledModule(null, m, Array.Empty<string>(), false)))
.ToList(),
instance.game,
instance.VersionCriteria())
.ToList();
if (autoRemoving.Count > 0)
Expand Down Expand Up @@ -1318,7 +1322,7 @@ public void Upgrade(ICollection<CkanModule> modules,
/// Enacts listed Module Replacements to the specified versions for the user's KSP.
/// Will *re-install* or *downgrade* (with a warning) as well as upgrade.
/// </summary>
/// <exception cref="DependencyNotSatisfiedKraken">Thrown if a dependency for a replacing module couldn't be satisfied.</exception>
/// <exception cref="DependenciesNotSatisfiedKraken">Thrown if a dependency for a replacing module couldn't be satisfied.</exception>
/// <exception cref="ModuleNotFoundKraken">Thrown if a module that should be replaced is not installed.</exception>
public void Replace(IEnumerable<ModuleReplacement> replacements,
RelationshipResolverOptions options,
Expand Down Expand Up @@ -1401,7 +1405,8 @@ public void Replace(IEnumerable<ModuleReplacement> replacements,
}
}
}
var resolver = new RelationshipResolver(modsToInstall, null, options, registry_manager.registry, instance.VersionCriteria());
var resolver = new RelationshipResolver(modsToInstall, null, options, registry_manager.registry,
instance.game, instance.VersionCriteria());
var resolvedModsToInstall = resolver.ModList().ToList();
AddRemove(ref possibleConfigOnlyDirs,
registry_manager,
Expand Down Expand Up @@ -1433,19 +1438,19 @@ public static IEnumerable<string> PrioritizedHosts(IEnumerable<Uri>? urls)
/// <returns>
/// true if anything found, false otherwise
/// </returns>
public static bool FindRecommendations(GameInstance? instance,
public static bool FindRecommendations(GameInstance instance,
HashSet<CkanModule> sourceModules,
List<CkanModule> toInstall,
Registry registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters)
{
var crit = instance?.VersionCriteria();
var crit = instance.VersionCriteria();
var resolver = new RelationshipResolver(sourceModules.Where(m => !m.IsDLC),
null,
RelationshipResolverOptions.KitchenSinkOpts(),
registry, crit);
registry, instance.game, crit);
var recommenders = resolver.Dependencies().ToHashSet();

var checkedRecs = resolver.Recommendations(recommenders)
Expand All @@ -1454,7 +1459,7 @@ public static bool FindRecommendations(GameInstance?
.ToHashSet();
var conflicting = new RelationshipResolver(toInstall.Concat(checkedRecs), null,
RelationshipResolverOptions.ConflictsOpts(),
registry, crit)
registry, instance.game, crit)
.ConflictList.Keys;
// Don't check recommendations that conflict with installed or installing mods
checkedRecs.ExceptWith(conflicting);
Expand Down Expand Up @@ -1486,7 +1491,7 @@ public static bool FindRecommendations(GameInstance?
toInstall.Concat(recommendations.Keys)
.Concat(suggestions.Keys))
.Where(kvp => CanInstall(toInstall.Append(kvp.Key).ToList(),
opts, registry, crit))
opts, registry, instance.game, crit))
.ToDictionary();

return recommendations.Count > 0
Expand All @@ -1507,14 +1512,15 @@ public static bool FindRecommendations(GameInstance?
public static bool CanInstall(List<CkanModule> toInstall,
RelationshipResolverOptions opts,
IRegistryQuerier registry,
GameVersionCriteria? crit)
IGame game,
GameVersionCriteria crit)
{
string request = string.Join(", ", toInstall.Select(m => m.identifier));
try
{
var installed = toInstall.Select(m => registry.InstalledModule(m.identifier)?.Module)
.OfType<CkanModule>();
var resolver = new RelationshipResolver(toInstall, installed, opts, registry, crit);
var resolver = new RelationshipResolver(toInstall, installed, opts, registry, game, crit);

var resolverModList = resolver.ModList(false).ToList();
if (resolverModList.Count >= toInstall.Count(m => !m.IsMetapackage))
Expand All @@ -1536,7 +1542,7 @@ public static bool CanInstall(List<CkanModule> toInstall,
foreach (var mod in k.modules)
{
// Try each option recursively to see if any are successful
if (CanInstall(toInstall.Append(mod).ToList(), opts, registry, crit))
if (CanInstall(toInstall.Append(mod).ToList(), opts, registry, game, crit))
{
// Child call will emit debug output, so we don't need to here
return true;
Expand Down
3 changes: 2 additions & 1 deletion Core/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ Which {0} provider would you like to install?</value></data>
{1}

If the game is still running, close it and try again. Otherwise check the permissions.</value></data>
<data name="KrakenMissingDependency" xml:space="preserve"><value>{0} missing dependency {1}</value></data>
<data name="KrakenMissingDependency" xml:space="preserve"><value>Unsatisfied dependency {1} needed for: {0}</value></data>
<data name="KrakenMissingDependencyNeededFor" xml:space="preserve"><value>needed for {0}</value></data>
<data name="KrakenConflictsWith" xml:space="preserve"><value>{0} conflicts with {1}</value></data>
<data name="KrakenDownloadErrorsHeader" xml:space="preserve"><value>Uh oh, the following things went wrong when downloading...</value></data>
<data name="KrakenModuleDownloadErrorsHeader" xml:space="preserve"><value>One or more downloads were unsuccessful:</value></data>
Expand Down
6 changes: 3 additions & 3 deletions Core/Registry/CompatibilitySorter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public CompatibilitySorter(GameVersionCriteria crit
IEnumerable<Dictionary<string, AvailableModule>> available,
Dictionary<string, HashSet<AvailableModule>> providers,
Dictionary<string, InstalledModule> installed,
HashSet<string> dlls,
ICollection<string> dlls,
IDictionary<string, ModuleVersion> dlc)
{
CompatibleVersions = crit;
Expand Down Expand Up @@ -104,8 +104,8 @@ public ICollection<CkanModule> LatestIncompatible
}

private readonly Dictionary<string, InstalledModule> installed;
private readonly HashSet<string> dlls;
private readonly IDictionary<string, ModuleVersion> dlc;
private readonly ICollection<string> dlls;
private readonly IDictionary<string, ModuleVersion> dlc;

private List<CkanModule>? latestCompatible;
private List<CkanModule>? latestIncompatible;
Expand Down
Loading