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..8550f51c7 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,80 @@ static Program() public static int Main(string[] args) { + if (args.Contains("--allowDirtyAssemblies")) + { + using (DirtyAssemblyResolveHelper.Create()) + { + return CommandLineApplication.Execute(args); + } + } return CommandLineApplication.Execute(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 + /// + /// 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!! + /// + 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); + + // 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"); + } + + 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. {guessedPath}"); + + return Assembly.LoadFrom(guessedPath); + } + } }