2020-12-21 11:50:46 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
2020-12-21 23:07:37 -04:00
namespace EpicGames.Core
2020-12-21 11:50:46 -04:00
{
public static class AssemblyUtils
{
2021-08-04 16:50:01 -04:00
/// <summary>
/// Gets the original location (path and filename) of an assembly.
/// This method is using Assembly.CodeBase property to properly resolve original
/// assembly path in case shadow copying is enabled.
/// </summary>
/// <returns>Absolute path and filename to the assembly.</returns>
2022-03-24 16:35:00 -04:00
public static string GetOriginalLocation ( this Assembly thisAssembly )
2021-08-04 16:50:01 -04:00
{
2022-03-24 16:35:00 -04:00
return new Uri ( thisAssembly . Location ) . LocalPath ;
2021-08-04 16:50:01 -04:00
}
2020-12-21 11:50:46 -04:00
2021-08-04 16:50:01 -04:00
/// <summary>
/// Version info of the executable which runs this code.
/// </summary>
2022-03-24 16:35:00 -04:00
public static FileVersionInfo ExecutableVersion = > FileVersionInfo . GetVersionInfo ( Assembly . GetEntryAssembly ( ) ! . GetOriginalLocation ( ) ) ;
2021-08-04 16:50:01 -04:00
/// <summary>
/// Installs an assembly resolver. Mostly used to get shared assemblies that we don't want copied around to various output locations as happens when "Copy Local" is set to true
/// for an assembly reference (which is the default).
/// </summary>
2022-03-24 16:35:00 -04:00
public static void InstallAssemblyResolver ( string pathToBinariesDotNET )
2021-08-04 16:50:01 -04:00
{
2020-12-21 11:50:46 -04:00
AppDomain . CurrentDomain . AssemblyResolve + = ( sender , args ) = >
2021-08-04 16:50:01 -04:00
{
// Name is fully qualified assembly definition - e.g. "p4dn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ff968dc1933aba6f"
2022-03-24 16:35:00 -04:00
string assemblyName = args . Name ! . Split ( ',' ) [ 0 ] ;
2020-12-21 11:50:46 -04:00
return (
2022-03-24 16:35:00 -04:00
from knownAssemblyName in new [ ] { "SwarmAgent.exe" , "../ThirdParty/Ionic/Ionic.Zip.Reduced.dll" , "../ThirdParty/Newtonsoft/NewtonSoft.Json.dll" }
where assemblyName . Equals ( Path . GetFileNameWithoutExtension ( knownAssemblyName ) , StringComparison . InvariantCultureIgnoreCase )
let resolvedAssemblyFilename = Path . Combine ( pathToBinariesDotNET , knownAssemblyName )
2021-08-04 16:50:01 -04:00
// check if the file exists first. If we just try to load it, we correctly throw an exception, but it's a generic
// FileNotFoundException, which is not informative. Better to return null.
2022-03-24 16:35:00 -04:00
select File . Exists ( resolvedAssemblyFilename ) ? Assembly . LoadFile ( resolvedAssemblyFilename ) : null
2021-08-04 16:50:01 -04:00
) . FirstOrDefault ( ) ;
2020-12-21 11:50:46 -04:00
} ;
2021-08-04 16:50:01 -04:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Installs an assembly resolver, which will load *any* assembly which exists recursively within the supplied folder.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="rootDirectory">The directory to enumerate.</param>
public static void InstallRecursiveAssemblyResolver ( string rootDirectory )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
RefreshAssemblyCache ( rootDirectory ) ;
2021-08-04 16:50:01 -04:00
2020-12-21 11:50:46 -04:00
AppDomain . CurrentDomain . AssemblyResolve + = ( sender , args ) = >
{
// Name is fully qualified assembly definition - e.g. "p4dn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ff968dc1933aba6f"
2022-03-24 16:35:00 -04:00
string assemblyName = args . Name ! . Split ( ',' ) [ 0 ] ;
if ( s_assemblyLocationCache . TryGetValue ( assemblyName , out string? assemblyLocation ) )
2020-12-21 11:50:46 -04:00
{
// We have this assembly in our folder.
2022-03-24 16:35:00 -04:00
if ( File . Exists ( assemblyLocation ) )
2020-12-21 11:50:46 -04:00
{
// The assembly still exists, so load it.
2022-03-24 16:35:00 -04:00
return Assembly . LoadFile ( assemblyLocation ) ;
2020-12-21 11:50:46 -04:00
}
else
{
// The assembly no longer exists on disk, so remove it from our cache.
2022-03-24 16:35:00 -04:00
s_assemblyLocationCache . Remove ( assemblyName ) ;
2020-12-21 11:50:46 -04:00
}
}
// The assembly wasn't found, though may have been compiled or copied as a dependency
2022-03-24 16:35:00 -04:00
RefreshAssemblyCache ( rootDirectory , String . Format ( "{0}.dll" , assemblyName ) ) ;
if ( s_assemblyLocationCache . TryGetValue ( assemblyName , out assemblyLocation ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
return Assembly . LoadFile ( assemblyLocation ) ;
2020-12-21 11:50:46 -04:00
}
return null ;
} ;
}
2022-03-24 16:35:00 -04:00
private static void RefreshAssemblyCache ( string rootDirectory , string pattern = "*.dll" )
2020-12-21 11:50:46 -04:00
{
// Initialize our cache of assemblies by enumerating all files in the given folder.
2022-03-24 16:35:00 -04:00
foreach ( string discoveredAssembly in Directory . EnumerateFiles ( rootDirectory , pattern , SearchOption . AllDirectories ) )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
AddFileToAssemblyCache ( discoveredAssembly ) ;
2020-12-21 11:50:46 -04:00
}
}
2022-03-24 16:35:00 -04:00
public static void AddFileToAssemblyCache ( string assemblyPath )
2020-12-21 11:50:46 -04:00
{
2022-04-14 14:31:29 -04:00
// Ignore any reference assemblies
string? directory = Path . GetFileName ( Path . GetDirectoryName ( assemblyPath ) ) ;
if ( ! string . IsNullOrEmpty ( directory ) & & ( directory = = "ref" | directory = = "refint" ) )
{
return ;
}
2022-03-24 16:35:00 -04:00
string assemblyName = Path . GetFileNameWithoutExtension ( assemblyPath ) ;
DateTime assemblyLastWriteTime = File . GetLastWriteTimeUtc ( assemblyPath ) ;
if ( s_assemblyLocationCache . ContainsKey ( assemblyName ) )
2020-12-21 11:50:46 -04:00
{
// We already have this assembly in our cache. Only replace it if the discovered file is newer (to avoid stale assemblies breaking stuff).
2022-03-24 16:35:00 -04:00
if ( assemblyLastWriteTime > s_assemblyWriteTimes [ assemblyName ] )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
s_assemblyLocationCache [ assemblyName ] = assemblyPath ;
s_assemblyWriteTimes [ assemblyName ] = assemblyLastWriteTime ;
2020-12-21 11:50:46 -04:00
}
}
else
{
// This is the first copy of this assembly ... add it to our cache.
2022-03-24 16:35:00 -04:00
s_assemblyLocationCache . Add ( assemblyName , assemblyPath ) ;
s_assemblyWriteTimes . Add ( assemblyName , assemblyLastWriteTime ) ;
2020-12-21 11:50:46 -04:00
}
}
// Map of assembly name to path on disk
2022-03-24 16:35:00 -04:00
private static readonly Dictionary < string , string > s_assemblyLocationCache = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
2020-12-21 11:50:46 -04:00
// Track last modified date of each assembly, so we can ensure we always reference the latest one in the case of stale assemblies on disk.
2022-03-24 16:35:00 -04:00
private static readonly Dictionary < string , DateTime > s_assemblyWriteTimes = new Dictionary < string , DateTime > ( StringComparer . OrdinalIgnoreCase ) ;
2020-12-21 11:50:46 -04:00
2021-08-04 16:50:01 -04:00
}
2020-12-21 11:50:46 -04:00
}