Browse Source

Fix loadBefore/conflictsWith to behave correctly

pull/72/head
Anairkoen Schno 3 years ago
parent
commit
7e677e730b
Signed by: DaNike GPG Key ID: BEFB74D5F3FC4387
3 changed files with 94 additions and 27 deletions
  1. +39
    -6
      IPA.Loader/Loader/PluginLoader.cs
  2. +1
    -1
      IPA.Loader/Loader/PluginMetadata.cs
  3. +54
    -20
      IPA.Loader/Utilities/Utils.cs

+ 39
- 6
IPA.Loader/Loader/PluginLoader.cs View File

@ -811,15 +811,41 @@ namespace IPA.Loader
} }
// preprocess LoadBefore into LoadAfter // preprocess LoadBefore into LoadAfter
foreach (var kvp in metadataCache)
foreach (var (_, (meta, _)) in metadataCache)
{ // we iterate the metadata cache because it contains both disabled and enabled plugins { // we iterate the metadata cache because it contains both disabled and enabled plugins
var loadBefore = kvp.Value.Meta.Manifest.LoadBefore;
var loadBefore = meta.Manifest.LoadBefore;
foreach (var id in loadBefore) foreach (var id in loadBefore)
{ {
if (metadataCache.TryGetValue(id, out var plugin)) if (metadataCache.TryGetValue(id, out var plugin))
{ {
// if the id exists in our metadata cache, make sure it knows to load after the plugin in kvp // if the id exists in our metadata cache, make sure it knows to load after the plugin in kvp
_ = plugin.Meta.LoadsAfter.Add(kvp.Value.Meta);
_ = plugin.Meta.LoadsAfter.Add(meta);
}
}
}
// preprocess conflicts to be mutual
foreach (var (_, (meta, _)) in metadataCache)
{
foreach (var (id, range) in meta.Manifest.Conflicts)
{
if (metadataCache.TryGetValue(id, out var plugin)
&& range.IsSatisfied(plugin.Meta.Version))
{
// make sure that there's a mutual dependency
var targetRange = new Range($"={meta.Version}", true);
var targetConflicts = plugin.Meta.Manifest.Conflicts;
if (!targetConflicts.TryGetValue(meta.Id, out var realRange))
{
// there's not already a listed conflict
targetConflicts.Add(meta.Id, targetRange);
}
else if (!realRange.IsSatisfied(meta.Version))
{
// there is already a listed conflict that isn't mutual
targetRange = new Range($"{realRange} || {targetRange}", true);
targetConflicts[meta.Id] = targetRange;
}
} }
} }
} }
@ -829,12 +855,12 @@ namespace IPA.Loader
var isProcessing = new HashSet<PluginMetadata>(); var isProcessing = new HashSet<PluginMetadata>();
{ {
bool TryResolveId(string id, [MaybeNullWhen(false)] out PluginMetadata meta, out bool disabled, out bool ignored)
bool TryResolveId(string id, [MaybeNullWhen(false)] out PluginMetadata meta, out bool disabled, out bool ignored, bool partial = false)
{ {
meta = null; meta = null;
disabled = false; disabled = false;
ignored = true; ignored = true;
Logger.loader.Trace($"Trying to resolve plugin '{id}'");
Logger.loader.Trace($"Trying to resolve plugin '{id}' partial:{partial}");
if (loadedPlugins.TryGetValue(id, out var foundMeta)) if (loadedPlugins.TryGetValue(id, out var foundMeta))
{ {
meta = foundMeta.Meta; meta = foundMeta.Meta;
@ -846,6 +872,12 @@ namespace IPA.Loader
if (metadataCache!.TryGetValue(id, out var plugin)) if (metadataCache!.TryGetValue(id, out var plugin))
{ {
Logger.loader.Trace($"- In metadata cache"); Logger.loader.Trace($"- In metadata cache");
if (partial)
{
Logger.loader.Trace($" - but requested in a partial lookup");
return false;
}
disabled = !plugin.Enabled; disabled = !plugin.Enabled;
meta = plugin.Meta; meta = plugin.Meta;
if (!disabled) if (!disabled)
@ -1003,7 +1035,8 @@ namespace IPA.Loader
foreach (var conflict in plugin.Manifest.Conflicts) foreach (var conflict in plugin.Manifest.Conflicts)
{ {
Logger.loader.Trace($">- Checking conflict '{conflict.Key}' {conflict.Value}"); Logger.loader.Trace($">- Checking conflict '{conflict.Key}' {conflict.Value}");
if (TryResolveId(conflict.Key, out var meta, out var conflDisabled, out var conflIgnored)
// this lookup must be partial to prevent loadBefore/conflictsWith from creating a recursion loop
if (TryResolveId(conflict.Key, out var meta, out var conflDisabled, out var conflIgnored, partial: true)
&& conflict.Value.IsSatisfied(meta.Version) && conflict.Value.IsSatisfied(meta.Version)
&& !conflIgnored && !conflDisabled) // the conflict is only *actually* a problem if it is both not ignored and not disabled && !conflIgnored && !conflDisabled) // the conflict is only *actually* a problem if it is both not ignored and not disabled
{ {


+ 1
- 1
IPA.Loader/Loader/PluginMetadata.cs View File

@ -144,6 +144,6 @@ namespace IPA.Loader
/// Gets all of the metadata as a readable string. /// Gets all of the metadata as a readable string.
/// </summary> /// </summary>
/// <returns>the readable printable metadata string</returns> /// <returns>the readable printable metadata string</returns>
public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.FullName}) from '{Utils.GetRelativePath(File?.FullName, UnityGame.InstallPath)}'";
public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.FullName}) from '{Utils.GetRelativePath(File?.FullName ?? "", UnityGame.InstallPath)}'";
} }
} }

+ 54
- 20
IPA.Loader/Utilities/Utils.cs View File

@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Linq; using System.Linq;
@ -6,6 +7,7 @@ using System.Collections.Generic;
using Mono.Cecil; using Mono.Cecil;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Diagnostics.CodeAnalysis;
#if NET3 #if NET3
using File = Net3_Proxy.File; using File = Net3_Proxy.File;
#endif #endif
@ -24,6 +26,8 @@ namespace IPA.Utilities
/// <returns>the corresponding byte array</returns> /// <returns>the corresponding byte array</returns>
public static byte[] StringToByteArray(string hex) public static byte[] StringToByteArray(string hex)
{ {
if (hex is null)
throw new ArgumentNullException(nameof(hex));
int numberChars = hex.Length; int numberChars = hex.Length;
byte[] bytes = new byte[numberChars / 2]; byte[] bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2) for (int i = 0; i < numberChars; i += 2)
@ -38,9 +42,11 @@ namespace IPA.Utilities
/// <returns>the hex form of the array</returns> /// <returns>the hex form of the array</returns>
public static string ByteArrayToString(byte[] ba) public static string ByteArrayToString(byte[] ba)
{ {
StringBuilder hex = new StringBuilder(ba.Length * 2);
if (ba is null)
throw new ArgumentNullException(nameof(ba));
var hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba) foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
_ = hex.AppendFormat("{0:x2}", b);
return hex.ToString(); return hex.ToString();
} }
@ -64,9 +70,9 @@ namespace IPA.Utilities
byte* x1 = p1, x2 = p2; byte* x1 = p1, x2 = p2;
int l = a1.Length; int l = a1.Length;
for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8) for (int i = 0; i < l / 8; i++, x1 += 8, x2 += 8)
if (*((long*)x1) != *((long*)x2)) return false;
if ((l & 4) != 0) { if (*((int*)x1) != *((int*)x2)) return false; x1 += 4; x2 += 4; }
if ((l & 2) != 0) { if (*((short*)x1) != *((short*)x2)) return false; x1 += 2; x2 += 2; }
if (*(long*)x1 != *(long*)x2) return false;
if ((l & 4) != 0) { if (*(int*)x1 != *(int*)x2) return false; x1 += 4; x2 += 4; }
if ((l & 2) != 0) { if (*(short*)x1 != *(short*)x2) return false; x1 += 2; x2 += 2; }
if ((l & 1) != 0) if (*x1 != *x2) return false; if ((l & 1) != 0) if (*x1 != *x2) return false;
return true; return true;
} }
@ -80,13 +86,18 @@ namespace IPA.Utilities
/// <returns>a path to get from <paramref name="folder"/> to <paramref name="file"/></returns> /// <returns>a path to get from <paramref name="folder"/> to <paramref name="file"/></returns>
public static string GetRelativePath(string file, string folder) public static string GetRelativePath(string file, string folder)
{ {
Uri pathUri = new Uri(file);
if (file is null)
throw new ArgumentNullException(nameof(file));
if (folder is null)
throw new ArgumentNullException(nameof(folder));
var pathUri = new Uri(file);
// Folders must end in a slash // Folders must end in a slash
if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString()))
if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
{ {
folder += Path.DirectorySeparatorChar; folder += Path.DirectorySeparatorChar;
} }
Uri folderUri = new Uri(folder);
var folderUri = new Uri(folder);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar)); return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
} }
@ -98,9 +109,14 @@ namespace IPA.Utilities
/// <param name="appendFileName">the filename of the file to append together</param> /// <param name="appendFileName">the filename of the file to append together</param>
/// <param name="onCopyException">a delegate called when there is an error copying. Return true to keep going.</param> /// <param name="onCopyException">a delegate called when there is an error copying. Return true to keep going.</param>
public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string appendFileName = "", public static void CopyAll(DirectoryInfo source, DirectoryInfo target, string appendFileName = "",
Func<Exception, FileInfo, bool> onCopyException = null)
Func<Exception, FileInfo, bool>? onCopyException = null)
{ {
if (source.FullName.ToLower() == target.FullName.ToLower())
if (source is null)
throw new ArgumentNullException(nameof(source));
if (target is null)
throw new ArgumentNullException(nameof(target));
if (source.FullName.ToUpperInvariant() == target.FullName.ToUpperInvariant())
{ {
return; return;
} }
@ -108,18 +124,18 @@ namespace IPA.Utilities
// Check if the target directory exists, if not, create it. // Check if the target directory exists, if not, create it.
if (Directory.Exists(target.FullName) == false) if (Directory.Exists(target.FullName) == false)
{ {
Directory.CreateDirectory(target.FullName);
_ = Directory.CreateDirectory(target.FullName);
} }
// Copy each file into it's new directory. // Copy each file into it's new directory.
foreach (FileInfo fi in source.GetFiles())
foreach (var fi in source.GetFiles())
{ {
try try
{ {
if (fi.Name == appendFileName) if (fi.Name == appendFileName)
File.AppendAllLines(Path.Combine(target.ToString(), fi.Name), File.ReadAllLines(fi.FullName)); File.AppendAllLines(Path.Combine(target.ToString(), fi.Name), File.ReadAllLines(fi.FullName));
else else
fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
_ = fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
} }
catch (Exception e) catch (Exception e)
{ {
@ -130,10 +146,9 @@ namespace IPA.Utilities
} }
// Copy each subdirectory using recursion. // Copy each subdirectory using recursion.
foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
foreach (var diSourceSubDir in source.GetDirectories())
{ {
DirectoryInfo nextTargetSubDir =
target.CreateSubdirectory(diSourceSubDir.Name);
var nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name);
CopyAll(diSourceSubDir, nextTargetSubDir, appendFileName, onCopyException); CopyAll(diSourceSubDir, nextTargetSubDir, appendFileName, onCopyException);
} }
} }
@ -153,7 +168,7 @@ namespace IPA.Utilities
{ {
if (DateTimeSafetyUnknown) if (DateTimeSafetyUnknown)
{ {
DateTime time = DateTime.MinValue;
var time = DateTime.MinValue;
try try
{ {
time = DateTime.Now; time = DateTime.Now;
@ -180,6 +195,9 @@ namespace IPA.Utilities
/// <returns>&lt; 0 if l is less than r, 0 if they are equal in the numeric portion, or &gt; 0 if l is greater than r</returns> /// <returns>&lt; 0 if l is less than r, 0 if they are equal in the numeric portion, or &gt; 0 if l is greater than r</returns>
public static int VersionCompareNoPrerelease(SemVer.Version l, SemVer.Version r) public static int VersionCompareNoPrerelease(SemVer.Version l, SemVer.Version r)
{ {
if (l is null) throw new ArgumentNullException(nameof(l));
if (r is null) throw new ArgumentNullException(nameof(r));
var cmpVal = l.Major - r.Major; var cmpVal = l.Major - r.Major;
if (cmpVal != 0) return cmpVal; if (cmpVal != 0) return cmpVal;
cmpVal = l.Minor - r.Minor; cmpVal = l.Minor - r.Minor;
@ -188,6 +206,7 @@ namespace IPA.Utilities
return cmpVal; return cmpVal;
} }
/// <summary> /// <summary>
/// An object used to manage scope guards. /// An object used to manage scope guards.
/// </summary> /// </summary>
@ -197,6 +216,10 @@ namespace IPA.Utilities
/// </code> /// </code>
/// </example> /// </example>
/// <seealso cref="ScopeGuard(Action)"/> /// <seealso cref="ScopeGuard(Action)"/>
[SuppressMessage("Design", "CA1034:Nested types should not be visible",
Justification = "This type needs to be public to avoid allocations")]
[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types",
Justification = "This type is never supposed to be compared")]
public struct ScopeGuardObject : IDisposable public struct ScopeGuardObject : IDisposable
{ {
private readonly Action action; private readonly Action action;
@ -221,9 +244,20 @@ namespace IPA.Utilities
/// </code> /// </code>
/// </example> /// </example>
public static ScopeGuardObject ScopeGuard(Action action) public static ScopeGuardObject ScopeGuard(Action action)
=> new ScopeGuardObject(action);
=> new(action);
/// <summary>
/// Deconstructs a <see cref="KeyValuePair{TKey, TValue}"/> as its key and value.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="kvp">The <see cref="KeyValuePair{TKey, TValue}"/> to deconstruct.</param>
/// <param name="key">The key in <paramref name="kvp"/>.</param>
/// <param name="value">The value in <paramref name="kvp"/>.</param>
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
=> (key, value) = (kvp.Key, kvp.Value);
internal static bool HasInterface(this TypeDefinition type, string interfaceFullName)
internal static bool HasInterface(this TypeDefinition? type, string interfaceFullName)
{ {
return (type?.Interfaces?.Any(i => i.InterfaceType.FullName == interfaceFullName) ?? false) return (type?.Interfaces?.Any(i => i.InterfaceType.FullName == interfaceFullName) ?? false)
|| (type?.Interfaces?.Any(t => HasInterface(t?.InterfaceType?.Resolve(), interfaceFullName)) ?? false); || (type?.Interfaces?.Any(t => HasInterface(t?.InterfaceType?.Resolve(), interfaceFullName)) ?? false);


Loading…
Cancel
Save