Skip to content

Commit

Permalink
Merge #2442 Show conflict messages in status bar
Browse files Browse the repository at this point in the history
  • Loading branch information
politas committed Jun 4, 2018
2 parents 7e71357 + 17d9d77 commit d595122
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
- [Core] Improve handling of missing game version (#2444 by: HebaruSan; reviewed: politas)
- [Core] Handle zero byte registry.json file (#2435 by: HebaruSan; reviewed: politas)
- [Multiple] Pass game instance from cmdline to GUI/ConsoleUI (#2449 by: HebaruSan; reviewed: politas)
- [GUI] Show conflict messages in status bar (#2442 by: HebaruSan; reviewed: dbent, politas)

### Internal
- [Core] Test upgrading mod with conflict on its own provides (#2431 by: HebaruSan; reviewed: politas)
Expand Down
4 changes: 2 additions & 2 deletions Core/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ internal static class EnumerableExtensions
{
public static ICollection<T> AsCollection<T>(this IEnumerable<T> source)
{
if (source is null)
if (source == null)
throw new ArgumentNullException(nameof(source));

return source is ICollection<T> collection ? collection : source.ToArray();
}

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
if (source is null)
if (source == null)
throw new ArgumentNullException(nameof(source));

return new HashSet<T>(source);
Expand Down
54 changes: 35 additions & 19 deletions Core/Relationships/RelationshipResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,13 @@ public void AddModulesToInstall(IEnumerable<CkanModule> modules)
// adding them to the list. This *must* be pre-populated with all
// user-specified modules, as they may be supplying things that provide
// virtual packages.
foreach (var module in ckan_modules)
foreach (CkanModule module in ckan_modules)
{
log.DebugFormat("Preparing to resolve relationships for {0} {1}", module.identifier, module.version);

//Need to check against installed mods and those to install.
var mods = modlist.Values.Concat(installed_modules).Where(listed_mod => listed_mod.ConflictsWith(module));
foreach (var listed_mod in mods)
foreach (CkanModule listed_mod in mods)
{
if (options.procede_with_inconsistencies)
{
Expand All @@ -216,8 +216,8 @@ public void AddModulesToInstall(IEnumerable<CkanModule> modules)
}
else
{
throw new InconsistentKraken(string.Format("{0} conflicts with {1}, can't install both.", module,
listed_mod));
throw new InconsistentKraken(
$"{module} conflicts with {listed_mod}");
}
}

Expand All @@ -234,16 +234,29 @@ public void AddModulesToInstall(IEnumerable<CkanModule> modules)
Resolve(module, options);
}

if (!options.without_enforce_consistency)
try
{
var final_modules = new List<CkanModule>(modlist.Values);
final_modules.AddRange(installed_modules);
// Finally, let's do a sanity check that our solution is actually sane.
SanityChecker.EnforceConsistency(
final_modules,
modlist.Values.Concat(installed_modules),
registry.InstalledDlls,
registry.InstalledDlc
);
);
}
catch (BadRelationshipsKraken k)
{
// Add to this.conflicts (catches conflicting DLLs and DLCs that the above loops miss)
foreach (var kvp in k.Conflicts)
{
conflicts.Add(new KeyValuePair<CkanModule, CkanModule>(
kvp.Key, null
));
}
if (!options.without_enforce_consistency)
{
// Only re-throw if caller asked for consistency enforcement
throw;
}
}
}

Expand Down Expand Up @@ -301,9 +314,7 @@ private void Resolve(CkanModule module, RelationshipResolverOptions options, IEn
/// options.without_toomanyprovides_kraken is not set.
///
/// See RelationshipResolverOptions for further adjustments that can be made.
///
/// </summary>

private void ResolveStanza(IEnumerable<RelationshipDescriptor> stanza, SelectionReason reason,
RelationshipResolverOptions options, bool soft_resolve = false, IEnumerable<RelationshipDescriptor> old_stanza = null)
{
Expand All @@ -312,7 +323,7 @@ private void ResolveStanza(IEnumerable<RelationshipDescriptor> stanza, Selection
return;
}

foreach (var descriptor in stanza)
foreach (RelationshipDescriptor descriptor in stanza)
{
string dep_name = descriptor.name;
log.DebugFormat("Considering {0}", dep_name);
Expand Down Expand Up @@ -419,7 +430,7 @@ private void ResolveStanza(IEnumerable<RelationshipDescriptor> stanza, Selection
var fixed_mods = new HashSet<CkanModule>(modlist.Values);
fixed_mods.UnionWith(installed_modules);

var conflicting_mod = fixed_mods.FirstOrDefault(mod => mod.ConflictsWith(candidate));
CkanModule conflicting_mod = fixed_mods.FirstOrDefault(mod => mod.ConflictsWith(candidate));
if (conflicting_mod == null)
{
// Okay, looks like we want this one. Adding.
Expand All @@ -440,8 +451,8 @@ private void ResolveStanza(IEnumerable<RelationshipDescriptor> stanza, Selection
}
else
{
throw new InconsistentKraken(string.Format("{0} conflicts with {1}, can't install both.", conflicting_mod,
candidate));
throw new InconsistentKraken(
$"{conflicting_mod} conflicts with {candidate}");
}
}
}
Expand Down Expand Up @@ -535,11 +546,11 @@ public Dictionary<CkanModule, String> ConflictList
var dict = new Dictionary<CkanModule, String>();
foreach (var conflict in conflicts)
{
var module = conflict.Key;
CkanModule module = conflict.Key;
CkanModule other = conflict.Value;
dict[module] = string.Format("{0} conflicts with {1}\r\n\r\n{0}:\r\n{2}\r\n{1}:\r\n{3}",
module.identifier, conflict.Value.identifier,
ReasonStringFor(module), ReasonStringFor(conflict.Value));
;
module.identifier, other?.identifier ?? "an unmanaged DLL or DLC",
ReasonStringFor(module), ReasonStringFor(other));
}
return dict;
}
Expand All @@ -557,6 +568,11 @@ public bool IsConsistent
/// <returns></returns>
public string ReasonStringFor(CkanModule mod)
{
if (mod == null)
{
// If we don't have a CkanModule, it must be a DLL or DLC
return "Unmanaged";
}
var reason = ReasonFor(mod);
var is_root_type = reason.GetType() == typeof(SelectionReason.UserRequested)
|| reason.GetType() == typeof(SelectionReason.Installed);
Expand Down
68 changes: 36 additions & 32 deletions Core/Relationships/SanityChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,26 @@ public static ICollection<string> ConsistencyErrors(
IDictionary<string, UnmanagedModuleVersion> dlc
)
{
modules = modules?.AsCollection();
dlls = dlls?.AsCollection();

List<KeyValuePair<CkanModule, RelationshipDescriptor>> unmetDepends;
List<KeyValuePair<CkanModule, RelationshipDescriptor>> conflicts;
var errors = new HashSet<string>();

// If we have no modules, then everything is fine. DLLs can't depend or conflict on things.
if (modules == null)
{
return errors;
}

foreach (var kvp in FindUnsatisfiedDepends(modules.ToList(), dlls?.ToHashSet(), dlc))
if (!CheckConsistency(modules, dlls, dlc, out unmetDepends, out conflicts))
{
errors.Add($"{kvp.Key} has an unsatisfied dependency: {kvp.Value} is not installed");
}

// Conflicts are more difficult. Mods are allowed to conflict with themselves.
// So we walk all our mod conflicts, find what (if anything) provide those
// conflicts, and return false if it's not the module we're examining.
foreach (var kvp in FindConflicting(modules, dlls?.ToHashSet(), dlc))
{
errors.Add($"{kvp.Key} conflicts with {kvp.Value}");
foreach (var kvp in unmetDepends)
{
errors.Add($"{kvp.Key} has an unsatisfied dependency: {kvp.Value} is not installed");
}
foreach (var kvp in conflicts)
{
errors.Add($"{kvp.Key} conflicts with {kvp.Value}");
}
}

// Return whatever we've found, which could be empty.
return errors;
}

/// <summary>
/// Ensures all modules in the list provided can co-exist.
/// Throws a InconsistentKraken containing a list of inconsistences if they do not.
/// Throws a BadRelationshipsKraken describing the problems otherwise.
/// Does nothing if the modules can happily co-exist.
/// </summary>
public static void EnforceConsistency(
Expand All @@ -63,11 +52,11 @@ public static void EnforceConsistency(
IDictionary<string, UnmanagedModuleVersion> dlc = null
)
{
ICollection<string> errors = ConsistencyErrors(modules, dlls, dlc);

if (errors.Count != 0)
List<KeyValuePair<CkanModule, RelationshipDescriptor>> unmetDepends;
List<KeyValuePair<CkanModule, RelationshipDescriptor>> conflicts;
if (!CheckConsistency(modules, dlls, dlc, out unmetDepends, out conflicts))
{
throw new InconsistentKraken(errors);
throw new BadRelationshipsKraken(unmetDepends, conflicts);
}
}

Expand All @@ -80,7 +69,22 @@ public static bool IsConsistent(
IDictionary<string, UnmanagedModuleVersion> dlc = null
)
{
return ConsistencyErrors(modules, dlls, dlc).Count == 0;
List<KeyValuePair<CkanModule, RelationshipDescriptor>> unmetDepends;
List<KeyValuePair<CkanModule, RelationshipDescriptor>> conflicts;
return CheckConsistency(modules, dlls, dlc, out unmetDepends, out conflicts);
}

private static bool CheckConsistency(
IEnumerable<CkanModule> modules,
IEnumerable<string> dlls,
IDictionary<string, UnmanagedModuleVersion> dlc,
out List<KeyValuePair<CkanModule, RelationshipDescriptor>> UnmetDepends,
out List<KeyValuePair<CkanModule, RelationshipDescriptor>> Conflicts
)
{
UnmetDepends = FindUnsatisfiedDepends(modules?.ToList(), dlls?.ToHashSet(), dlc);
Conflicts = FindConflicting( modules, dlls?.ToHashSet(), dlc);
return !UnmetDepends.Any() && !Conflicts.Any();
}

/// <summary>
Expand Down Expand Up @@ -154,9 +158,9 @@ IDictionary<string, UnmanagedModuleVersion> dlc

private sealed class ProvidesInfo
{
public string ProviderIdentifier { get; }
public string ProviderIdentifier { get; }
public ModuleVersion ProviderVersion { get; }
public string ProvideeIdentifier { get; }
public string ProvideeIdentifier { get; }
public ModuleVersion ProvideeVersion { get; }

public ProvidesInfo(
Expand All @@ -167,9 +171,9 @@ ModuleVersion provideeVersion
)
{
ProviderIdentifier = providerIdentifier;
ProviderVersion = providerVersion;
ProviderVersion = providerVersion;
ProvideeIdentifier = provideeIdentifier;
ProvideeVersion = provideeVersion;
ProvideeVersion = provideeVersion;
}
}
}
Expand Down
11 changes: 3 additions & 8 deletions Core/Types/CkanModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -539,14 +539,9 @@ public bool ConflictsWith(CkanModule module)
/// </summary>
internal static bool UniConflicts(CkanModule mod1, CkanModule mod2)
{
if (mod1.conflicts == null)
{
return false;
}
return
mod1.conflicts.Any(
conflict =>
mod2.ProvidesList.Contains(conflict.name) && conflict.WithinBounds(mod2.version));
return mod1?.conflicts?.Any(
conflict => conflict.MatchesAny(new CkanModule[] {mod2}, null, null)
) ?? false;
}

/// <summary>
Expand Down
35 changes: 35 additions & 0 deletions Core/Types/Kraken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ public string InconsistenciesPretty
}
}

public string ShortDescription
{
get
{
return String.Join("; ", inconsistencies);
}
}

public InconsistentKraken(ICollection<string> inconsistencies, Exception innerException = null)
: base(null, innerException)
{
Expand All @@ -195,6 +203,33 @@ public override string ToString()
private const string header = "The following inconsistencies were found:\r\n";
}

/// <summary>
/// A mutation of InconsistentKraken that allows catching code to extract the causes of the errors.
/// </summary>
public class BadRelationshipsKraken : InconsistentKraken
{
public BadRelationshipsKraken(
ICollection<KeyValuePair<CkanModule, RelationshipDescriptor>> depends,
ICollection<KeyValuePair<CkanModule, RelationshipDescriptor>> conflicts
) : base(
(depends?.Select(dep => $"{dep.Key} missing dependency {dep.Value}")
?? new string[] {}
).Concat(
conflicts?.Select(conf => $"{conf.Key} conflicts with {conf.Value}")
?? new string[] {}
).ToArray()
)
{
Depends = depends?.ToList()
?? new List<KeyValuePair<CkanModule, RelationshipDescriptor>>();
Conflicts = conflicts?.ToList()
?? new List<KeyValuePair<CkanModule, RelationshipDescriptor>>();
}

public List<KeyValuePair<CkanModule, RelationshipDescriptor>> Depends { get; private set; }
public List<KeyValuePair<CkanModule, RelationshipDescriptor>> Conflicts { get; private set; }
}

/// <summary>
/// The terrible state when a file exists when we expect it not to be there.
/// For example, when we install a mod, and it tries to overwrite a file from another mod.
Expand Down
4 changes: 2 additions & 2 deletions Core/Versioning/ModuleVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public partial class ModuleVersion
private static readonly ConcurrentDictionary<Tuple<string, string>, int> ComparisonCache =
new ConcurrentDictionary<Tuple<string, string>, int>();
}

public partial class ModuleVersion
{
private readonly int _epoch;
Expand Down Expand Up @@ -298,7 +298,7 @@ Comparison numComp(string v1, string v2)
return comparison;
}

if (other is null)
if (other == null)
throw new ArgumentNullException(nameof(other));

if (other._epoch == _epoch && other._version.Equals(_version))
Expand Down
4 changes: 2 additions & 2 deletions Core/Versioning/UnmanagedModuleVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public sealed class UnmanagedModuleVersion : ModuleVersion
// HACK: Hardcoding a value of "0" for autodetected DLLs preserves previous behavior.
public UnmanagedModuleVersion(string version) : base(version ?? "0")
{
IsUnknownVersion = version is null;
_string = version is null ? "(unmanaged)" : $"{version} (unmanaged)";
IsUnknownVersion = version == null;
_string = version == null ? "(unmanaged)" : $"{version} (unmanaged)";
}

public override string ToString()
Expand Down
1 change: 1 addition & 0 deletions GUI/Main.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d595122

Please sign in to comment.