2019-12-26 23:01:54 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2015-07-17 16:38:17 -04:00
using System ;
2018-11-14 19:05:13 -05:00
using System.Collections.Generic ;
2015-07-22 14:00:30 -04:00
using System.Diagnostics ;
2015-07-17 16:38:17 -04:00
using System.IO ;
2015-08-06 15:55:44 -04:00
using System.Linq ;
2015-07-17 16:38:17 -04:00
using System.Reflection ;
namespace Tools.DotNETCommon
{
2020-12-21 23:47:46 -04:00
[Obsolete("Functionality in the Tools.DotNETCommon namespace is deprecated. Please reference the EpicGames.Core namespace and assembly instead.")]
2015-07-17 16:38:17 -04:00
public static class AssemblyUtils
{
/// <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>
public static string GetOriginalLocation ( this Assembly ThisAssembly )
{
return new Uri ( ThisAssembly . CodeBase ) . LocalPath ;
}
2015-07-22 14:00:30 -04:00
/// <summary>
/// Version info of the executable which runs this code.
/// </summary>
public static FileVersionInfo ExecutableVersion
{
get
{
return FileVersionInfo . GetVersionInfo ( Assembly . GetEntryAssembly ( ) . GetOriginalLocation ( ) ) ;
}
}
2015-08-06 15:55:44 -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>
public static void InstallAssemblyResolver ( string PathToBinariesDotNET )
{
AppDomain . CurrentDomain . AssemblyResolve + = ( sender , args ) = >
{
// Name is fully qualified assembly definition - e.g. "p4dn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ff968dc1933aba6f"
string AssemblyName = args . Name . Split ( ',' ) [ 0 ] ;
2020-06-19 17:05:51 -04:00
return (
from KnownAssemblyName in new [ ] { "SwarmAgent.exe" , "../ThirdParty/Ionic/Ionic.Zip.Reduced.dll" , "../ThirdParty/Newtonsoft/NewtonSoft.Json.dll" }
2015-08-06 15:55:44 -04:00
where AssemblyName . Equals ( Path . GetFileNameWithoutExtension ( KnownAssemblyName ) , StringComparison . InvariantCultureIgnoreCase )
let ResolvedAssemblyFilename = Path . Combine ( PathToBinariesDotNET , KnownAssemblyName )
// 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.
select File . Exists ( ResolvedAssemblyFilename ) ? Assembly . LoadFile ( ResolvedAssemblyFilename ) : null
) . FirstOrDefault ( ) ;
2020-06-19 17:05:51 -04:00
} ;
2015-08-06 15:55:44 -04:00
}
2018-11-14 19:05:13 -05:00
/// <summary>
/// Installs an assembly resolver, which will load *any* assembly which exists recursively within the supplied folder.
/// </summary>
/// <param name="RootDirectory">The directory to enumerate.</param>
public static void InstallRecursiveAssemblyResolver ( string RootDirectory )
{
2020-06-23 18:40:00 -04:00
RefreshAssemblyCache ( RootDirectory ) ;
AppDomain . CurrentDomain . AssemblyResolve + = ( sender , args ) = >
{
// Name is fully qualified assembly definition - e.g. "p4dn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ff968dc1933aba6f"
string AssemblyName = args . Name . Split ( ',' ) [ 0 ] ;
string AssemblyLocation ;
if ( AssemblyLocationCache . TryGetValue ( AssemblyName , out AssemblyLocation ) )
{
// We have this assembly in our folder.
if ( File . Exists ( AssemblyLocation ) )
{
// The assembly still exists, so load it.
return Assembly . LoadFile ( AssemblyLocation ) ;
}
else
{
// The assembly no longer exists on disk, so remove it from our cache.
AssemblyLocationCache . Remove ( AssemblyName ) ;
}
}
// The assembly wasn't found, though may have been compiled or copied as a dependency
RefreshAssemblyCache ( RootDirectory , string . Format ( "{0}.dll" , AssemblyName ) ) ;
if ( AssemblyLocationCache . TryGetValue ( AssemblyName , out AssemblyLocation ) )
{
return Assembly . LoadFile ( AssemblyLocation ) ;
}
return null ;
} ;
}
private static void RefreshAssemblyCache ( string RootDirectory , string Pattern = "*.dll" )
{
2018-11-14 19:05:13 -05:00
// Initialize our cache of assemblies by enumerating all files in the given folder.
2020-06-23 18:40:00 -04:00
foreach ( string DiscoveredAssembly in Directory . EnumerateFiles ( RootDirectory , Pattern , SearchOption . AllDirectories ) )
2018-11-14 19:05:13 -05:00
{
2020-11-09 10:57:52 -04:00
AddFileToAssemblyCache ( DiscoveredAssembly ) ;
2018-11-14 19:05:13 -05:00
}
2020-06-23 18:40:00 -04:00
2018-11-14 19:05:13 -05:00
}
2020-06-23 18:40:00 -04:00
2020-11-09 10:57:52 -04:00
public static void AddFileToAssemblyCache ( string AssemblyPath )
{
string AssemblyName = Path . GetFileNameWithoutExtension ( AssemblyPath ) ;
DateTime AssemblyLastWriteTime = File . GetLastWriteTimeUtc ( AssemblyPath ) ;
if ( AssemblyLocationCache . ContainsKey ( AssemblyName ) )
{
// We already have this assembly in our cache. Only replace it if the discovered file is newer (to avoid stale assemblies breaking stuff).
if ( AssemblyLastWriteTime > AssemblyWriteTimes [ AssemblyName ] )
{
AssemblyLocationCache [ AssemblyName ] = AssemblyPath ;
AssemblyWriteTimes [ AssemblyName ] = AssemblyLastWriteTime ;
}
}
else
{
// This is the first copy of this assembly ... add it to our cache.
AssemblyLocationCache . Add ( AssemblyName , AssemblyPath ) ;
AssemblyWriteTimes . Add ( AssemblyName , AssemblyLastWriteTime ) ;
}
}
2020-06-23 18:40:00 -04:00
// Map of assembly name to path on disk
2020-08-31 11:57:10 -04:00
private static Dictionary < string , string > AssemblyLocationCache = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
2020-06-23 18:40:00 -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.
2020-08-31 11:57:10 -04:00
private static Dictionary < string , DateTime > AssemblyWriteTimes = new Dictionary < string , DateTime > ( StringComparer . OrdinalIgnoreCase ) ;
2020-06-23 18:40:00 -04:00
2015-07-22 14:00:30 -04:00
}
2015-07-17 16:38:17 -04:00
}