diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 4253df8162..2dc82b380a 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -108,6 +108,8 @@ public class RelationshipResolver /// private readonly HashSet installed_modules; + private HashSet alreadyResolved = new HashSet(); + /// /// Creates a new Relationship resolver. /// @@ -327,6 +329,16 @@ private void RemoveModsFromInstalledList(IEnumerable mods) /// private void Resolve(CkanModule module, RelationshipResolverOptions options, IEnumerable old_stanza = null) { + if (alreadyResolved.Contains(module)) + { + return; + } + else + { + // Mark this module as resolved so we don't recurse here again + alreadyResolved.Add(module); + } + // Even though we may resolve top-level suggests for our module, // we don't install suggestions all the down unless with_all_suggests // is true. @@ -378,9 +390,17 @@ private void ResolveStanza(IEnumerable stanza, Selection { log.DebugFormat("Considering {0}", descriptor.ToString()); - // If we already have this dependency covered, skip. - if (descriptor.MatchesAny(modlist.Values, null, null)) + // If we already have this dependency covered, + // resolve its relationships if we haven't already. + if (descriptor.MatchesAny(modlist.Values, null, null, out CkanModule installingCandidate)) { + if (installingCandidate != null) + { + // Resolve the relationships of the matching module here + // because that's when it would happen if non-virtual + Resolve(installingCandidate, options, stanza); + } + // If null, it's a DLL or DLC, which we can't resolve continue; } else if (descriptor.ContainsAny(modlist.Keys)) diff --git a/Core/Types/RelationshipDescriptor.cs b/Core/Types/RelationshipDescriptor.cs index a53c72c44b..6408244d6b 100644 --- a/Core/Types/RelationshipDescriptor.cs +++ b/Core/Types/RelationshipDescriptor.cs @@ -9,10 +9,20 @@ namespace CKAN { public abstract class RelationshipDescriptor : IEquatable { - public abstract bool MatchesAny( + public bool MatchesAny( IEnumerable modules, HashSet dlls, IDictionary dlc + ) + { + return MatchesAny(modules, dlls, dlc, out CkanModule _); + } + + public abstract bool MatchesAny( + IEnumerable modules, + HashSet dlls, + IDictionary dlc, + out CkanModule matched ); public abstract bool WithinBounds(CkanModule otherModule); @@ -99,7 +109,8 @@ public bool WithinBounds(ModuleVersion other) public override bool MatchesAny( IEnumerable modules, HashSet dlls, - IDictionary dlc + IDictionary dlc, + out CkanModule matched ) { modules = modules?.AsCollection(); @@ -107,6 +118,7 @@ IDictionary dlc // DLLs are considered to match any version if (dlls != null && dlls.Contains(name)) { + matched = null; return true; } @@ -114,8 +126,15 @@ IDictionary dlc { // See if anyone else "provides" the target name // Note that versions can't be checked for "provides" clauses - if (modules.Any(m => m.identifier != name && m.provides != null && m.provides.Contains(name))) + var matches = modules + .Where(m => + m.identifier != name + && m.provides != null + && m.provides.Contains(name)) + .ToList(); + if (matches.Any()) { + matched = matches.FirstOrDefault(); return true; } @@ -124,6 +143,7 @@ IDictionary dlc { if (WithinBounds(m)) { + matched = m; return true; } } @@ -135,11 +155,13 @@ IDictionary dlc { if (WithinBounds(d.Value)) { + matched = null; return true; } } } + matched = null; return false; } @@ -233,11 +255,23 @@ public override bool WithinBounds(CkanModule otherModule) public override bool MatchesAny( IEnumerable modules, HashSet dlls, - IDictionary dlc + IDictionary dlc, + out CkanModule matched ) { - return any_of?.Any(r => r.MatchesAny(modules, dlls, dlc)) - ?? false; + if (any_of != null) + { + foreach (RelationshipDescriptor rel in any_of) + { + if (rel.MatchesAny(modules, dlls, dlc, out CkanModule whatMatched)) + { + matched = whatMatched; + return true; + } + } + } + matched = null; + return false; } public override List LatestAvailableWithProvides(