Add C# class library optimization for normal game builds (without AOT)
This commit is contained in:
@@ -225,6 +225,26 @@ bool DeployDataStep::Perform(CookingData& data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize deployed C# class library (remove DLLs unused by scripts)
|
||||||
|
if (aotMode == DotNetAOTModes::None && buildSettings.SkipUnusedDotnetLibsPackaging)
|
||||||
|
{
|
||||||
|
LOG(Info, "Optimizing .NET class library size to include only used assemblies");
|
||||||
|
const String logFile = data.CacheDirectory / TEXT("StripDotnetLibs.txt");
|
||||||
|
String args = String::Format(
|
||||||
|
TEXT("-log -logfile=\"{}\" -runDotNetClassLibStripping -mutex -binaries=\"{}\""),
|
||||||
|
logFile, data.DataOutputPath);
|
||||||
|
for (const String& define : data.CustomDefines)
|
||||||
|
{
|
||||||
|
args += TEXT(" -D");
|
||||||
|
args += define;
|
||||||
|
}
|
||||||
|
if (ScriptsBuilder::RunBuildTool(args))
|
||||||
|
{
|
||||||
|
data.Error(TEXT("Failed to optimize .Net class library."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (!FileSystem::DirectoryExists(dstMono))
|
if (!FileSystem::DirectoryExists(dstMono))
|
||||||
|
|||||||
@@ -56,19 +56,88 @@ namespace Flax.Build
|
|||||||
public static void RunDotNetAOT()
|
public static void RunDotNetAOT()
|
||||||
{
|
{
|
||||||
Log.Info("Running .NET AOT in mode " + AOTMode);
|
Log.Info("Running .NET AOT in mode " + AOTMode);
|
||||||
DotNetAOT.Run();
|
DotNetAOT.RunAOT();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes .NET class library stripping process as a part of the game cooking (called by DeployDataStep in Editor).
|
||||||
|
/// </summary>
|
||||||
|
[CommandLine("runDotNetClassLibStripping", "")]
|
||||||
|
public static void RunDotNetClassLibStripping()
|
||||||
|
{
|
||||||
|
Log.Info("Running .NET class Library stripping");
|
||||||
|
DotNetAOT.RunClassLibStripping();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The DotNet Ahead of Time Compilation (AOT) feature.
|
/// The DotNet Ahead of Time Compilation (AOT) feature.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DotNetAOT
|
internal static class DotNetAOT
|
||||||
{
|
{
|
||||||
/// <summary>
|
internal static void RunClassLibStripping()
|
||||||
/// Executes AOT process as a part of the game cooking (called by PrecompileAssembliesStep in Editor).
|
{
|
||||||
/// </summary>
|
var outputPath = Configuration.BinariesFolder; // Provided by DeployDataStep
|
||||||
public static void Run()
|
if (!Directory.Exists(outputPath))
|
||||||
|
throw new Exception("Missing output folder " + outputPath);
|
||||||
|
var dotnetOutputPath = Path.Combine(outputPath, "Dotnet");
|
||||||
|
if (!Directory.Exists(dotnetOutputPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Find input files
|
||||||
|
var inputFiles = Directory.GetFiles(outputPath, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||||
|
inputFiles.RemoveAll(FilterAssembly);
|
||||||
|
for (int i = 0; i < inputFiles.Count; i++)
|
||||||
|
inputFiles[i] = Utilities.NormalizePath(inputFiles[i]);
|
||||||
|
inputFiles.Sort();
|
||||||
|
|
||||||
|
// Peek class library folder
|
||||||
|
var coreLibPaths = Directory.GetFiles(dotnetOutputPath, "System.Private.CoreLib.dll", SearchOption.AllDirectories);
|
||||||
|
if (coreLibPaths.Length != 1)
|
||||||
|
throw new Exception("Invalid C# class library setup in " + dotnetOutputPath);
|
||||||
|
var dotnetLibPath = Utilities.NormalizePath(Path.GetDirectoryName(coreLibPaths[0]));
|
||||||
|
|
||||||
|
using (var assemblyResolver = new MonoCecil.BasicAssemblyResolver())
|
||||||
|
{
|
||||||
|
assemblyResolver.SearchDirectories.Add(outputPath);
|
||||||
|
assemblyResolver.SearchDirectories.Add(dotnetLibPath);
|
||||||
|
|
||||||
|
// Build list of all used assemblies
|
||||||
|
var assembliesPaths = new List<string>();
|
||||||
|
foreach (var inputFile in inputFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BuildAssembliesList(inputFile, assembliesPaths, assemblyResolver, string.Empty, null);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all C# class lib assemblies
|
||||||
|
var stdLibFiles = Directory.GetFiles(dotnetLibPath, "*.dll", SearchOption.TopDirectoryOnly).ToList();
|
||||||
|
stdLibFiles.RemoveAll(FilterAssembly);
|
||||||
|
for (int i = 0; i < stdLibFiles.Count; i++)
|
||||||
|
stdLibFiles[i] = Utilities.NormalizePath(stdLibFiles[i]);
|
||||||
|
|
||||||
|
// Remove any unused C# class lib assemblies
|
||||||
|
long sizeOfRemoved = 0;
|
||||||
|
foreach (var file in stdLibFiles)
|
||||||
|
{
|
||||||
|
if (!assembliesPaths.Contains(file))
|
||||||
|
{
|
||||||
|
Log.Info("Removing unused C# assembly: " + Path.GetFileName(file));
|
||||||
|
var fileInfo = new FileInfo(file);
|
||||||
|
sizeOfRemoved += fileInfo.Length;
|
||||||
|
fileInfo.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.Info("Removed C# assemblies size: " + (sizeOfRemoved / (1024 * 1024) + " MB"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void RunAOT()
|
||||||
{
|
{
|
||||||
var platform = Configuration.BuildPlatforms[0];
|
var platform = Configuration.BuildPlatforms[0];
|
||||||
var arch = Configuration.BuildArchitectures[0];
|
var arch = Configuration.BuildArchitectures[0];
|
||||||
@@ -435,19 +504,22 @@ namespace Flax.Build
|
|||||||
|
|
||||||
internal static void BuildAssembliesList(string assemblyPath, AssemblyNameReference assemblyReference, List<string> outputList, IAssemblyResolver assemblyResolver, string callerPath, HashSet<string> warnings)
|
internal static void BuildAssembliesList(string assemblyPath, AssemblyNameReference assemblyReference, List<string> outputList, IAssemblyResolver assemblyResolver, string callerPath, HashSet<string> warnings)
|
||||||
{
|
{
|
||||||
// Detect usage of C# API that is not supported in AOT builds
|
|
||||||
var assemblyName = Path.GetFileName(assemblyPath);
|
var assemblyName = Path.GetFileName(assemblyPath);
|
||||||
if (assemblyReference.Name.Contains("System.Linq.Expressions") ||
|
if (warnings != null)
|
||||||
assemblyReference.Name.Contains("System.Reflection.Emit") ||
|
|
||||||
assemblyReference.Name.Contains("System.Reflection.Emit.ILGeneration"))
|
|
||||||
{
|
{
|
||||||
if (!warnings.Contains(assemblyReference.Name))
|
// Detect usage of C# API that is not supported in AOT builds
|
||||||
|
if (assemblyReference.Name.Contains("System.Linq.Expressions") ||
|
||||||
|
assemblyReference.Name.Contains("System.Reflection.Emit") ||
|
||||||
|
assemblyReference.Name.Contains("System.Reflection.Emit.ILGeneration"))
|
||||||
{
|
{
|
||||||
warnings.Add(assemblyReference.Name);
|
if (!warnings.Contains(assemblyReference.Name))
|
||||||
if (callerPath.Length != 0)
|
{
|
||||||
Log.Warning($"Warning! Assembly '{assemblyName}' (referenced by '{callerPath}') references '{assemblyReference.Name}' which is not supported in AOT builds and might cause error (due to lack of JIT at runtime).");
|
warnings.Add(assemblyReference.Name);
|
||||||
else
|
if (callerPath.Length != 0)
|
||||||
Log.Warning($"Warning! Assembly '{assemblyName}' references '{assemblyReference.Name}' which is not supported in AOT builds and might cause error (due to lack of JIT at runtime).");
|
Log.Warning($"Warning! Assembly '{assemblyName}' (referenced by '{callerPath}') references '{assemblyReference.Name}' which is not supported in AOT builds and might cause error (due to lack of JIT at runtime).");
|
||||||
|
else
|
||||||
|
Log.Warning($"Warning! Assembly '{assemblyName}' references '{assemblyReference.Name}' which is not supported in AOT builds and might cause error (due to lack of JIT at runtime).");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (callerPath.Length != 0)
|
if (callerPath.Length != 0)
|
||||||
|
|||||||
@@ -22,6 +22,19 @@ namespace Flax.Build
|
|||||||
|
|
||||||
public HashSet<string> SearchDirectories = new();
|
public HashSet<string> SearchDirectories = new();
|
||||||
|
|
||||||
|
public AssemblyDefinition Resolve(string path)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
|
foreach (var e in _cache)
|
||||||
|
{
|
||||||
|
if (string.Equals(e.Value.Name.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return e.Value;
|
||||||
|
}
|
||||||
|
var assembly = ModuleDefinition.ReadModule(path, new ReaderParameters()).Assembly;
|
||||||
|
_cache[assembly.FullName] = assembly;
|
||||||
|
return assembly;
|
||||||
|
}
|
||||||
|
|
||||||
public AssemblyDefinition Resolve(AssemblyNameReference name)
|
public AssemblyDefinition Resolve(AssemblyNameReference name)
|
||||||
{
|
{
|
||||||
return Resolve(name, new ReaderParameters());
|
return Resolve(name, new ReaderParameters());
|
||||||
|
|||||||
Reference in New Issue
Block a user