2023-03-28 13:24:16 -04:00
#if UNITY_EDITOR & & ENABLE_BURST_AOT
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Text ;
using System.Text.RegularExpressions ;
using UnityEditor ;
using UnityEditor.Android ;
using UnityEditor.Build ;
using UnityEditor.Build.Reporting ;
using UnityEditor.Compilation ;
using UnityEditor.Scripting ;
using UnityEditor.Scripting.ScriptCompilation ;
using UnityEditor.Scripting.Compilers ;
using UnityEditor.UnityLinker ;
using UnityEditor.Utils ;
using UnityEngine ;
using CompilerMessageType = UnityEditor . Scripting . Compilers . CompilerMessageType ;
using Debug = UnityEngine . Debug ;
#if UNITY_EDITOR_OSX
using System.ComponentModel ;
using Unity.Burst.LowLevel ;
using UnityEditor.Callbacks ;
#endif
namespace Unity.Burst.Editor
{
using static BurstCompilerOptions ;
internal class TargetCpus
{
public List < BurstTargetCpu > Cpus ;
public TargetCpus ( )
{
Cpus = new List < BurstTargetCpu > ( ) ;
}
public TargetCpus ( BurstTargetCpu single )
{
Cpus = new List < BurstTargetCpu > ( 1 )
{
single
} ;
}
public bool IsX86 ( )
{
foreach ( var cpu in Cpus )
{
switch ( cpu )
{
case BurstTargetCpu . X86_SSE2 :
case BurstTargetCpu . X86_SSE4 :
return true ;
}
}
return false ;
}
public override string ToString ( )
{
var result = "" ;
var first = true ;
foreach ( var cpu in Cpus )
{
if ( first )
{
result + = $"{cpu}" ;
first = false ;
}
else
{
result + = $", {cpu}" ;
}
}
return result ;
}
public TargetCpus Clone ( )
{
var copy = new TargetCpus
{
Cpus = new List < BurstTargetCpu > ( Cpus . Count )
} ;
foreach ( var cpu in Cpus )
{
copy . Cpus . Add ( cpu ) ;
}
return copy ;
}
}
2023-05-07 18:43:11 -04:00
#if ! ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
2023-03-28 13:24:16 -04:00
internal class LinkXMLGenerator : IUnityLinkerProcessor
{
public int callbackOrder = > 1 ;
public string GenerateAdditionalLinkXmlFile ( BuildReport report , UnityLinkerBuildPipelineData data )
{
var linkXml = Path . GetFullPath ( Path . Combine ( "Temp" , BurstAotCompiler . BurstLinkXmlName ) ) ;
return linkXml ;
}
public void OnBeforeRun ( BuildReport report , UnityLinkerBuildPipelineData data )
{
}
public void OnAfterRun ( BuildReport report , UnityLinkerBuildPipelineData data )
{
}
}
2023-05-07 18:43:11 -04:00
#endif
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
#if ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
internal class BurstAOTCompilerPostprocessor : IGenerateNativePluginsForAssemblies
#else
internal class BurstAOTCompilerPostprocessor : IPostBuildPlayerScriptDLLs
#endif
{
public int callbackOrder = > 0 ;
#if ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
public IGenerateNativePluginsForAssemblies . PrepareResult PrepareOnMainThread ( IGenerateNativePluginsForAssemblies . PrepareArgs args )
{
if ( ForceDisableBurstCompilation )
return new ( ) ;
DoSetup ( args . report ) ;
return new ( )
{
additionalInputFiles = new [ ]
{
// Any files in this list will be scanned for changes, and any changes in these files will trigger
// a rerun of the Burst compiler on the player build (even if the script assemblies have not changed).
//
// We add the settings so that changing any Burst setting will trigger a rebuild.
BurstPlatformAotSettings . GetPath ( BurstPlatformAotSettings . ResolveTarget ( settings . summary . platform ) ) ,
// We don't want to scan every file in the Burst package (though every file could potentially change
// behavior). When working on Burst code locally, you may need to select "Clean Build" in the Build
// settings window to force a rebuild to pick up the changes.
//
// But we add the compiler executable to have at least on file in the package. This should be good
// enough for users. Because any change in Burst will come with a change of the Burst package
// version, which will change the pathname for this file (which will then trigger a rebuild, even
// if the contents have not changed).
Path . Combine ( BurstLoader . RuntimePath , BurstAotCompiler . BurstAotCompilerExecutable ) ,
} ,
displayName = "Running Burst Compiler"
} ;
}
public IGenerateNativePluginsForAssemblies . GenerateResult GenerateNativePluginsForAssemblies ( IGenerateNativePluginsForAssemblies . GenerateArgs args )
{
if ( ForceDisableBurstCompilation )
return new ( ) ;
if ( Directory . Exists ( BurstAotCompiler . OutputBaseFolder ) )
Directory . Delete ( BurstAotCompiler . OutputBaseFolder , true ) ;
var assemblies = args . assemblyFiles . Select ( path = > new Assembly (
Path . GetFileNameWithoutExtension ( path ) ,
path ,
Array . Empty < string > ( ) ,
Array . Empty < string > ( ) ,
Array . Empty < Assembly > ( ) ,
Array . Empty < string > ( ) ,
UnityEditor . Compilation . AssemblyFlags . None ) )
// We don't run Burst on UnityEngine assemblies, so we skip them to save time
. Where ( a = > ! a . name . StartsWith ( "UnityEngine." ) )
. ToArray ( ) ;
return new ( ) { generatedPlugins = DoGenerate ( assemblies ) . ToArray ( ) } ;
}
#else
public void OnPostBuildPlayerScriptDLLs ( BuildReport report )
{
var step = report . BeginBuildStep ( "burst" ) ;
try
{
DoSetup ( report ) ;
DoGenerate ( BurstAotCompiler . GetPlayerAssemblies ( report ) )
. ToList ( ) ; // Force enumeration
}
finally
{
report . EndBuildStep ( step ) ;
}
}
#endif
private BurstAotCompiler . BurstAOTSettings settings ;
public void DoSetup ( BuildReport report )
{
settings = new BurstAotCompiler . BurstAOTSettings ( )
{
summary = report . summary ,
productName = PlayerSettings . productName
} ;
settings . aotSettingsForTarget = BurstPlatformAotSettings . GetOrCreateSettings ( settings . summary . platform ) ;
settings . isSupported = BurstAotCompiler . IsSupportedPlatform ( settings . summary . platform , settings . aotSettingsForTarget ) ;
if ( settings . isSupported )
{
settings . targetPlatform = BurstAotCompiler . GetTargetPlatformAndDefaultCpu ( settings . summary . platform ,
out settings . targetCpus , settings . aotSettingsForTarget ) ;
settings . combinations =
BurstAotCompiler . CollectCombinations ( settings . targetPlatform , settings . targetCpus ,
settings . summary ) ;
settings . scriptingBackend =
#if UNITY_2021_2_OR_NEWER
PlayerSettings . GetScriptingBackend ( NamedBuildTarget . FromBuildTargetGroup ( BuildPipeline . GetBuildTargetGroup ( settings . summary . platform ) ) ) ;
#else
PlayerSettings . GetScriptingBackend ( BuildPipeline . GetBuildTargetGroup ( settings . summary . platform ) ) ;
#endif
#if UNITY_IOS
if ( settings . targetPlatform = = TargetPlatform . iOS )
{
settings . extraOptions = new List < string > ( ) ;
settings . extraOptions . Add ( GetOption ( OptionLinkerOptions , $"min-ios-version={PlayerSettings.iOS.targetOSVersionString}" ) ) ;
}
#endif
#if UNITY_TVOS
if ( settings . targetPlatform = = TargetPlatform . tvOS )
{
settings . extraOptions = new List < string > ( ) ;
settings . extraOptions . Add ( GetOption ( OptionLinkerOptions , $"min-tvos-version={PlayerSettings.tvOS.targetOSVersionString}" ) ) ;
}
#endif
#if UNITY_2022_2_OR_NEWER & & UNITY_ANDROID
if ( settings . targetPlatform = = TargetPlatform . Android )
{
// Enable Armv9 security features (PAC/BTI) if needed
settings . aotSettingsForTarget . EnableArmv9SecurityFeatures = PlayerSettings . Android . enableArmv9SecurityFeatures ;
}
#endif
if ( settings . targetPlatform = = TargetPlatform . UWP )
{
settings . extraOptions = new List < string > ( ) ;
if ( ! string . IsNullOrEmpty ( EditorUserBuildSettings . wsaUWPVisualStudioVersion ) )
{
settings . extraOptions . Add ( GetOption ( OptionLinkerOptions , $"vs-version={EditorUserBuildSettings.wsaUWPVisualStudioVersion}" ) ) ;
}
if ( ! string . IsNullOrEmpty ( EditorUserBuildSettings . wsaUWPSDK ) )
{
settings . extraOptions . Add ( GetOption ( OptionLinkerOptions , $"target-sdk-version={EditorUserBuildSettings.wsaUWPSDK}" ) ) ;
}
}
}
}
public IEnumerable < string > DoGenerate ( Assembly [ ] assemblies )
{
if ( ! settings . isSupported )
return Array . Empty < string > ( ) ;
return BurstAotCompiler . OnPostBuildPlayerScriptDLLsImpl ( settings , assemblies ) ;
}
}
#if ! ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
2023-03-28 13:24:16 -04:00
internal class BurstAndroidGradlePostprocessor : IPostGenerateGradleAndroidProject
{
int IOrderedCallback . callbackOrder = > 1 ;
void IPostGenerateGradleAndroidProject . OnPostGenerateGradleAndroidProject ( string path )
{
var aotSettingsForTarget = BurstPlatformAotSettings . GetOrCreateSettings ( BuildTarget . Android ) ;
// Early exit if burst is not activated
2023-05-07 18:43:11 -04:00
if ( BurstCompilerOptions . ForceDisableBurstCompilation | | ! aotSettingsForTarget . EnableBurstCompilation )
2023-03-28 13:24:16 -04:00
{
return ;
}
// Copy bursted .so's from tempburstlibs to the actual location in the gradle project
var sourceLocation = Path . GetFullPath ( Path . Combine ( "Temp" , "StagingArea" , "tempburstlibs" ) ) ;
var targetLocation = Path . GetFullPath ( Path . Combine ( path , "src" , "main" , "jniLibs" ) ) ;
FileUtil . CopyDirectoryRecursive ( sourceLocation , targetLocation , true ) ;
}
}
// For static builds, there are two different approaches:
// Postprocessing adds the libraries after Unity is done building,
// for platforms that need to build a project file, etc.
// Preprocessing simply adds the libraries to the Unity build,
// for platforms where Unity can directly build an app.
internal class StaticPreProcessor : IPreprocessBuildWithReport
{
private const string TempSourceLibrary = @"Temp/StagingArea/SourcePlugins" ;
public int callbackOrder { get { return 0 ; } }
public void OnPreprocessBuild ( BuildReport report )
{
var aotSettingsForTarget = BurstPlatformAotSettings . GetOrCreateSettings ( report . summary . platform ) ;
// Early exit if burst is not activated
2023-05-07 18:43:11 -04:00
if ( BurstCompilerOptions . ForceDisableBurstCompilation | | ! aotSettingsForTarget . EnableBurstCompilation )
2023-03-28 13:24:16 -04:00
{
return ;
}
if ( report . summary . platform = = BuildTarget . Switch )
{
if ( ! Directory . Exists ( TempSourceLibrary ) )
{
Directory . CreateDirectory ( TempSourceLibrary ) ;
Directory . CreateDirectory ( TempSourceLibrary ) ;
}
2023-05-07 18:43:11 -04:00
BurstAotCompiler . WriteStaticLinkCppFile ( TempSourceLibrary ) ;
2023-03-28 13:24:16 -04:00
}
}
}
2023-05-07 18:43:11 -04:00
#endif
2023-03-28 13:24:16 -04:00
/// <summary>
/// Integration of the burst AOT compiler into the Unity build player pipeline
/// </summary>
2023-05-07 18:43:11 -04:00
internal class BurstAotCompiler
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
internal const string BurstAotCompilerExecutable = "bcl.exe" ;
#if ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
// When using the new player build API, don't write to Temp/StagingArea.
// We still need code in Unity to support old versions of Burst not using the new API.
// for that case, we will just pick up files written to the Temp/StagingArea.
// So in order to not pick up files twice, use a different output location for the new
// API.
internal const string OutputBaseFolder = @"Temp/BurstOutput/" ;
#else
private const string OutputBaseFolder = @"Temp/StagingArea/" ;
#endif
private const string TempStagingManaged = OutputBaseFolder + @"Data/Managed/" ;
2023-03-28 13:24:16 -04:00
private const string LibraryPlayerScriptAssemblies = "Library/PlayerScriptAssemblies" ;
private const string TempManagedSymbols = @"Temp/ManagedSymbols/" ;
internal const string BurstLinkXmlName = "burst.link.xml" ;
2023-05-07 18:43:11 -04:00
internal struct BurstAOTSettings
{
public BuildSummary summary ;
public BurstPlatformAotSettings aotSettingsForTarget ;
public TargetPlatform targetPlatform ;
public TargetCpus targetCpus ;
public List < BurstAotCompiler . BurstOutputCombination > combinations ;
public ScriptingImplementation scriptingBackend ;
public string productName ;
public bool isSupported ;
public List < string > extraOptions ;
}
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
static void CopyDirectory ( string sourceDir , string destinationDir , bool recursive )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
// Get information about the source directory
var dir = new DirectoryInfo ( sourceDir ) ;
// Check if the source directory exists
if ( ! dir . Exists )
throw new DirectoryNotFoundException ( $"Source directory not found: {dir.FullName}" ) ;
// Cache directories before we start copying
DirectoryInfo [ ] dirs = dir . GetDirectories ( ) ;
// Create the destination directory
Directory . CreateDirectory ( destinationDir ) ;
// Get the files in the source directory and copy to the destination directory
foreach ( FileInfo file in dir . GetFiles ( ) )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
string targetFilePath = Path . Combine ( destinationDir , file . Name ) ;
file . CopyTo ( targetFilePath ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
// If recursive and copying subdirectories, recursively call this method
if ( recursive )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
foreach ( DirectoryInfo subDir in dirs )
{
string newDestinationDir = Path . Combine ( destinationDir , subDir . Name ) ;
CopyDirectory ( subDir . FullName , newDestinationDir , true ) ;
}
2023-03-28 13:24:16 -04:00
}
}
2023-05-07 18:43:11 -04:00
internal static IEnumerable < string > OnPostBuildPlayerScriptDLLsImpl ( BurstAOTSettings settings , Assembly [ ] playerAssemblies )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
var buildTarget = settings . summary . platform ;
2023-03-28 13:24:16 -04:00
string burstMiscAlongsidePath = "" ;
2023-05-07 18:43:11 -04:00
if ( ( settings . summary . options & BuildOptions . InstallInBuildFolder ) = = 0 )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
burstMiscAlongsidePath = BurstPlatformAotSettings . FetchOutputPath ( settings . summary ) ;
2023-03-28 13:24:16 -04:00
}
HashSet < string > assemblyDefines = new HashSet < string > ( ) ;
// Early exit if burst is not activated or the platform is not supported
2023-05-07 18:43:11 -04:00
if ( BurstCompilerOptions . ForceDisableBurstCompilation | | ! settings . aotSettingsForTarget . EnableBurstCompilation )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
return Array . Empty < string > ( ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
var isDevelopmentBuild = ( settings . summary . options & BuildOptions . Development ) ! = 0 ;
2023-03-28 13:24:16 -04:00
var commonOptions = new List < string > ( ) ;
var stagingFolder = Path . GetFullPath ( TempStagingManaged ) ;
// grab the location of the root of the player folder - for handling nda platforms that require keys
var keyFolder = BuildPipeline . GetPlaybackEngineDirectory ( buildTarget , BuildOptions . None ) ;
commonOptions . Add ( GetOption ( OptionAotKeyFolder , keyFolder ) ) ;
commonOptions . Add ( GetOption ( OptionAotDecodeFolder , Path . Combine ( Environment . CurrentDirectory , "Library" , "Burst" ) ) ) ;
// Extract the TargetPlatform and Cpus from the current build settings
2023-05-07 18:43:11 -04:00
commonOptions . Add ( GetOption ( OptionPlatform , settings . targetPlatform ) ) ;
2023-03-28 13:24:16 -04:00
// --------------------------------------------------------------------------------------------------------
// 1) Calculate AssemblyFolders
// These are the folders to look for assembly resolution
// --------------------------------------------------------------------------------------------------------
var assemblyFolders = new List < string > { stagingFolder } ;
2023-05-07 18:43:11 -04:00
foreach ( var assembly in playerAssemblies )
AddAssemblyFolder ( assembly . outputPath , stagingFolder , buildTarget , assemblyFolders ) ;
if ( buildTarget = = BuildTarget . WSAPlayer | | buildTarget = = BuildTarget . GameCoreXboxOne | | buildTarget = = BuildTarget . GameCoreXboxSeries )
2023-03-28 13:24:16 -04:00
{
// On UWP, not all assemblies are copied to StagingArea, so we want to
// find all directories that we can reference assemblies from
// If we don't do this, we will crash with AssemblyResolutionException
// when following type references.
foreach ( var assembly in playerAssemblies )
{
foreach ( var assemblyRef in assembly . compiledAssemblyReferences )
2023-05-07 18:43:11 -04:00
AddAssemblyFolder ( assemblyRef , stagingFolder , buildTarget , assemblyFolders ) ;
2023-03-28 13:24:16 -04:00
}
}
2023-05-07 18:43:11 -04:00
if ( settings . extraOptions ! = null )
{
commonOptions . AddRange ( settings . extraOptions ) ;
}
2023-03-28 13:24:16 -04:00
// Copy assembly used during staging to have a trace
if ( BurstLoader . IsDebugging )
{
try
{
var copyAssemblyFolder = Path . Combine ( Environment . CurrentDirectory , "Logs" , "StagingAssemblies" ) ;
try
{
if ( Directory . Exists ( copyAssemblyFolder ) ) Directory . Delete ( copyAssemblyFolder ) ;
}
catch
{
}
if ( ! Directory . Exists ( copyAssemblyFolder ) ) Directory . CreateDirectory ( copyAssemblyFolder ) ;
foreach ( var file in Directory . EnumerateFiles ( stagingFolder ) )
{
File . Copy ( file , Path . Combine ( copyAssemblyFolder , Path . GetFileName ( file ) ) ) ;
}
}
catch
{
}
}
// --------------------------------------------------------------------------------------------------------
// 2) Calculate root assemblies
// These are the assemblies that the compiler will look for methods to compile
// This list doesn't typically include .NET runtime assemblies but only assemblies compiled as part
// of the current Unity project
// --------------------------------------------------------------------------------------------------------
var rootAssemblies = new List < string > ( ) ;
foreach ( var playerAssembly in playerAssemblies )
{
2023-05-07 18:43:11 -04:00
#if ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
var playerAssemblyPath = Path . GetFullPath ( playerAssembly . outputPath ) ;
#else
2023-03-28 13:24:16 -04:00
// the file at path `playerAssembly.outputPath` is actually not on the disk
// while it is in the staging folder because OnPostBuildPlayerScriptDLLs is being called once the files are already
// transferred to the staging folder, so we are going to work from it but we are reusing the file names that we got earlier
2023-05-07 18:43:11 -04:00
var playerAssemblyPath = Path . Combine ( stagingFolder , Path . GetFileName ( playerAssembly . outputPath ) ) ;
#endif
if ( ! File . Exists ( playerAssemblyPath ) )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
Debug . LogWarning ( $"Unable to find player assembly: {playerAssembly.outputPath}" ) ;
2023-03-28 13:24:16 -04:00
}
else
{
2023-05-07 18:43:11 -04:00
rootAssemblies . Add ( playerAssemblyPath ) ;
2023-03-28 13:24:16 -04:00
assemblyDefines . UnionWith ( playerAssembly . defines ) ;
}
}
2023-05-07 18:43:11 -04:00
commonOptions . AddRange ( rootAssemblies . Select ( root = > GetOption ( OptionRootAssembly , root ) ) ) ;
2023-03-28 13:24:16 -04:00
commonOptions . AddRange ( assemblyDefines . Select ( define = > GetOption ( OptionCompilationDefines , define ) ) ) ;
// --------------------------------------------------------------------------------------------------------
// 4) Compile each combination
//
// Here bcl.exe is called for each target CPU combination
// --------------------------------------------------------------------------------------------------------
string debugLogFile = null ;
if ( BurstLoader . IsDebugging )
{
// Reset log files
try
{
var logDir = Path . Combine ( Environment . CurrentDirectory , "Logs" ) ;
debugLogFile = Path . Combine ( logDir , "burst_bcl_editor.log" ) ;
if ( ! Directory . Exists ( logDir ) ) Directory . CreateDirectory ( logDir ) ;
File . WriteAllText ( debugLogFile , string . Empty ) ;
}
catch
{
debugLogFile = null ;
}
}
2023-05-07 18:43:11 -04:00
if ( ( settings . summary . options & BuildOptions . InstallInBuildFolder ) = = 0 )
2023-03-28 13:24:16 -04:00
{
CreateFolderForMiscFiles ( burstMiscAlongsidePath ) ;
}
// Log the targets generated by BurstReflection.FindExecuteMethods
2023-05-07 18:43:11 -04:00
foreach ( var combination in settings . combinations )
2023-03-28 13:24:16 -04:00
{
// Gets the output folder
2023-05-07 18:43:11 -04:00
var stagingOutputFolder = Path . GetFullPath ( Path . Combine ( OutputBaseFolder , combination . OutputPath ) ) ;
2023-03-28 13:24:16 -04:00
var outputFilePrefix = Path . Combine ( stagingOutputFolder , combination . LibraryName ) ;
var options = new List < string > ( commonOptions )
{
GetOption ( OptionAotOutputPath , outputFilePrefix ) ,
GetOption ( OptionTempDirectory , Path . Combine ( Environment . CurrentDirectory , "Temp" , "Burst" ) )
} ;
foreach ( var cpu in combination . TargetCpus . Cpus )
{
options . Add ( GetOption ( OptionTarget , cpu ) ) ;
}
2023-05-07 18:43:11 -04:00
if ( settings . targetPlatform = = TargetPlatform . iOS | | settings . targetPlatform = = TargetPlatform . tvOS | | settings . targetPlatform = = TargetPlatform . Switch )
2023-03-28 13:24:16 -04:00
{
options . Add ( GetOption ( OptionStaticLinkage ) ) ;
2023-05-07 18:43:11 -04:00
#if ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
WriteStaticLinkCppFile ( $"{OutputBaseFolder}/{combination.OutputPath}" ) ;
#endif
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
if ( settings . targetPlatform = = TargetPlatform . Windows )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionLinkerOptions , $"PdbAltPath=\" { settings . productName } _ { combination . OutputPath } / { Path . GetFileNameWithoutExtension ( combination . LibraryName ) } . pdb \ "" ) ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
#if UNITY_2022_2_OR_NEWER & & UNITY_ANDROID
if ( settings . targetPlatform = = TargetPlatform . Android )
{
// Enable Armv9 security features (PAC/BTI) if needed
if ( settings . aotSettingsForTarget . EnableArmv9SecurityFeatures )
options . Add ( GetOption ( OptionBranchProtection , "Standard" ) ) ;
}
#endif
options . AddRange ( assemblyFolders . Select ( assemblyFolder = > GetOption ( OptionAotAssemblyFolder , assemblyFolder ) ) ) ;
2023-03-28 13:24:16 -04:00
// Set the flag to print a message on missing MonoPInvokeCallback attribute on IL2CPP only
2023-05-07 18:43:11 -04:00
if ( settings . scriptingBackend = = ScriptingImplementation . IL2CPP )
2023-03-28 13:24:16 -04:00
{
options . Add ( GetOption ( OptionPrintLogOnMissingPInvokeCallbackAttribute ) ) ;
}
// Log the targets generated by BurstReflection.FindExecuteMethods
if ( BurstLoader . IsDebugging & & debugLogFile ! = null )
{
try
{
var writer = new StringWriter ( ) ;
writer . WriteLine ( "-----------------------------------------------------------" ) ;
writer . WriteLine ( "Combination: " + combination ) ;
writer . WriteLine ( "-----------------------------------------------------------" ) ;
foreach ( var option in options )
{
writer . WriteLine ( option ) ;
}
writer . WriteLine ( "Assemblies in AssemblyFolders:" ) ;
foreach ( var assemblyFolder in assemblyFolders )
{
writer . WriteLine ( "|- Folder: " + assemblyFolder ) ;
foreach ( var assemblyOrDll in Directory . EnumerateFiles ( assemblyFolder , "*.dll" ) )
{
var fileInfo = new FileInfo ( assemblyOrDll ) ;
writer . WriteLine ( " |- " + assemblyOrDll + " Size: " + fileInfo . Length + " Date: " + fileInfo . LastWriteTime ) ;
}
}
File . AppendAllText ( debugLogFile , writer . ToString ( ) ) ;
}
catch
{
// ignored
}
}
// Allow burst to find managed symbols in the backup location in case the symbols are stripped in the build location
options . Add ( GetOption ( OptionAotPdbSearchPaths , TempManagedSymbols ) ) ;
if ( isDevelopmentBuild & & Environment . GetEnvironmentVariable ( "UNITY_BURST_ENABLE_SAFETY_CHECKS_IN_PLAYER_BUILD" ) ! = null )
{
options . Add ( "--global-safety-checks-setting=ForceOn" ) ;
}
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionGenerateLinkXml , Path . Combine ( "Temp" , BurstLinkXmlName ) ) ) ;
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
if ( ! string . IsNullOrWhiteSpace ( settings . aotSettingsForTarget . DisabledWarnings ) )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionDisableWarnings , settings . aotSettingsForTarget . DisabledWarnings ) ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
if ( isDevelopmentBuild | | settings . aotSettingsForTarget . EnableDebugInAllBuilds )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
if ( ! isDevelopmentBuild )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
Debug . LogWarning (
"Symbols are being generated for burst compiled code, please ensure you intended this - see Burst AOT settings." ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionDebug ,
( settings . aotSettingsForTarget . DebugDataKind = = DebugDataKind . Full ) & & ( ! combination . WorkaroundFullDebugInfo ) ? "Full" : "LineOnly" ) ) ;
}
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
if ( ! settings . aotSettingsForTarget . EnableOptimisations )
{
options . Add ( GetOption ( OptionDisableOpt ) ) ;
}
else
{
switch ( settings . aotSettingsForTarget . OptimizeFor )
2023-03-28 13:24:16 -04:00
{
case OptimizeFor . Default :
case OptimizeFor . Balanced :
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionOptLevel , 2 ) ) ;
2023-03-28 13:24:16 -04:00
break ;
case OptimizeFor . Performance :
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionOptLevel , 3 ) ) ;
2023-03-28 13:24:16 -04:00
break ;
case OptimizeFor . Size :
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionOptForSize ) ) ;
options . Add ( GetOption ( OptionOptLevel , 3 ) ) ;
2023-03-28 13:24:16 -04:00
break ;
case OptimizeFor . FastCompilation :
2023-05-07 18:43:11 -04:00
options . Add ( GetOption ( OptionOptLevel , 1 ) ) ;
2023-03-28 13:24:16 -04:00
break ;
}
2023-05-07 18:43:11 -04:00
}
if ( BurstLoader . IsDebugging )
{
options . Add ( GetOption ( "debug-logging" ) ) ;
}
// Write current options to the response file
var responseFile = Path . GetTempFileName ( ) ;
File . WriteAllLines ( responseFile , options ) ;
if ( BurstLoader . IsDebugging )
{
Debug . Log ( $"bcl.exe {OptionBurstcSwitch} @{responseFile}\n\nResponse File:\n" + string . Join ( "\n" , options ) ) ;
}
try
{
var burstcSwitch = OptionBurstcSwitch ;
if ( ! string . IsNullOrEmpty (
Environment . GetEnvironmentVariable ( "UNITY_BURST_DISABLE_INCREMENTAL_PLAYER_BUILDS" ) ) )
{
burstcSwitch = "" ;
}
2023-03-28 13:24:16 -04:00
BclRunner . RunManagedProgram ( Path . Combine ( BurstLoader . RuntimePath , BurstAotCompilerExecutable ) ,
2023-05-07 18:43:11 -04:00
$"{burstcSwitch} {BclRunner.EscapeForShell(" @" + responseFile)}" ,
2023-03-28 13:24:16 -04:00
new BclOutputErrorParser ( ) ) ;
// Additionally copy the pdb to the root of the player build so run in editor also locates the symbols
var pdbPath = $"{Path.Combine(stagingOutputFolder, combination.LibraryName)}.pdb" ;
if ( File . Exists ( pdbPath ) )
{
2023-05-07 18:43:11 -04:00
var dstPath = Path . Combine ( OutputBaseFolder , $"{combination.LibraryName}.pdb" ) ;
2023-03-28 13:24:16 -04:00
File . Copy ( pdbPath , dstPath , overwrite : true ) ;
}
}
catch ( BuildFailedException )
{
throw ;
}
catch ( Exception e )
{
throw new BuildFailedException ( e ) ;
}
}
2023-05-07 18:43:11 -04:00
PostProcessCombinations ( settings . targetPlatform , settings . combinations , settings . summary ) ;
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
var pdbsRemainInBuild = isDevelopmentBuild | | settings . aotSettingsForTarget . EnableDebugInAllBuilds | | settings . targetPlatform = = TargetPlatform . UWP ;
2023-03-28 13:24:16 -04:00
// Finally move out any symbols/misc files from the final output
2023-05-07 18:43:11 -04:00
if ( ( settings . summary . options & BuildOptions . InstallInBuildFolder ) = = 0 )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
return CollateMiscFiles ( settings . combinations , burstMiscAlongsidePath , pdbsRemainInBuild ) ;
}
return Array . Empty < string > ( ) ;
}
private static void AddAssemblyFolder ( string assemblyRef , string stagingFolder , BuildTarget buildTarget ,
List < string > assemblyFolders )
{
// Exclude folders with assemblies already compiled in the `folder`
var assemblyName = Path . GetFileName ( assemblyRef ) ;
if ( assemblyName ! = null & & File . Exists ( Path . Combine ( stagingFolder , assemblyName ) ) )
{
return ;
}
var directory = Path . GetDirectoryName ( assemblyRef ) ;
if ( directory ! = null )
{
var fullPath = Path . GetFullPath ( directory ) ;
if ( IsMonoReferenceAssemblyDirectory ( fullPath ) | | IsDotNetStandardAssemblyDirectory ( fullPath ) )
{
// Don't pass reference assemblies to burst because they contain methods without implementation
// If burst accidentally resolves them, it will emit calls to burst_abort.
fullPath = Path . Combine ( EditorApplication . applicationContentsPath , "MonoBleedingEdge/lib/mono" ) ;
#if UNITY_2021_2_OR_NEWER
// In 2021.2 we got multiple mono distributions, per platform.
fullPath = Path . Combine ( fullPath , "unityaot-" + BuildTargetDiscovery . GetPlatformProfileSuffix ( buildTarget ) ) ;
#else
fullPath = Path . Combine ( fullPath , "unityaot" ) ;
#endif
fullPath = Path . GetFullPath ( fullPath ) ; // GetFullPath will normalize path separators to OS native format
if ( ! assemblyFolders . Contains ( fullPath ) )
assemblyFolders . Add ( fullPath ) ;
fullPath = Path . Combine ( fullPath , "Facades" ) ;
if ( ! assemblyFolders . Contains ( fullPath ) )
assemblyFolders . Add ( fullPath ) ;
}
else if ( ! assemblyFolders . Contains ( fullPath ) )
{
assemblyFolders . Add ( fullPath ) ;
}
2023-03-28 13:24:16 -04:00
}
}
private static void CreateFolderForMiscFiles ( string finalFolder )
{
try
{
if ( Directory . Exists ( finalFolder ) ) Directory . Delete ( finalFolder , true ) ;
}
catch
{
}
Directory . CreateDirectory ( finalFolder ) ;
}
2023-05-07 18:43:11 -04:00
private static IEnumerable < string > CollateMiscFiles ( List < BurstOutputCombination > combinations , string finalFolder , bool retainPdbs )
2023-03-28 13:24:16 -04:00
{
foreach ( var combination in combinations )
{
2023-05-07 18:43:11 -04:00
var inputPath = Path . GetFullPath ( Path . Combine ( OutputBaseFolder , combination . OutputPath ) ) ;
2023-03-28 13:24:16 -04:00
var outputPath = Path . Combine ( finalFolder , combination . OutputPath ) ;
Directory . CreateDirectory ( outputPath ) ;
2023-05-07 18:43:11 -04:00
if ( ! Directory . Exists ( inputPath ) )
continue ;
2023-03-28 13:24:16 -04:00
var files = Directory . GetFiles ( inputPath ) ;
var directories = Directory . GetDirectories ( inputPath ) ;
foreach ( var fileName in files )
{
var lowerCase = fileName . ToLower ( ) ;
if ( ( ! retainPdbs & & lowerCase . EndsWith ( ".pdb" ) ) | | lowerCase . EndsWith ( ".dsym" ) | | lowerCase . EndsWith ( ".txt" ) )
{
// Move the file out of the staging area so its not included in the build
File . Move ( fileName , Path . Combine ( outputPath , Path . GetFileName ( fileName ) ) ) ;
}
2023-05-07 18:43:11 -04:00
else if ( ! combination . CollateDirectory )
{
yield return fileName ;
}
2023-03-28 13:24:16 -04:00
}
foreach ( var fileName in directories )
{
var lowerCase = fileName . ToLower ( ) ;
if ( ( ! retainPdbs & & lowerCase . EndsWith ( ".pdb" ) ) | | lowerCase . EndsWith ( ".dsym" ) | | lowerCase . EndsWith ( ".txt" ) )
{
// Move the folder out of the staging area so its not included in the build
Directory . Move ( fileName , Path . Combine ( outputPath , Path . GetFileName ( fileName ) ) ) ;
}
2023-05-07 18:43:11 -04:00
else if ( ! combination . CollateDirectory )
{
yield return fileName ;
}
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
if ( combination . CollateDirectory )
yield return inputPath ;
2023-03-28 13:24:16 -04:00
}
}
private static bool AndroidHasX86 ( AndroidArchitecture architecture )
{
// Deal with rename that occured
AndroidArchitecture val ;
if ( AndroidArchitecture . TryParse ( "X86" , out val ) )
{
return ( architecture & val ) ! = 0 ;
}
else if ( AndroidArchitecture . TryParse ( "x86" , out val ) )
{
return ( architecture & val ) ! = 0 ;
}
return false ;
}
private static bool AndroidHasX86_64 ( AndroidArchitecture architecture )
{
// Deal with rename that occured
AndroidArchitecture val ;
if ( AndroidArchitecture . TryParse ( "X86_64" , out val ) )
{
return ( architecture & val ) ! = 0 ;
}
else if ( AndroidArchitecture . TryParse ( "x86_64" , out val ) )
{
return ( architecture & val ) ! = 0 ;
}
return false ;
}
private enum SimulatorPlatforms
{
iOS ,
tvOS
}
private static bool IsForSimulator ( BuildTarget target )
{
switch ( target )
{
case BuildTarget . iOS :
return IsForSimulator ( SimulatorPlatforms . iOS ) ;
case BuildTarget . tvOS :
return IsForSimulator ( SimulatorPlatforms . tvOS ) ;
default :
return false ;
}
}
private static bool IsForSimulator ( TargetPlatform targetPlatform )
{
switch ( targetPlatform )
{
case TargetPlatform . iOS :
return IsForSimulator ( SimulatorPlatforms . iOS ) ;
case TargetPlatform . tvOS :
return IsForSimulator ( SimulatorPlatforms . tvOS ) ;
default :
return false ;
}
}
private static bool IsForSimulator ( SimulatorPlatforms simulatorPlatforms )
{
switch ( simulatorPlatforms )
{
case SimulatorPlatforms . iOS :
return UnityEditor . PlayerSettings . iOS . sdkVersion = = iOSSdkVersion . SimulatorSDK ;
case SimulatorPlatforms . tvOS :
return UnityEditor . PlayerSettings . tvOS . sdkVersion = = tvOSSdkVersion . Simulator ;
}
return false ;
}
2023-05-07 18:43:11 -04:00
public static void WriteStaticLinkCppFile ( string dir )
{
Directory . CreateDirectory ( dir ) ;
string cppPath = Path . Combine ( dir , "lib_burst_generated.cpp" ) ;
// Additionally we need a small cpp file (weak symbols won't unfortunately override directly from the libs
//presumably due to link order?
File . WriteAllText ( cppPath , @ "
extern "" C ""
{
void Staticburst_initialize ( void * ) ;
void * StaticBurstStaticMethodLookup ( void * ) ;
int burst_enable_static_linkage = 1 ;
void burst_initialize ( void * i ) { Staticburst_initialize ( i ) ; }
void * BurstStaticMethodLookup ( void * i ) { return StaticBurstStaticMethodLookup ( i ) ; }
}
");
}
2023-03-28 13:24:16 -04:00
/// <summary>
/// Collect CPU combinations for the specified TargetPlatform and TargetCPU
/// </summary>
/// <param name="targetPlatform">The target platform (e.g Windows)</param>
/// <param name="targetCpus">The target CPUs (e.g X64_SSE4)</param>
/// <param name="report">Error reporting</param>
/// <returns>The list of CPU combinations</returns>
2023-05-07 18:43:11 -04:00
internal static List < BurstOutputCombination > CollectCombinations ( TargetPlatform targetPlatform , TargetCpus targetCpus , BuildSummary summary )
2023-03-28 13:24:16 -04:00
{
var combinations = new List < BurstOutputCombination > ( ) ;
if ( targetPlatform = = TargetPlatform . macOS )
{
// NOTE: OSX has a special folder for the plugin
// Declared in GetStagingAreaPluginsFolder
// PlatformDependent\OSXPlayer\Extensions\Managed\OSXDesktopStandalonePostProcessor.cs
2023-05-07 18:43:11 -04:00
var outputPath = Path . Combine ( Path . GetFileName ( summary . outputPath ) , "Contents" , "Plugins" ) ;
2023-03-28 13:24:16 -04:00
// Based on : PlatformDependent/OSXPlayer/Extension/OSXStandaloneBuildWindowExtension.cs
var aotSettings = BurstPlatformAotSettings . GetOrCreateSettings ( BuildTarget . StandaloneOSX ) ;
var buildTargetName = BuildPipeline . GetBuildTargetName ( BuildTarget . StandaloneOSX ) ;
var architecture = EditorUserBuildSettings . GetPlatformSettings ( buildTargetName , "Architecture" ) . ToLowerInvariant ( ) ;
switch ( architecture )
{
case "x64" :
combinations . Add ( new BurstOutputCombination ( outputPath , aotSettings . GetDesktopCpu64Bit ( ) ) ) ;
break ;
case "arm64" :
2023-05-07 18:43:11 -04:00
// According to
// https://web.archive.org/web/20220504192056/https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Support/AArch64TargetParser.def#L240
// M1 is equivalent to Armv8.5-A, so it supports everything from HALFFP target
// (there's no direct confirmation on crypto because it's not mandatory)
combinations . Add ( new BurstOutputCombination ( outputPath , new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64_HALFFP ) ) ) ;
2023-03-28 13:24:16 -04:00
break ;
default :
combinations . Add ( new BurstOutputCombination ( Path . Combine ( outputPath , "x64" ) , aotSettings . GetDesktopCpu64Bit ( ) ) ) ;
2023-05-07 18:43:11 -04:00
combinations . Add ( new BurstOutputCombination ( Path . Combine ( outputPath , "arm64" ) , new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64_HALFFP ) ) ) ;
2023-03-28 13:24:16 -04:00
break ;
}
}
else if ( targetPlatform = = TargetPlatform . iOS | | targetPlatform = = TargetPlatform . tvOS )
{
if ( IsForSimulator ( targetPlatform ) )
{
Debug . LogWarning ( "Burst Does not currently support the simulator, burst is disabled for this build." ) ;
}
else if ( Application . platform ! = RuntimePlatform . OSXEditor )
{
Debug . LogWarning ( "Burst Cross Compilation to iOS/tvOS for standalone player, is only supported on OSX Editor at this time, burst is disabled for this build." ) ;
}
else
{
2023-05-07 18:43:11 -04:00
// Looks like a way to detect iOS CPU capabilities in runtime (like getauxval()) is sysctlbyname()
// https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics
// TODO: add support for it when needed, for now using the lowest common denominator
// https://web.archive.org/web/20220504192056/https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Support/AArch64TargetParser.def#L240
// This LLVM code implies A11 is the first Armv8.2-A CPU
// However, it doesn't support dotprod, so we can't consider it equivalent to our HALFFP variant
// A13 (equivalent to Armv8.4-A) and M1 seem to be the first CPUs we can claim HALFFP compatible
// Since we need to support older CPUs, have to use the "basic" Armv8A here
combinations . Add ( new BurstOutputCombination ( "StaticLibraries" , new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ) ) ;
2023-03-28 13:24:16 -04:00
}
}
else if ( targetPlatform = = TargetPlatform . Android )
{
// TODO: would be better to query AndroidNdkRoot (but thats not exposed from unity)
string ndkRoot = null ;
var targetAPILevel = PlayerSettings . Android . GetMinTargetAPILevel ( ) ;
2023-05-07 18:43:11 -04:00
#if UNITY_ANDROID
2023-03-28 13:24:16 -04:00
ndkRoot = UnityEditor . Android . AndroidExternalToolsSettings . ndkRootPath ;
2023-05-07 18:43:11 -04:00
#else
2023-03-28 13:24:16 -04:00
// 2019.1 now has an embedded ndk
if ( EditorPrefs . HasKey ( "NdkUseEmbedded" ) )
{
if ( EditorPrefs . GetBool ( "NdkUseEmbedded" ) )
{
ndkRoot = Path . Combine ( BuildPipeline . GetPlaybackEngineDirectory ( BuildTarget . Android , BuildOptions . None ) , "NDK" ) ;
}
else
{
ndkRoot = EditorPrefs . GetString ( "AndroidNdkRootR16b" ) ;
}
}
#endif
// If we still don't have a valid root, try the old key
if ( string . IsNullOrEmpty ( ndkRoot ) )
{
ndkRoot = EditorPrefs . GetString ( "AndroidNdkRoot" ) ;
}
// Verify the directory at least exists, if not we fall back to ANDROID_NDK_ROOT current setting
if ( ! string . IsNullOrEmpty ( ndkRoot ) & & ! Directory . Exists ( ndkRoot ) )
{
ndkRoot = null ;
}
// Always set the ANDROID_NDK_ROOT (if we got a valid result from above), so BCL knows where to find the Android toolchain and its the one the user expects
if ( ! string . IsNullOrEmpty ( ndkRoot ) )
{
Environment . SetEnvironmentVariable ( "ANDROID_NDK_ROOT" , ndkRoot ) ;
}
Environment . SetEnvironmentVariable ( "BURST_ANDROID_MIN_API_LEVEL" , $"{targetAPILevel}" ) ;
// Setting tempburstlibs/ as the interim target directory
// Don't target libs/ directly because incremental build pipeline doesn't expect the so's at that path
// Rather, so's are copied to the actual location in the gradle project in BurstAndroidGradlePostprocessor
var androidTargetArch = PlayerSettings . Android . targetArchitectures ;
if ( ( androidTargetArch & AndroidArchitecture . ARMv7 ) ! = 0 )
{
2023-05-07 18:43:11 -04:00
combinations . Add ( new BurstOutputCombination ( "tempburstlibs/armeabi-v7a" , new TargetCpus ( BurstTargetCpu . ARMV7A_NEON32 ) , collateDirectory : true ) ) ;
2023-03-28 13:24:16 -04:00
}
if ( ( androidTargetArch & AndroidArchitecture . ARM64 ) ! = 0 )
{
2023-05-07 18:43:11 -04:00
var aotSettingsForTarget = BurstPlatformAotSettings . GetOrCreateSettings ( summary . platform ) ;
combinations . Add ( new BurstOutputCombination ( "tempburstlibs/arm64-v8a" , aotSettingsForTarget . GetAndroidCpuArm64 ( ) , collateDirectory : true ) ) ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
#if UNITY_2019_4_OR_NEWER
2023-03-28 13:24:16 -04:00
if ( AndroidHasX86 ( androidTargetArch ) )
{
2023-05-07 18:43:11 -04:00
combinations . Add ( new BurstOutputCombination ( "tempburstlibs/x86" , new TargetCpus ( BurstTargetCpu . X86_SSE4 ) , collateDirectory : true ) ) ;
2023-03-28 13:24:16 -04:00
}
if ( AndroidHasX86_64 ( androidTargetArch ) )
{
2023-05-07 18:43:11 -04:00
combinations . Add ( new BurstOutputCombination ( "tempburstlibs/x86_64" , new TargetCpus ( BurstTargetCpu . X64_SSE4 ) , collateDirectory : true ) ) ;
2023-03-28 13:24:16 -04:00
}
#endif
}
else if ( targetPlatform = = TargetPlatform . UWP )
{
2023-05-07 18:43:11 -04:00
var aotSettingsForTarget = BurstPlatformAotSettings . GetOrCreateSettings ( summary . platform ) ;
2023-03-28 13:24:16 -04:00
if ( EditorUserBuildSettings . wsaUWPBuildType = = WSAUWPBuildType . ExecutableOnly )
{
2023-05-07 18:43:11 -04:00
combinations . Add ( new BurstOutputCombination ( $"Plugins/{GetUWPTargetArchitecture()}" , targetCpus , collateDirectory : true ) ) ;
2023-03-28 13:24:16 -04:00
}
else
{
2023-05-07 18:43:11 -04:00
combinations . Add ( new BurstOutputCombination ( "Plugins/x64" , aotSettingsForTarget . GetDesktopCpu64Bit ( ) , collateDirectory : true ) ) ;
combinations . Add ( new BurstOutputCombination ( "Plugins/x86" , aotSettingsForTarget . GetDesktopCpu32Bit ( ) , collateDirectory : true ) ) ;
combinations . Add ( new BurstOutputCombination ( "Plugins/ARM" , new TargetCpus ( BurstTargetCpu . THUMB2_NEON32 ) , collateDirectory : true ) ) ;
combinations . Add ( new BurstOutputCombination ( "Plugins/ARM64" , new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) , collateDirectory : true ) ) ;
2023-03-28 13:24:16 -04:00
}
}
2023-05-07 18:43:11 -04:00
#if ! UNITY_2022_2_OR_NEWER
2023-03-28 13:24:16 -04:00
else if ( targetPlatform = = TargetPlatform . Lumin )
{
// Set the LUMINSDK_UNITY so bcl.exe will be able to find the SDK
if ( string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "LUMINSDK_UNITY" ) ) )
{
var sdkRoot = EditorPrefs . GetString ( "LuminSDKRoot" ) ;
if ( ! string . IsNullOrEmpty ( sdkRoot ) )
{
Environment . SetEnvironmentVariable ( "LUMINSDK_UNITY" , sdkRoot ) ;
}
}
combinations . Add ( new BurstOutputCombination ( "Data/Plugins/" , targetCpus ) ) ;
}
2023-05-07 18:43:11 -04:00
#endif
2023-03-28 13:24:16 -04:00
else if ( targetPlatform = = TargetPlatform . Switch )
{
combinations . Add ( new BurstOutputCombination ( "NativePlugins/" , targetCpus ) ) ;
}
else
{
if ( targetPlatform = = TargetPlatform . Windows )
{
// This is what is expected by PlatformDependent\Win\Plugins.cpp
if ( targetCpus . IsX86 ( ) )
{
2023-05-07 18:43:11 -04:00
combinations . Add ( new BurstOutputCombination ( "Data/Plugins/x86" , targetCpus , collateDirectory : true ) ) ;
2023-03-28 13:24:16 -04:00
}
else
{
2023-05-07 18:43:11 -04:00
var windowsArchitecture = GetWindows64BitTargetArchitecture ( ) ;
if ( string . Equals ( windowsArchitecture , "ARM64" , StringComparison . OrdinalIgnoreCase ) )
{
combinations . Add ( new BurstOutputCombination ( "Data/Plugins/ARM64" , targetCpus , collateDirectory : true , workaroundBrokenDebug : true ) ) ;
}
else
{
combinations . Add ( new BurstOutputCombination ( "Data/Plugins/x86_64" , targetCpus , collateDirectory : true ) ) ;
}
2023-03-28 13:24:16 -04:00
}
}
else
{
// Safeguard
combinations . Add ( new BurstOutputCombination ( "Data/Plugins/" , targetCpus ) ) ;
}
}
return combinations ;
}
2023-05-07 18:43:11 -04:00
private static void PostProcessCombinations ( TargetPlatform targetPlatform , List < BurstOutputCombination > combinations , BuildSummary summary )
2023-03-28 13:24:16 -04:00
{
if ( targetPlatform = = TargetPlatform . macOS & & combinations . Count > 1 )
{
// Figure out which files we need to lipo
string outputSymbolsDir = null ;
2023-05-07 18:43:11 -04:00
var outputDir = Path . Combine ( OutputBaseFolder , Path . GetFileName ( summary . outputPath ) , "Contents" , "Plugins" ) ;
2023-03-28 13:24:16 -04:00
var sliceCount = combinations . Count ;
var binarySlices = new string [ sliceCount ] ;
var debugSymbolSlices = new string [ sliceCount ] ;
for ( int i = 0 ; i < sliceCount ; i + + )
{
var slice = combinations [ i ] ;
var binaryFileName = slice . LibraryName + ".bundle" ;
2023-05-07 18:43:11 -04:00
var binaryPath = Path . Combine ( OutputBaseFolder , slice . OutputPath , binaryFileName ) ;
2023-03-28 13:24:16 -04:00
binarySlices [ i ] = binaryPath ;
// Only attempt to lipo symbols if they actually exist
var dsymPath = binaryPath + ".dsym" ;
var debugSymbolsPath = Path . Combine ( dsymPath , "Contents" , "Resources" , "DWARF" , binaryFileName ) ;
if ( File . Exists ( debugSymbolsPath ) )
{
if ( string . IsNullOrWhiteSpace ( outputSymbolsDir ) )
{
// Copy over the symbols from the first combination for metadata files which we aren't merging, like Info.plist
var outputDsymPath = Path . Combine ( outputDir , binaryFileName + ".dsym" ) ;
2023-05-07 18:43:11 -04:00
CopyDirectory ( dsymPath , outputDsymPath , true ) ;
2023-03-28 13:24:16 -04:00
outputSymbolsDir = Path . Combine ( outputDsymPath , "Contents" , "Resources" , "DWARF" ) ;
}
debugSymbolSlices [ i ] = debugSymbolsPath ;
}
}
// lipo combinations together
var outBinaryFileName = combinations [ 0 ] . LibraryName + ".bundle" ;
RunLipo ( binarySlices , Path . Combine ( outputDir , outBinaryFileName ) ) ;
if ( ! string . IsNullOrWhiteSpace ( outputSymbolsDir ) )
RunLipo ( debugSymbolSlices , Path . Combine ( outputSymbolsDir , outBinaryFileName ) ) ;
// Remove single-slice binary so they don't end up in the build
for ( int i = 0 ; i < sliceCount ; i + + )
2023-05-07 18:43:11 -04:00
Directory . Delete ( Path . GetDirectoryName ( binarySlices [ i ] ) , true ) ;
2023-03-28 13:24:16 -04:00
// Since we have combined the files, we need to adjust combinations for the next step
var outFolder = Path . GetDirectoryName ( combinations [ 0 ] . OutputPath ) ; // remove platform folder
combinations . Clear ( ) ;
combinations . Add ( new BurstOutputCombination ( outFolder , new TargetCpus ( ) ) ) ;
}
}
private static void RunLipo ( string [ ] inputFiles , string outputFile )
{
var outputDir = Path . GetDirectoryName ( outputFile ) ;
Directory . CreateDirectory ( outputDir ) ;
var cmdLine = new StringBuilder ( ) ;
foreach ( var input in inputFiles )
{
if ( string . IsNullOrEmpty ( input ) )
continue ;
cmdLine . Append ( BclRunner . EscapeForShell ( input ) ) ;
cmdLine . Append ( ' ' ) ;
}
cmdLine . Append ( "-create -output " ) ;
cmdLine . Append ( BclRunner . EscapeForShell ( outputFile ) ) ;
string lipoPath ;
var currentEditorPlatform = Application . platform ;
switch ( currentEditorPlatform )
{
case RuntimePlatform . LinuxEditor :
lipoPath = Path . Combine ( BurstLoader . RuntimePath , "hostlin" , "llvm-lipo" ) ;
break ;
case RuntimePlatform . OSXEditor :
lipoPath = Path . Combine ( BurstLoader . RuntimePath , "hostmac" , "llvm-lipo" ) ;
break ;
case RuntimePlatform . WindowsEditor :
lipoPath = Path . Combine ( BurstLoader . RuntimePath , "hostwin" , "llvm-lipo.exe" ) ;
break ;
default :
throw new NotSupportedException ( "Unknown Unity editor platform: " + currentEditorPlatform ) ;
}
BclRunner . RunNativeProgram ( lipoPath , cmdLine . ToString ( ) , null ) ;
}
2023-05-07 18:43:11 -04:00
internal static Assembly [ ] GetPlayerAssemblies ( BuildReport report )
2023-03-28 13:24:16 -04:00
{
// We need to build the list of root assemblies based from the "PlayerScriptAssemblies" folder.
// This is so we compile the versions of the library built for the individual platforms, not the editor version.
var oldOutputDir = EditorCompilationInterface . GetCompileScriptsOutputDirectory ( ) ;
try
{
EditorCompilationInterface . SetCompileScriptsOutputDirectory ( LibraryPlayerScriptAssemblies ) ;
var shouldIncludeTestAssemblies = report . summary . options . HasFlag ( BuildOptions . IncludeTestAssemblies ) ;
#if UNITY_2021_1_OR_NEWER
// Workaround that with 'Server Build' ticked in the build options, since there is no 'AssembliesType.Server'
// enum, we need to manually add the BuildingForHeadlessPlayer compilation option.
#if UNITY_2022_1_OR_NEWER
var isHeadless = report . summary . subtarget = = ( int ) StandaloneBuildSubtarget . Server ;
#elif UNITY_2021_2_OR_NEWER
// A really really really gross hack - thanks Cristian Mazo! Querying the BuildOptions.EnableHeadlessMode is
// obselete, but accessing its integer value is not... Note: this is just the temporary workaround to unblock
// us (as of 1st June 2021, I say this with **much hope** that it is indeed temporary!).
var isHeadless = report . summary . options . HasFlag ( ( BuildOptions ) 16384 ) ;
#else
var isHeadless = report . summary . options . HasFlag ( BuildOptions . EnableHeadlessMode ) ;
#endif
if ( isHeadless )
{
var compilationOptions = EditorCompilationInterface . GetAdditionalEditorScriptCompilationOptions ( ) ;
compilationOptions | = EditorScriptCompilationOptions . BuildingForHeadlessPlayer ;
if ( shouldIncludeTestAssemblies )
{
compilationOptions | = EditorScriptCompilationOptions . BuildingIncludingTestAssemblies ;
}
return CompilationPipeline . ToAssemblies ( CompilationPipeline . GetScriptAssemblies ( EditorCompilationInterface . Instance , compilationOptions ) ) ;
}
else
{
return CompilationPipeline . GetAssemblies ( shouldIncludeTestAssemblies ? AssembliesType . Player : AssembliesType . PlayerWithoutTestAssemblies ) ;
}
2023-05-07 18:43:11 -04:00
#else
2023-03-28 13:24:16 -04:00
// Workaround that with 'Server Build' ticked in the build options, since there is no 'AssembliesType.Server'
// enum, we need to manually add the 'UNITY_SERVER' define to the player assembly search list.
if ( report . summary . options . HasFlag ( BuildOptions . EnableHeadlessMode ) )
{
var compilationOptions = EditorCompilationInterface . GetAdditionalEditorScriptCompilationOptions ( ) ;
if ( shouldIncludeTestAssemblies )
{
compilationOptions | = EditorScriptCompilationOptions . BuildingIncludingTestAssemblies ;
}
return CompilationPipeline . GetPlayerAssemblies ( EditorCompilationInterface . Instance , compilationOptions , new string [ ] { "UNITY_SERVER" } ) ;
}
else
{
return CompilationPipeline . GetAssemblies ( shouldIncludeTestAssemblies ? AssembliesType . Player : AssembliesType . PlayerWithoutTestAssemblies ) ;
}
#endif
}
finally
{
EditorCompilationInterface . SetCompileScriptsOutputDirectory ( oldOutputDir ) ; // restore output directory back to original value
}
}
private static bool IsMonoReferenceAssemblyDirectory ( string path )
{
var editorDir = Path . GetFullPath ( EditorApplication . applicationContentsPath ) ;
return path . IndexOf ( editorDir , StringComparison . OrdinalIgnoreCase ) ! = - 1 & & path . IndexOf ( "MonoBleedingEdge" , StringComparison . OrdinalIgnoreCase ) ! = - 1 & & path . IndexOf ( "-api" , StringComparison . OrdinalIgnoreCase ) ! = - 1 ;
}
private static bool IsDotNetStandardAssemblyDirectory ( string path )
{
var editorDir = Path . GetFullPath ( EditorApplication . applicationContentsPath ) ;
return path . IndexOf ( editorDir , StringComparison . OrdinalIgnoreCase ) ! = - 1 & & path . IndexOf ( "netstandard" , StringComparison . OrdinalIgnoreCase ) ! = - 1 & & path . IndexOf ( "shims" , StringComparison . OrdinalIgnoreCase ) ! = - 1 ;
}
2023-05-07 18:43:11 -04:00
internal static TargetPlatform GetTargetPlatformAndDefaultCpu ( BuildTarget target , out TargetCpus targetCpu , BurstPlatformAotSettings aotSettingsForTarget )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
var platform = TryGetTargetPlatform ( target , out targetCpu , aotSettingsForTarget ) ;
2023-03-28 13:24:16 -04:00
if ( ! platform . HasValue )
{
throw new NotSupportedException ( "The target platform " + target + " is not supported by the burst compiler" ) ;
}
return platform . Value ;
}
2023-05-07 18:43:11 -04:00
internal static bool IsSupportedPlatform ( BuildTarget target , BurstPlatformAotSettings aotSettingsForTarget )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
return TryGetTargetPlatform ( target , out var _ , aotSettingsForTarget ) . HasValue ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
private static TargetPlatform ? TryGetTargetPlatform ( BuildTarget target , out TargetCpus targetCpus , BurstPlatformAotSettings aotSettingsForTarget )
2023-03-28 13:24:16 -04:00
{
switch ( target )
{
case BuildTarget . StandaloneWindows :
targetCpus = aotSettingsForTarget . GetDesktopCpu32Bit ( ) ;
return TargetPlatform . Windows ;
case BuildTarget . StandaloneWindows64 :
2023-05-07 18:43:11 -04:00
var windowsArchitecture = GetWindows64BitTargetArchitecture ( ) ;
if ( string . Equals ( windowsArchitecture , "x64" , StringComparison . OrdinalIgnoreCase ) )
{
targetCpus = aotSettingsForTarget . GetDesktopCpu64Bit ( ) ;
}
else if ( string . Equals ( windowsArchitecture , "ARM64" , StringComparison . OrdinalIgnoreCase ) )
{
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ;
}
else
{
throw new InvalidOperationException ( "Unknown Windows 64 Bit CPU architecture: " + windowsArchitecture ) ;
}
2023-03-28 13:24:16 -04:00
return TargetPlatform . Windows ;
case BuildTarget . StandaloneOSX :
targetCpus = aotSettingsForTarget . GetDesktopCpu64Bit ( ) ;
return TargetPlatform . macOS ;
case BuildTarget . StandaloneLinux64 :
targetCpus = aotSettingsForTarget . GetDesktopCpu64Bit ( ) ;
return TargetPlatform . Linux ;
case BuildTarget . WSAPlayer :
{
var uwpArchitecture = GetUWPTargetArchitecture ( ) ;
if ( string . Equals ( uwpArchitecture , "x64" , StringComparison . OrdinalIgnoreCase ) )
{
targetCpus = aotSettingsForTarget . GetDesktopCpu64Bit ( ) ;
}
else if ( string . Equals ( uwpArchitecture , "x86" , StringComparison . OrdinalIgnoreCase ) )
{
targetCpus = aotSettingsForTarget . GetDesktopCpu32Bit ( ) ;
}
else if ( string . Equals ( uwpArchitecture , "ARM" , StringComparison . OrdinalIgnoreCase ) )
{
targetCpus = new TargetCpus ( BurstTargetCpu . THUMB2_NEON32 ) ;
}
else if ( string . Equals ( uwpArchitecture , "ARM64" , StringComparison . OrdinalIgnoreCase ) )
{
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ;
}
else
{
throw new InvalidOperationException ( "Unknown UWP CPU architecture: " + uwpArchitecture ) ;
}
return TargetPlatform . UWP ;
}
case BuildTarget . GameCoreXboxOne :
targetCpus = new TargetCpus ( BurstTargetCpu . AVX ) ;
return TargetPlatform . GameCoreXboxOne ;
case BuildTarget . GameCoreXboxSeries :
targetCpus = new TargetCpus ( BurstTargetCpu . AVX2 ) ;
return TargetPlatform . GameCoreXboxSeries ;
case BuildTarget . PS4 :
targetCpus = new TargetCpus ( BurstTargetCpu . X64_SSE4 ) ;
return TargetPlatform . PS4 ;
case BuildTarget . Android :
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV7A_NEON32 ) ;
return TargetPlatform . Android ;
case BuildTarget . iOS :
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV7A_NEON32 ) ;
return TargetPlatform . iOS ;
case BuildTarget . tvOS :
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ;
return TargetPlatform . tvOS ;
2023-05-07 18:43:11 -04:00
#if ! UNITY_2022_2_OR_NEWER
2023-03-28 13:24:16 -04:00
case BuildTarget . Lumin :
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ;
return TargetPlatform . Lumin ;
2023-05-07 18:43:11 -04:00
#endif
2023-03-28 13:24:16 -04:00
case BuildTarget . Switch :
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ;
return TargetPlatform . Switch ;
case BuildTarget . PS5 :
targetCpus = new TargetCpus ( BurstTargetCpu . AVX2 ) ;
return TargetPlatform . PS5 ;
2023-05-07 18:43:11 -04:00
}
#if UNITY_2022_1_OR_NEWER
const int qnxTarget = ( int ) BuildTarget . QNX ;
#else
const int qnxTarget = 46 ;
2023-03-28 13:24:16 -04:00
#endif
2023-05-07 18:43:11 -04:00
if ( qnxTarget = = ( int ) target )
{
// QNX is supported on 2019.4 (shadow branch), 2020.3 (shadow branch) and 2022.1+ (official).
var qnxArchitecture = GetQNXTargetArchitecture ( ) ;
if ( "Arm64" = = qnxArchitecture )
{
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ;
}
else if ( "X64" = = qnxArchitecture )
{
targetCpus = new TargetCpus ( BurstTargetCpu . X64_SSE4 ) ;
}
else if ( "X86" = = qnxArchitecture )
{
targetCpus = new TargetCpus ( BurstTargetCpu . X86_SSE4 ) ;
}
else if ( "Arm32" = = qnxArchitecture )
{
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV7A_NEON32 ) ;
}
else
{
throw new InvalidOperationException ( "Unknown QNX CPU architecture: " + qnxArchitecture ) ;
}
return TargetPlatform . QNX ;
2023-03-28 13:24:16 -04:00
}
2023-05-07 18:43:11 -04:00
#if UNITY_2021_2_OR_NEWER
const int embeddedLinuxTarget = ( int ) BuildTarget . EmbeddedLinux ;
#else
const int embeddedLinuxTarget = 45 ;
#endif
if ( embeddedLinuxTarget = = ( int ) target )
2023-03-28 13:24:16 -04:00
{
//EmbeddedLinux is supported on 2019.4 (shadow branch), 2020.3 (shadow branch) and 2021.2+ (official).
var embeddedLinuxArchitecture = GetEmbeddedLinuxTargetArchitecture ( ) ;
if ( "Arm64" = = embeddedLinuxArchitecture )
{
targetCpus = new TargetCpus ( BurstTargetCpu . ARMV8A_AARCH64 ) ;
}
else if ( "X64" = = embeddedLinuxArchitecture )
{
targetCpus = new TargetCpus ( BurstTargetCpu . X64_SSE2 ) ; //lowest supported for now
}
else if ( ( "X86" = = embeddedLinuxArchitecture ) | | ( "Arm32" = = embeddedLinuxArchitecture ) )
{
//32bit platforms cannot be support with the current SDK/Toolchain combination.
//i686-embedded-linux-gnu/8.3.0\libgcc.a(_moddi3.o + _divdi3.o): contains a compressed section, but zlib is not available
//_moddi3.o + _divdi3.o are required by LLVM for 64bit operations on 32bit platforms.
throw new InvalidOperationException ( $"No EmbeddedLinux Burst Support on {embeddedLinuxArchitecture} architecture." ) ;
}
else
{
throw new InvalidOperationException ( "Unknown EmbeddedLinux CPU architecture: " + embeddedLinuxArchitecture ) ;
}
return TargetPlatform . EmbeddedLinux ;
}
targetCpus = new TargetCpus ( BurstTargetCpu . Auto ) ;
return null ;
}
2023-05-07 18:43:11 -04:00
private static string GetWindows64BitTargetArchitecture ( )
2023-03-28 13:24:16 -04:00
{
2023-05-07 18:43:11 -04:00
var buildTargetName = BuildPipeline . GetBuildTargetName ( BuildTarget . StandaloneWindows64 ) ;
var architecture = EditorUserBuildSettings . GetPlatformSettings ( buildTargetName , "Architecture" ) . ToLowerInvariant ( ) ;
if ( string . Equals ( architecture , "x64" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( architecture , "ARM64" , StringComparison . OrdinalIgnoreCase ) )
{
return architecture ;
}
// Default to x64 if editor user build setting is garbage
return "x64" ;
2023-03-28 13:24:16 -04:00
}
private static string GetUWPTargetArchitecture ( )
{
var architecture = EditorUserBuildSettings . wsaArchitecture ;
if ( string . Equals ( architecture , "x64" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( architecture , "x86" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( architecture , "ARM" , StringComparison . OrdinalIgnoreCase ) | |
string . Equals ( architecture , "ARM64" , StringComparison . OrdinalIgnoreCase ) )
{
return architecture ;
}
// Default to x64 if editor user build setting is garbage
return "x64" ;
}
private static string GetEmbeddedLinuxTargetArchitecture ( )
{
var flags = System . Reflection . BindingFlags . Public |
System . Reflection . BindingFlags . Static |
System . Reflection . BindingFlags . FlattenHierarchy ;
var property = typeof ( EditorUserBuildSettings ) . GetProperty ( "selectedEmbeddedLinuxArchitecture" , flags ) ;
if ( null = = property )
{
return "NOT_FOUND" ;
}
var value = ( int ) property . GetValue ( null , null ) ;
switch ( value )
{
case /*UnityEditor.EmbeddedLinuxArchitecture.Arm64*/ 0 : return "Arm64" ;
case /*UnityEditor.EmbeddedLinuxArchitecture.Arm32*/ 1 : return "Arm32" ;
case /*UnityEditor.EmbeddedLinuxArchitecture.X64*/ 2 : return "X64" ;
case /*UnityEditor.EmbeddedLinuxArchitecture.X86*/ 3 : return "X86" ;
default : return $"UNKNOWN_{value}" ;
}
}
2023-05-07 18:43:11 -04:00
private static string GetQNXTargetArchitecture ( )
{
var flags = System . Reflection . BindingFlags . Public |
System . Reflection . BindingFlags . Static |
System . Reflection . BindingFlags . FlattenHierarchy ;
var property = typeof ( EditorUserBuildSettings ) . GetProperty ( "selectedQnxArchitecture" , flags ) ;
if ( null = = property )
{
return "NOT_FOUND" ;
}
var value = ( int ) property . GetValue ( null , null ) ;
switch ( value )
{
case /*UnityEditor.QNXArchitecture.Arm64*/ 0 : return "Arm64" ;
case /*UnityEditor.QNXArchitecture.Arm32*/ 1 : return "Arm32" ;
case /*UnityEditor.QNXArchitecture.X64*/ 2 : return "X64" ;
case /*UnityEditor.QNXArchitecture.X86*/ 3 : return "X86" ;
default : return $"UNKNOWN_{value}" ;
}
}
2023-03-28 13:24:16 -04:00
/// <summary>
/// Defines an output path (for the generated code) and the target CPU
/// </summary>
2023-05-07 18:43:11 -04:00
internal struct BurstOutputCombination
2023-03-28 13:24:16 -04:00
{
public readonly TargetCpus TargetCpus ;
public readonly string OutputPath ;
public readonly string LibraryName ;
2023-05-07 18:43:11 -04:00
public readonly bool CollateDirectory ;
public readonly bool WorkaroundFullDebugInfo ;
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
public BurstOutputCombination ( string outputPath , TargetCpus targetCpus , string libraryName = DefaultLibraryName , bool collateDirectory = false , bool workaroundBrokenDebug = false )
2023-03-28 13:24:16 -04:00
{
TargetCpus = targetCpus . Clone ( ) ;
OutputPath = outputPath ;
LibraryName = libraryName ;
2023-05-07 18:43:11 -04:00
CollateDirectory = collateDirectory ;
WorkaroundFullDebugInfo = workaroundBrokenDebug ;
2023-03-28 13:24:16 -04:00
}
public override string ToString ( )
{
return $"{nameof(TargetCpus)}: {TargetCpus}, {nameof(OutputPath)}: {OutputPath}, {nameof(LibraryName)}: {LibraryName}" ;
}
}
private class BclRunner
{
private static readonly Regex MatchVersion = new Regex ( @"com.unity.burst@(\d+.*?)[\\/]" ) ;
public static void RunManagedProgram ( string exe , string args , CompilerOutputParserBase parser )
{
RunManagedProgram ( exe , args , Application . dataPath + "/.." , parser ) ;
}
private static void RunManagedProgram (
string exe ,
string args ,
string workingDirectory ,
CompilerOutputParserBase parser )
{
Program p ;
if ( Application . platform = = RuntimePlatform . WindowsEditor )
{
ProcessStartInfo si = new ProcessStartInfo ( )
{
Arguments = args ,
CreateNoWindow = true ,
FileName = exe
} ;
p = new Program ( si ) ;
}
else
{
p = ( Program ) new ManagedProgram ( MonoInstallationFinder . GetMonoInstallation ( "MonoBleedingEdge" ) , ( string ) null , exe , args , false , null ) ;
}
RunProgram ( p , exe , args , workingDirectory , parser ) ;
}
public static void RunNativeProgram ( string exe , string args , CompilerOutputParserBase parser )
{
RunNativeProgram ( exe , args , Application . dataPath + "/.." , parser ) ;
}
private static void RunNativeProgram ( string exePath , string arguments , string workingDirectory , CompilerOutputParserBase parser )
{
// On non Windows platform, make sure that the command is executable
// This is a workaround - occasionally the execute bits are lost from our package
if ( Application . platform ! = RuntimePlatform . WindowsEditor & & Path . IsPathRooted ( exePath ) )
{
var escapedExePath = EscapeForShell ( exePath , singleQuoteWrapped : true ) ;
var shArgs = $"-c '[ ! -x {escapedExePath} ] && chmod 755 {escapedExePath}'" ;
var p = new Program ( new ProcessStartInfo ( "sh" , shArgs ) { CreateNoWindow = true } ) ;
p . GetProcessStartInfo ( ) . WorkingDirectory = workingDirectory ;
p . Start ( ) ;
p . WaitForExit ( ) ;
}
var startInfo = new ProcessStartInfo ( exePath , arguments ) ;
startInfo . CreateNoWindow = true ;
RunProgram ( new Program ( startInfo ) , exePath , arguments , workingDirectory , parser ) ;
}
public static string EscapeForShell ( string s , bool singleQuoteWrapped = false )
{
// On Windows it's enough to enclose the path in double quotes (double quotes are not allowed in paths)
if ( Application . platform = = RuntimePlatform . WindowsEditor ) return $"\" { s } \ "" ;
// On non-windows platforms we enclose in single-quotes and escape any existing single quotes with: '\'':
// John's Folder => 'John'\''s Folder'
var sb = new StringBuilder ( ) ;
var escaped = s . Replace ( "'" , "'\\''" ) ;
sb . Append ( '\'' ) ;
sb . Append ( escaped ) ;
sb . Append ( '\'' ) ;
// If the outer-context is already wrapped in single-quotes, we need to double escape things:
// John's Folder => 'John'\''s Folder'
// => '\''John'\''\'\'''\''s Folder'\''
if ( singleQuoteWrapped )
{
// Pain
return sb . ToString ( ) . Replace ( "'" , "'\\''" ) ;
}
return sb . ToString ( ) ;
}
public static void RunProgram (
Program p ,
string exe ,
string args ,
string workingDirectory ,
CompilerOutputParserBase parser )
{
Stopwatch stopwatch = new Stopwatch ( ) ;
stopwatch . Start ( ) ;
using ( p )
{
p . GetProcessStartInfo ( ) . WorkingDirectory = workingDirectory ;
p . Start ( ) ;
p . WaitForExit ( ) ;
stopwatch . Stop ( ) ;
Console . WriteLine ( "{0} exited after {1} ms." , ( object ) exe , ( object ) stopwatch . ElapsedMilliseconds ) ;
IEnumerable < UnityEditor . Scripting . Compilers . CompilerMessage > compilerMessages = null ;
string [ ] errorOutput = p . GetErrorOutput ( ) ;
string [ ] standardOutput = p . GetStandardOutput ( ) ;
if ( parser ! = null )
{
compilerMessages = parser . Parse ( errorOutput , standardOutput , true , "n/a (burst)" ) ;
}
var errorMessageBuilder = new StringBuilder ( ) ;
if ( compilerMessages ! = null )
{
foreach ( UnityEditor . Scripting . Compilers . CompilerMessage compilerMessage in compilerMessages )
{
switch ( compilerMessage . type )
{
case CompilerMessageType . Warning :
Debug . LogWarning ( compilerMessage . message , compilerMessage . file , compilerMessage . line , compilerMessage . column ) ;
break ;
case CompilerMessageType . Error :
Debug . LogPlayerBuildError ( compilerMessage . message , compilerMessage . file , compilerMessage . line , compilerMessage . column ) ;
break ;
}
}
}
if ( p . ExitCode ! = 0 )
{
// We try to output the version in the heading error if we can
var matchVersion = MatchVersion . Match ( exe ) ;
errorMessageBuilder . Append ( matchVersion . Success ?
"Burst compiler (" + matchVersion . Groups [ 1 ] . Value + ") failed running" :
"Burst compiler failed running" ) ;
errorMessageBuilder . AppendLine ( ) ;
errorMessageBuilder . AppendLine ( ) ;
// Don't output the path if we are not burst-debugging or the exe exist
if ( BurstLoader . IsDebugging | | ! File . Exists ( exe ) )
{
errorMessageBuilder . Append ( exe ) . Append ( " " ) . Append ( args ) ;
errorMessageBuilder . AppendLine ( ) ;
errorMessageBuilder . AppendLine ( ) ;
}
errorMessageBuilder . AppendLine ( "stdout:" ) ;
foreach ( string str in standardOutput )
errorMessageBuilder . AppendLine ( str ) ;
errorMessageBuilder . AppendLine ( "stderr:" ) ;
foreach ( string str in errorOutput )
errorMessageBuilder . AppendLine ( str ) ;
throw new BuildFailedException ( errorMessageBuilder . ToString ( ) ) ;
}
Console . WriteLine ( p . GetAllOutput ( ) ) ;
}
}
}
/// <summary>
/// Internal class used to parse bcl output errors
/// </summary>
private class BclOutputErrorParser : CompilerOutputParserBase
{
// Format of an error message:
//
//C:\work\burst\src\Burst.Compiler.IL.Tests\Program.cs(17,9): error: Loading a managed string literal is not supported by burst
// at Buggy.NiceBug() (at C:\work\burst\src\Burst.Compiler.IL.Tests\Program.cs:17)
//
//
// [1] [2] [3] [4] [5]
// path line col type message
private static readonly Regex MatchLocation = new Regex ( @"^(.*?)\((\d+)\s*,\s*(\d+)\):\s*([\w\s]+)\s*:\s*(.*)" ) ;
// Matches " at "
private static readonly Regex MatchAt = new Regex ( @"^\s+at\s+" ) ;
public override IEnumerable < UnityEditor . Scripting . Compilers . CompilerMessage > Parse (
string [ ] errorOutput ,
string [ ] standardOutput ,
bool compilationHadFailure ,
string assemblyName )
{
var messages = new List < UnityEditor . Scripting . Compilers . CompilerMessage > ( ) ;
var textBuilder = new StringBuilder ( ) ;
for ( var i = 0 ; i < errorOutput . Length ; i + + )
{
string line = errorOutput [ i ] ;
var message = new UnityEditor . Scripting . Compilers . CompilerMessage { assemblyName = assemblyName } ;
// If we are able to match a location, we can decode it including the following attached " at " lines
textBuilder . Clear ( ) ;
var match = MatchLocation . Match ( line ) ;
if ( match . Success )
{
var path = match . Groups [ 1 ] . Value ;
int . TryParse ( match . Groups [ 2 ] . Value , out message . line ) ;
int . TryParse ( match . Groups [ 3 ] . Value , out message . column ) ;
if ( match . Groups [ 4 ] . Value . Contains ( "error" ) )
{
message . type = CompilerMessageType . Error ;
}
else
{
message . type = CompilerMessageType . Warning ;
}
message . file = ! string . IsNullOrEmpty ( path ) ? path : "unknown" ;
// Replace '\' with '/' to let the editor open the file
message . file = message . file . Replace ( '\\' , '/' ) ;
// Make path relative to project path path
var projectPath = Path . GetDirectoryName ( Application . dataPath ) ? . Replace ( '\\' , '/' ) ;
if ( projectPath ! = null & & message . file . StartsWith ( projectPath ) )
{
message . file = message . file . Substring ( projectPath . EndsWith ( "/" ) ? projectPath . Length : projectPath . Length + 1 ) ;
}
// debug
// textBuilder.AppendLine("line: " + message.line + " column: " + message.column + " error: " + message.type + " file: " + message.file);
textBuilder . Append ( match . Groups [ 5 ] . Value ) ;
}
else
{
// Don't output any blank line
if ( string . IsNullOrWhiteSpace ( line ) )
{
continue ;
}
// Otherwise we output an error, but without source location information
// so that at least the user can see it directly in the log errors
message . type = CompilerMessageType . Error ;
message . line = 0 ;
message . column = 0 ;
message . file = "unknown" ;
textBuilder . Append ( line ) ;
}
// Collect attached location call context information ("at ...")
// we do it for both case (as if we have an exception in bcl we want to print this in a single line)
bool isFirstAt = true ;
for ( int j = i + 1 ; j < errorOutput . Length ; j + + )
{
var nextLine = errorOutput [ j ] ;
// Empty lines are ignored by the stack trace parser.
if ( string . IsNullOrWhiteSpace ( nextLine ) )
{
i + + ;
continue ;
}
if ( MatchAt . Match ( nextLine ) . Success )
{
i + + ;
if ( isFirstAt )
{
textBuilder . AppendLine ( ) ;
isFirstAt = false ;
}
textBuilder . AppendLine ( nextLine ) ;
}
else
{
break ;
}
}
message . message = textBuilder . ToString ( ) ;
messages . Add ( message ) ;
}
return messages ;
}
protected override string GetErrorIdentifier ( )
{
throw new NotImplementedException ( ) ; // as we overriding the method Parse()
}
protected override Regex GetOutputRegex ( )
{
throw new NotImplementedException ( ) ; // as we overriding the method Parse()
}
}
2023-05-07 18:43:11 -04:00
#if UNITY_EDITOR_OSX & & ! ENABLE_GENERATE_NATIVE_PLUGINS_FOR_ASSEMBLIES_API
2023-03-28 13:24:16 -04:00
private class StaticLibraryPostProcessor
{
private const string TempSourceLibrary = @"Temp/StagingArea/StaticLibraries" ;
[PostProcessBuildAttribute(1)]
public static void OnPostProcessBuild ( BuildTarget target , string path )
{
// Early out if we are building for the simulator, as we don't
//currently generate burst libraries that will work for that.
if ( IsForSimulator ( target ) )
{
return ;
}
// We only support AOT compilation for ios from a macos host (we require xcrun and the apple tool chains)
//for other hosts, we simply act as if burst is not being used (an error will be generated by the build aot step)
//this keeps the behaviour consistent with how it was before static linkage was introduced
if ( target = = BuildTarget . iOS )
{
var aotSettingsForTarget = BurstPlatformAotSettings . GetOrCreateSettings ( BuildTarget . iOS ) ;
// Early exit if burst is not activated
if ( ! aotSettingsForTarget . EnableBurstCompilation )
{
return ;
}
PostAddStaticLibraries ( path ) ;
}
if ( target = = BuildTarget . tvOS )
{
var aotSettingsForTarget = BurstPlatformAotSettings . GetOrCreateSettings ( BuildTarget . tvOS ) ;
// Early exit if burst is not activated
if ( ! aotSettingsForTarget . EnableBurstCompilation )
{
return ;
}
PostAddStaticLibraries ( path ) ;
}
}
private static void PostAddStaticLibraries ( string path )
{
var assm = AppDomain . CurrentDomain . GetAssemblies ( ) . SingleOrDefault ( assembly = >
assembly . GetName ( ) . Name = = "UnityEditor.iOS.Extensions.Xcode" ) ;
Type PBXType = assm ? . GetType ( "UnityEditor.iOS.Xcode.PBXProject" ) ;
Type PBXSourceTree = assm ? . GetType ( "UnityEditor.iOS.Xcode.PBXSourceTree" ) ;
if ( PBXType ! = null & & PBXSourceTree ! = null )
{
var project = Activator . CreateInstance ( PBXType , null ) ;
var _sGetPBXProjectPath = PBXType . GetMethod ( "GetPBXProjectPath" ) ;
var _ReadFromFile = PBXType . GetMethod ( "ReadFromFile" ) ;
var _sGetUnityTargetName = PBXType . GetMethod ( "GetUnityTargetName" ) ;
var _AddFileToBuild = PBXType . GetMethod ( "AddFileToBuild" ) ;
var _AddFile = PBXType . GetMethod ( "AddFile" ) ;
var _WriteToString = PBXType . GetMethod ( "WriteToString" ) ;
var sourcetree = new EnumConverter ( PBXSourceTree ) . ConvertFromString ( "Source" ) ;
string sPath = ( string ) _sGetPBXProjectPath ? . Invoke ( null , new object [ ] { path } ) ;
_ReadFromFile ? . Invoke ( project , new object [ ] { sPath } ) ;
var _TargetGuidByName = PBXType . GetMethod ( "GetUnityFrameworkTargetGuid" ) ;
string g = ( string ) _TargetGuidByName ? . Invoke ( project , null ) ;
var srcPath = TempSourceLibrary ;
var dstPath = "Libraries" ;
var dstCopyPath = Path . Combine ( path , dstPath ) ;
var burstCppLinkFile = "lib_burst_generated.cpp" ;
2023-05-07 18:43:11 -04:00
var libName = $"{DefaultLibraryName}.a" ;
var libSrcPath = Path . Combine ( srcPath , libName ) ;
var libExists = File . Exists ( libSrcPath ) ;
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
if ( ! libExists )
2023-03-28 13:24:16 -04:00
{
return ; // No libs, so don't write the cpp either
}
2023-05-07 18:43:11 -04:00
File . Copy ( libSrcPath , Path . Combine ( dstCopyPath , libName ) ) ;
2023-03-28 13:24:16 -04:00
AddLibToProject ( project , _AddFileToBuild , _AddFile , sourcetree , g , dstPath , libName ) ;
// Additionally we need a small cpp file (weak symbols won't unfortunately override directly from the libs
//presumably due to link order?
2023-05-07 18:43:11 -04:00
WriteStaticLinkCppFile ( dstCopyPath ) ;
string cppPath = Path . Combine ( dstPath , burstCppLinkFile ) ;
2023-03-28 13:24:16 -04:00
string fileg = ( string ) _AddFile ? . Invoke ( project , new object [ ] { cppPath , cppPath , sourcetree } ) ;
_AddFileToBuild ? . Invoke ( project , new object [ ] { g , fileg } ) ;
string pstring = ( string ) _WriteToString ? . Invoke ( project , null ) ;
File . WriteAllText ( sPath , pstring ) ;
}
}
private static void AddLibToProject ( object project , System . Reflection . MethodInfo _AddFileToBuild , System . Reflection . MethodInfo _AddFile , object sourcetree , string g , string dstPath , string lib32Name )
{
string fg = ( string ) _AddFile ? . Invoke ( project ,
new object [ ] { Path . Combine ( dstPath , lib32Name ) , Path . Combine ( dstPath , lib32Name ) , sourcetree } ) ;
_AddFileToBuild ? . Invoke ( project , new object [ ] { g , fg } ) ;
}
}
#endif
}
}
#endif