diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
index c0188ffe2..74216f070 100644
--- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
+++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp
@@ -14,13 +14,14 @@ void PrecompileAssembliesStep::OnBuildStarted(CookingData& data)
const DotNetAOTModes aotMode = data.Tools->UseAOT();
if (aotMode == DotNetAOTModes::None)
return;
+ const auto& buildSettings = *BuildSettings::Get();
// Redirect C# assemblies to intermediate cooking directory (processed by ILC)
data.ManagedCodeOutputPath = data.CacheDirectory / TEXT("AOTAssemblies");
// Reset any AOT cache from previous run if the AOT mode has changed (eg. Mono AOT -> ILC on Desktop)
const String aotModeCacheFilePath = data.ManagedCodeOutputPath / TEXT("AOTMode.txt");
- String aotModeCacheValue = String::Format(TEXT("{};{}"), (int32)aotMode, (int32)data.Configuration);
+ String aotModeCacheValue = String::Format(TEXT("{};{};{}"), (int32)aotMode, (int32)data.Configuration, (int32)buildSettings.SkipUnusedDotnetLibsPackaging);
for (const String& define : data.CustomDefines)
aotModeCacheValue += define;
if (FileSystem::DirectoryExists(data.ManagedCodeOutputPath))
@@ -60,9 +61,11 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
const Char *platform, *architecture, *configuration = ::ToString(data.Configuration);
data.GetBuildPlatformName(platform, architecture);
const String logFile = data.CacheDirectory / TEXT("AotLog.txt");
- auto args = String::Format(
+ String args = String::Format(
TEXT("-log -logfile=\"{}\" -runDotNetAOT -mutex -platform={} -arch={} -configuration={} -aotMode={} -binaries=\"{}\" -intermediate=\"{}\""),
logFile, platform, architecture, configuration, ToString(aotMode), data.DataOutputPath, data.ManagedCodeOutputPath);
+ if (!buildSettings.SkipUnusedDotnetLibsPackaging)
+ args += TEXT(" -skipUnusedDotnetLibs=false"); // Run AOT on whole class library (not just used libs)
for (const String& define : data.CustomDefines)
{
args += TEXT(" -D");
diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h
index 90fb3c144..318c304e0 100644
--- a/Source/Engine/Core/Config/BuildSettings.h
+++ b/Source/Engine/Core/Config/BuildSettings.h
@@ -77,11 +77,17 @@ public:
bool ShadersGenerateDebugData = false;
///
- /// If checked, .NET 7 Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
+ /// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
///
API_FIELD(Attributes="EditorOrder(3000), EditorDisplay(\"Scripting\", \"Skip .NET Runtime Packaging\")")
bool SkipDotnetPackaging = false;
+ ///
+ /// If checked, .NET Runtime packaging will skip unused libraries from packaging resulting in smaller game builds.
+ ///
+ API_FIELD(Attributes="EditorOrder(3010), EditorDisplay(\"Scripting\", \"Skip Unused .NET Runtime Libs Packaging\")")
+ bool SkipUnusedDotnetLibsPackaging = true;
+
public:
///
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
@@ -101,5 +107,6 @@ public:
DESERIALIZE(ShadersNoOptimize);
DESERIALIZE(ShadersGenerateDebugData);
DESERIALIZE(SkipDotnetPackaging);
+ DESERIALIZE(SkipUnusedDotnetLibsPackaging);
}
};
diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs
index cab9a5c5f..c8ed9836a 100644
--- a/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs
+++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs
@@ -43,6 +43,12 @@ namespace Flax.Build
[CommandLine("aotMode", "")]
public static DotNetAOTModes AOTMode;
+ ///
+ /// See SkipUnusedDotnetLibsPackaging in BuildSettings.
+ ///
+ [CommandLine("skipUnusedDotnetLibs", "")]
+ public static bool SkipUnusedDotnetLibsPackaging = true;
+
///
/// Executes AOT process as a part of the game cooking (called by PrecompileAssembliesStep in Editor).
///
@@ -85,7 +91,7 @@ namespace Flax.Build
// Find input files
var inputFiles = Directory.GetFiles(aotAssembliesPath, "*.dll", SearchOption.TopDirectoryOnly).ToList();
- inputFiles.RemoveAll(x => x.EndsWith(".dll.dll") || Path.GetFileName(x) == "BuilderRulesCache.dll");
+ inputFiles.RemoveAll(FilterAssembly);
for (int i = 0; i < inputFiles.Count; i++)
inputFiles[i] = Utilities.NormalizePath(inputFiles[i]);
inputFiles.Sort();
@@ -239,27 +245,43 @@ namespace Flax.Build
// Build list of assemblies to process (use game assemblies as root to walk over used references from stdlib)
var assembliesPaths = new List();
- using (var assemblyResolver = new MonoCecil.BasicAssemblyResolver())
+ if (Configuration.SkipUnusedDotnetLibsPackaging)
{
- assemblyResolver.SearchDirectories.Add(aotAssembliesPath);
- assemblyResolver.SearchDirectories.Add(dotnetLibPath);
-
- var warnings = new HashSet();
- foreach (var inputFile in inputFiles)
+ // Use game assemblies as root to walk over used references from stdlib
+ using (var assemblyResolver = new MonoCecil.BasicAssemblyResolver())
{
- try
+ assemblyResolver.SearchDirectories.Add(aotAssembliesPath);
+ assemblyResolver.SearchDirectories.Add(dotnetLibPath);
+
+ var warnings = new HashSet();
+ foreach (var inputFile in inputFiles)
{
- BuildAssembliesList(inputFile, assembliesPaths, assemblyResolver, string.Empty, warnings);
- }
- catch (Exception)
- {
- Log.Error($"Failed to load assembly '{inputFile}'");
- throw;
+ try
+ {
+ BuildAssembliesList(inputFile, assembliesPaths, assemblyResolver, string.Empty, warnings);
+ }
+ catch (Exception)
+ {
+ Log.Error($"Failed to load assembly '{inputFile}'");
+ throw;
+ }
}
}
}
+ else
+ {
+ // Use all libs
+ 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]);
+ stdLibFiles.Sort();
+ assembliesPaths.AddRange(stdLibFiles);
+ assembliesPaths.AddRange(inputFiles);
+ }
// Run compilation
+ bool failed = false;
var compileAssembly = (string assemblyPath) =>
{
// Determinate whether use debug information for that assembly
@@ -297,7 +319,13 @@ namespace Flax.Build
// Run cross-compiler compiler
Log.Info(" * " + assemblyPath);
- Utilities.Run(aotCompilerPath, $"{aotCompilerArgs} \"{assemblyPath}\"", null, platformToolsRoot, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ThrowExceptionOnError | Utilities.RunOptions.ConsoleLogOutput, envVars);
+ int result = Utilities.Run(aotCompilerPath, $"{aotCompilerArgs} \"{assemblyPath}\"", null, platformToolsRoot, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput, envVars);
+ if (result != 0)
+ {
+ Log.Error("Failed to run AOT on assembly " + assemblyPath);
+ failed = true;
+ return;
+ }
}
// Skip if deployed file is already valid
@@ -323,6 +351,8 @@ namespace Flax.Build
foreach (var assemblyPath in assembliesPaths)
compileAssembly(assemblyPath);
}
+ if (failed)
+ throw new Exception($"Failed to run AOT. See log ({Configuration.LogFile}).");
}
else
{
@@ -334,6 +364,28 @@ namespace Flax.Build
Utilities.FileCopy(Path.Combine(aotAssembliesPath, "THIRD-PARTY-NOTICES.TXT"), Path.Combine(dotnetOutputPath, "THIRD-PARTY-NOTICES.TXT"));
}
+ internal static bool FilterAssembly(string x)
+ {
+ // Skip AOT output products
+ if (x.EndsWith(".dll.dll"))
+ return true;
+
+ // Skip Flax.Build rules assembly
+ if (Path.GetFileName(x) == "BuilderRulesCache.dll")
+ return true;
+
+ // Skip non-C# DLLs
+ try
+ {
+ using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(x, new ReaderParameters { ReadSymbols = false, InMemory = true, ReadingMode = ReadingMode.Deferred }))
+ return false;
+ }
+ catch
+ {
+ return true;
+ }
+ }
+
internal static void BuildAssembliesList(string assemblyPath, List outputList, IAssemblyResolver assemblyResolver, string callerPath, HashSet warnings)
{
// Skip if already processed