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

Show conflict messages in status bar #2442

Merged
merged 1 commit into from
Jun 4, 2018
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
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