Skip to content
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
32 changes: 12 additions & 20 deletions src/Stunts.CompiledProxy/DependencyResolver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Collections.Immutable;
using System.IO;
using System.Reflection;

Expand Down Expand Up @@ -42,15 +41,12 @@ namespace Stunts
/// </remarks>
public static class DependencyResolver
{
static HashSet<string> searchPaths = new();
static ImmutableHashSet<string> searchPaths = ImmutableHashSet<string>.Empty;

static DependencyResolver() => AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;

static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
{
if (searchPaths.Count == 0)
return null;

var requested = new AssemblyName(args.Name);
if (requested.Name == null)
return null;
Expand All @@ -60,19 +56,14 @@ public static class DependencyResolver
var file = Path.GetFullPath(Path.Combine(dir, requested.Name + ".dll"));
if (File.Exists(file))
{
try
{
var actual = AssemblyName.GetAssemblyName(file);
// Only load compatible versions, allowing only minor version
// mismatch.
if (actual.Version.Major == requested.Version.Major &&
actual.Version.Minor >= requested.Version.Minor)
return Assembly.LoadFrom(file);
}
catch (Exception e)
{
Debug.Fail($"Failed to load an assembly from '{file}'.", e.ToString());
}
var actual = AssemblyName.GetAssemblyName(file);
// A simple-name probe has no version. Otherwise, only allow
// compatible major/minor versions.
if (requested.Version == null ||
actual.Version != null &&
actual.Version.Major == requested.Version.Major &&
actual.Version.Minor >= requested.Version.Minor)
return Assembly.LoadFrom(file);
}
}

Expand All @@ -84,6 +75,7 @@ public static class DependencyResolver
/// loading the generator.
/// </summary>
/// <returns>Whether the directory was added or it was already registered.</returns>
public static bool AddSearchPath(string path) => searchPaths.Add(path);
public static bool AddSearchPath(string path)
=> ImmutableInterlocked.Update(ref searchPaths, paths => paths.Add(path));
}
}
47 changes: 47 additions & 0 deletions src/Stunts.UnitTests/DependencyResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.IO;
using System.Reflection;
using Xunit;

namespace Stunts.UnitTests
{
public class DependencyResolverTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ResolvesDependencyWithOrWithoutVersion(bool includeVersion)
{
var assembly = typeof(StuntGenerator).Assembly;
DependencyResolver.AddSearchPath(Path.GetDirectoryName(assembly.Location));

var resolved = Resolve(includeVersion ? assembly.FullName : assembly.GetName().Name);

Assert.Same(assembly, resolved);
}

[Fact]
public void DoesNotResolveIncompatibleMajorVersion()
{
var assembly = typeof(StuntGenerator).Assembly;
DependencyResolver.AddSearchPath(Path.GetDirectoryName(assembly.Location));
var name = assembly.GetName();
name.Version = new Version(name.Version.Major + 1, 0, 0, 0);

Assert.Null(Resolve(name.FullName));
}

[Fact]
public void DoesNotResolveMissingSatelliteAssembly()
{
DependencyResolver.AddSearchPath(Path.GetDirectoryName(typeof(StuntGenerator).Assembly.Location));

Assert.Null(Resolve("Stunts.CodeAnalysis.resources, Culture=es"));
}

static Assembly Resolve(string name)
=> (Assembly)typeof(DependencyResolver)
.GetMethod("OnAssemblyResolve", BindingFlags.NonPublic | BindingFlags.Static)
.Invoke(null, new object[] { null, new ResolveEventArgs(name) });
}
}
Loading