From 8814a90307292a624f401465e07a360ff9a4ba74 Mon Sep 17 00:00:00 2001 From: OneThatWalks Date: Thu, 18 Feb 2021 14:03:22 -0500 Subject: [PATCH 1/4] Basic dirty assembly loading --- .../Commands/MigrationCommand.cs | 3 + src/FluentMigrator.DotNet.Cli/Program.cs | 69 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/FluentMigrator.DotNet.Cli/Commands/MigrationCommand.cs b/src/FluentMigrator.DotNet.Cli/Commands/MigrationCommand.cs index 8915152aa..dc782ca05 100644 --- a/src/FluentMigrator.DotNet.Cli/Commands/MigrationCommand.cs +++ b/src/FluentMigrator.DotNet.Cli/Commands/MigrationCommand.cs @@ -58,5 +58,8 @@ public class MigrationCommand : BaseCommand [Option("--include-untagged-maintenances", Description = "Include untagged maintenances.")] public bool IncludeUntaggedMaintenances { get; } + + [Option("--allowDirtyAssemblies", Description = "Allows dirty assemblies.")] + public bool AllowDirtyAssemblies { get; } } } diff --git a/src/FluentMigrator.DotNet.Cli/Program.cs b/src/FluentMigrator.DotNet.Cli/Program.cs index 9be95b79e..dec5e59f7 100644 --- a/src/FluentMigrator.DotNet.Cli/Program.cs +++ b/src/FluentMigrator.DotNet.Cli/Program.cs @@ -16,6 +16,13 @@ // #endregion +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + using FluentMigrator.DotNet.Cli.Commands; using McMaster.Extensions.CommandLineUtils; @@ -32,7 +39,69 @@ static Program() public static int Main(string[] args) { + if (args.Contains("--allowDirtyAssemblies")) + { + DirtyAssemblyResolveHelper.Create(); + } return CommandLineApplication.Execute(args); } } + + /// + /// Sometimes NuGet/VS/other tool does not generate the right assembly binding redirects + /// or just for any other magical reasons + /// our users get FileNotFoundException when trying to run their benchmarks + /// + /// We want our users to be happy and we try to help the .NET framework when it fails to load an assembly + /// + /// It's not recommended to copy this code OR reuse it anywhere. It's an UGLY WORKAROUND. + /// + /// If one day we can remove it, the person doing that should celebrate!! + /// + // ReSharper disable once CheckNamespace I did it on purpose to show that it MUST not touch any BenchmarkDotNet stuff + internal class DirtyAssemblyResolveHelper : IDisposable + { + internal static IDisposable Create() => new DirtyAssemblyResolveHelper(); + + private DirtyAssemblyResolveHelper() => AppDomain.CurrentDomain.AssemblyResolve += HelpTheFrameworkToResolveTheAssembly; + + public void Dispose() => AppDomain.CurrentDomain.AssemblyResolve -= HelpTheFrameworkToResolveTheAssembly; + + /// + /// according to https://msdn.microsoft.com/en-us/library/ff527268(v=vs.110).aspx + /// "the handler is invoked whenever the runtime fails to bind to an assembly by name." + /// + /// not null when we find it manually, null when can't help + private Assembly HelpTheFrameworkToResolveTheAssembly(object sender, ResolveEventArgs args) + { + var fullName = new AssemblyName(args.Name); + string simpleName = fullName.Name; + + // Get assemblies from runtime environment + string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), $"{simpleName}.dll"); + + // Create the list of assembly paths consisting of runtime assemblies + var paths = new List(runtimeAssemblies); + string guessedPath = paths.FirstOrDefault(); + + if (guessedPath == null) + { + guessedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{simpleName}.dll"); + } + + if (!File.Exists(guessedPath)) + return null; // we can't help, and we also don't call Assembly.Load which if fails comes back here, creates endless loop and causes StackOverflow + + // the file is right there, but has most probably different version and there is no assembly redirect + // so we just load it and ignore the version mismatch + // we can at least try because benchmarks are not executed in the Host process, + // so even if we load some bad version of the assembly + // we might still produce the right exe with proper references + + // we warn the user about that, in case some Super User want to be aware of that + Console.WriteLine($"// Wrong assembly binding redirects for {simpleName}, loading it from disk anyway."); + + return Assembly.LoadFrom(guessedPath); + } + } } From 904effa89645a3d4d2cfac1c620d5b10f531170b Mon Sep 17 00:00:00 2001 From: OneThatWalks Date: Thu, 18 Feb 2021 14:07:04 -0500 Subject: [PATCH 2/4] Appease the disposal pattern --- src/FluentMigrator.DotNet.Cli/Program.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/FluentMigrator.DotNet.Cli/Program.cs b/src/FluentMigrator.DotNet.Cli/Program.cs index dec5e59f7..6fbd7e178 100644 --- a/src/FluentMigrator.DotNet.Cli/Program.cs +++ b/src/FluentMigrator.DotNet.Cli/Program.cs @@ -41,7 +41,10 @@ public static int Main(string[] args) { if (args.Contains("--allowDirtyAssemblies")) { - DirtyAssemblyResolveHelper.Create(); + using (DirtyAssemblyResolveHelper.Create()) + { + return CommandLineApplication.Execute(args); + } } return CommandLineApplication.Execute(args); } From e5cd12b0b86d247ca6338845132398d84aa95740 Mon Sep 17 00:00:00 2001 From: OneThatWalks Date: Thu, 18 Feb 2021 14:10:34 -0500 Subject: [PATCH 3/4] Show the path we loaded from --- src/FluentMigrator.DotNet.Cli/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FluentMigrator.DotNet.Cli/Program.cs b/src/FluentMigrator.DotNet.Cli/Program.cs index 6fbd7e178..202ab5ea9 100644 --- a/src/FluentMigrator.DotNet.Cli/Program.cs +++ b/src/FluentMigrator.DotNet.Cli/Program.cs @@ -102,7 +102,7 @@ private Assembly HelpTheFrameworkToResolveTheAssembly(object sender, ResolveEven // we might still produce the right exe with proper references // we warn the user about that, in case some Super User want to be aware of that - Console.WriteLine($"// Wrong assembly binding redirects for {simpleName}, loading it from disk anyway."); + Console.WriteLine($"// Wrong assembly binding redirects for {simpleName}, loading it from disk anyway. {guessedPath}"); return Assembly.LoadFrom(guessedPath); } From 29b98a2c438c640e997732c05ef6404bc9e3642d Mon Sep 17 00:00:00 2001 From: OneThatWalks Date: Fri, 19 Feb 2021 10:12:45 -0500 Subject: [PATCH 4/4] He said ship it, also added some comments for clarity --- src/FluentMigrator.DotNet.Cli/Program.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/FluentMigrator.DotNet.Cli/Program.cs b/src/FluentMigrator.DotNet.Cli/Program.cs index 202ab5ea9..8550f51c7 100644 --- a/src/FluentMigrator.DotNet.Cli/Program.cs +++ b/src/FluentMigrator.DotNet.Cli/Program.cs @@ -51,6 +51,12 @@ public static int Main(string[] args) } /// + /// Temporary workaround for + /// https://github.com/fluentmigrator/fluentmigrator/issues/1406 + /// + /// taken mostly from + /// https://github.com/dotnet/BenchmarkDotNet/blob/852bb8cd9c2ac2530866dc53723c5f2ce3d411fa/src/BenchmarkDotNet/Helpers/DirtyAssemblyResolveHelper.cs#L18 + /// /// Sometimes NuGet/VS/other tool does not generate the right assembly binding redirects /// or just for any other magical reasons /// our users get FileNotFoundException when trying to run their benchmarks @@ -61,7 +67,6 @@ public static int Main(string[] args) /// /// If one day we can remove it, the person doing that should celebrate!! /// - // ReSharper disable once CheckNamespace I did it on purpose to show that it MUST not touch any BenchmarkDotNet stuff internal class DirtyAssemblyResolveHelper : IDisposable { internal static IDisposable Create() => new DirtyAssemblyResolveHelper(); @@ -85,8 +90,11 @@ private Assembly HelpTheFrameworkToResolveTheAssembly(object sender, ResolveEven // Create the list of assembly paths consisting of runtime assemblies var paths = new List(runtimeAssemblies); + + // Since we search by simple name, there should hopefully be one file string guessedPath = paths.FirstOrDefault(); + // If there wasn't a runtime file check the app directory for the file if (guessedPath == null) { guessedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{simpleName}.dll");