private static DotsRuntimeCSharpProgram SetupGame(AsmDefDescription game) { var gameProgram = GetOrMakeDotsRuntimeCSharpProgramFor(game); var withoutExt = new NPath(gameProgram.FileName).FileNameWithoutExtension; NPath exportManifest = new NPath(withoutExt + "/export.manifest"); Backend.Current.RegisterFileInfluencingGraph(exportManifest); if (exportManifest.FileExists()) { var dataFiles = exportManifest.MakeAbsolute().ReadAllLines(); foreach (var dataFile in dataFiles.Select(d => new NPath(d))) { gameProgram.SupportFiles.Add(new DeployableFile(dataFile, "Data/" + dataFile.FileName)); } } var configToSetupGame = new Dictionary <DotsRuntimeCSharpProgramConfiguration, DotNetAssembly>(); if (!PerConfigBuildSettings.ContainsKey(game.Name)) { return(null); } foreach (var config in PerConfigBuildSettings[game.Name].Where(config => !CanSkipSetupOf(game.Name, config))) { gameProgram.ProjectFile.StartInfo.Add(c => c == config, StartInfoFor(config, EntryPointExecutableFor(gameProgram, config))); gameProgram.ProjectFile.BuildCommand.Add(c => c == config, new BeeBuildCommand(GameDeployBinaryFor(gameProgram, config).ToString(), false, false).ToExecuteArgs()); DotNetAssembly setupGame = gameProgram.SetupSpecificConfiguration(config).WithDeployables(new DotNetAssembly( Il2Cpp.Distribution.Path.Combine("build/profiles/Tiny/Facades/netstandard.dll"), Framework.FrameworkNone)); var postILProcessedGame = ILPostProcessorTool.SetupInvocation(setupGame, config, gameProgram.Defines.For(config).ToArray()); var postTypeRegGenGame = TypeRegistrationTool.SetupInvocation(postILProcessedGame, config); configToSetupGame[config] = postTypeRegGenGame; } ; var il2CppOutputProgram = new Il2Cpp.Il2CppOutputProgram(gameProgram.AsmDefDescription.Name.Replace(".", "-") + ".il2cpp"); var configToSetupGameBursted = new Dictionary <DotsRuntimeCSharpProgramConfiguration, DotNetAssembly>(); BurstCompiler hostBurstCompiler = null; if (HostPlatform.IsWindows) { hostBurstCompiler = new BurstCompilerForWindows64(); hostBurstCompiler.Link = true; hostBurstCompiler.OnlyStaticMethods = true; } else if (HostPlatform.IsOSX) { hostBurstCompiler = new BurstCompilerForMac(); hostBurstCompiler.Link = true; hostBurstCompiler.OnlyStaticMethods = true; } else { Console.WriteLine("Tiny burst not yet supported on linux."); } var webBurstCompiler = new BurstCompilerForEmscripten(); foreach (var kvp in configToSetupGame) { var config = kvp.Key; var setupGame = kvp.Value; if (config.UseBurst) { var outputDir = $"artifacts/{game.Name}/{config.Identifier}_bursted"; var isWebGL = config.Platform is WebGLPlatform; var extension = config.NativeProgramConfiguration.ToolChain.DynamicLibraryFormat.Extension; //burst generates a .bundle on os x. if (config.Platform is MacOSXPlatform) { extension = "bundle"; } var burstlib = BurstCompiler.SetupBurstCompilationAndLinkForAssemblies( isWebGL ? webBurstCompiler : hostBurstCompiler, setupGame, new NPath(outputDir).Combine( $"lib_burst_generated{("."+extension) ?? ""}"), outputDir, out var burstedGame); if (isWebGL) { il2CppOutputProgram.Libraries.Add(c => c.Equals(config.NativeProgramConfiguration), burstlib); } else { burstedGame = burstedGame.WithDeployables(burstlib); } configToSetupGameBursted[config] = burstedGame; } else { configToSetupGameBursted[config] = setupGame; } } var configToSetupGameStripped = new Dictionary <DotsRuntimeCSharpProgramConfiguration, DotNetAssembly>(); foreach (var kvp in configToSetupGameBursted) { var config = kvp.Key; var setupGame = kvp.Value; if (config.ScriptingBackend == ScriptingBackend.TinyIl2cpp) { setupGame = Il2Cpp.UnityLinker.SetupInvocation(setupGame, $"artifacts/{game.Name}/{config.Identifier}_stripped", config.NativeProgramConfiguration); il2CppOutputProgram.SetupConditionalSourcesAndLibrariesForConfig(config, setupGame); configToSetupGameStripped[kvp.Key] = setupGame; } else { configToSetupGameStripped[kvp.Key] = kvp.Value; } } foreach (var kvp in configToSetupGameStripped) { var config = kvp.Key; var setupGame = kvp.Value; NPath deployPath = GameDeployDirectoryFor(gameProgram, config); IDeployable deployedGame; NPath entryPointExecutable = null; if (config.ScriptingBackend == ScriptingBackend.TinyIl2cpp) { var builtNativeProgram = il2CppOutputProgram.SetupSpecificConfiguration( config.NativeProgramConfiguration, config.NativeProgramConfiguration.ExecutableFormat ) .WithDeployables(setupGame.RecursiveRuntimeDependenciesIncludingSelf.SelectMany(a => a.Deployables.Where(d => !(d is DotNetAssembly) && !(d is StaticLibrary))) .ToArray()); if (builtNativeProgram is IPackagedAppExtension) { (builtNativeProgram as IPackagedAppExtension).SetAppPackagingParameters(gameProgram.AsmDefDescription.Name, config.NativeProgramConfiguration.CodeGen, gameProgram.SupportFiles.For(config)); } deployedGame = builtNativeProgram.DeployTo(deployPath); entryPointExecutable = deployedGame.Path; if (config.EnableManagedDebugging) { Backend.Current.AddDependency(deployedGame.Path, Il2Cpp.CopyIL2CPPMetadataFile(deployPath, setupGame)); } } else { deployedGame = setupGame.DeployTo(deployPath); var dotNetAssembly = (DotNetAssembly)deployedGame; //Usually a dotnet runtime game does not have a static void main(), and instead references another "entrypoint asmdef" that provides it. //This is convenient, but what makes it weird is that you have to start YourEntryPoint.exe instead of YourGame.exe. Until we have a better //solution for this, we're going to copy YourEntryPoint.exe to YourGame.exe, so that it's easier to find, and so that when it runs and you look //at the process name you understand what it is. if (deployedGame.Path.HasExtension("dll")) { var to = deployPath.Combine(deployedGame.Path.ChangeExtension("exe").FileName); var from = dotNetAssembly.RecursiveRuntimeDependenciesIncludingSelf.SingleOrDefault(a => a.Path.HasExtension("exe"))?.Path; if (from == null) { throw new InvalidProgramException($"Program {dotNetAssembly.Path} is an executable-like thing, but doesn't reference anything with Main"); } Backend.Current.AddDependency(deployedGame.Path, CopyTool.Instance().Setup(to, from)); entryPointExecutable = to; } else { entryPointExecutable = deployedGame.Path; } } //Because we use multidag, and try to not run all the setupcode when we just want to create projectfiles, we have a bit of a challenge. //Projectfiles require exact start and build commands. So we need to have a cheap way to calculate those. However, it's important that they //exactly match the actual place where the buildprogram is going to place our files. If these don't match things break down. The checks //in this block, they compare the "quick way to determine where the binary will be placed, and what the start executable is", with the //actual return values returned from .DeployTo(), when we do run the actual buildcode. NPath deployedGamePath = GameDeployBinaryFor(gameProgram, config); if (deployedGame.Path != deployedGamePath) { throw new InvalidProgramException($"We expected deployPath to be {deployedGamePath}, but in reality it was {deployedGame.Path}"); } var expectedEntryPointExecutable = EntryPointExecutableFor(gameProgram, config); if (entryPointExecutable != expectedEntryPointExecutable) { throw new InvalidProgramException($"We expected entryPointExecutable to be {expectedEntryPointExecutable}, but in reality it was {entryPointExecutable}"); } Backend.Current.AddAliasDependency(config.Identifier, deployedGamePath); } return(gameProgram); }