From 3bdc70a4c6a2ebc3a6d9ea2535b112cd0b8641df Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Jan 2024 17:30:45 +0100 Subject: [PATCH] Add automatic building of `nethost` for consoles --- .../Flax.Build/Deps/Dependencies/nethost.cs | 192 ++++++++++-------- .../Tools/Flax.Build/Utilities/Utilities.cs | 20 +- 2 files changed, 122 insertions(+), 90 deletions(-) diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs index f5eed36d7..7966870a3 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs @@ -9,8 +9,6 @@ using Flax.Build.Platforms; using Flax.Deploy; using System.IO.Compression; -#pragma warning disable 0219 - namespace Flax.Deps.Dependencies { /// @@ -30,6 +28,7 @@ namespace Flax.Deps.Dependencies return new[] { TargetPlatform.PS4, + TargetPlatform.PS5, TargetPlatform.Switch, }; case TargetPlatform.Linux: @@ -46,6 +45,7 @@ namespace Flax.Deps.Dependencies public override bool BuildByDefault => false; private string root; + private bool cleanArtifacts; private void Build(BuildOptions options, TargetPlatform targetPlatform, TargetArchitecture architecture) { @@ -55,13 +55,14 @@ namespace Flax.Deps.Dependencies // Clean output directory var artifacts = Path.Combine(root, "artifacts"); - SetupDirectory(artifacts, true); + SetupDirectory(artifacts, cleanArtifacts); + cleanArtifacts = true; // Peek options - string os, arch, runtimeFlavor, subset, hostRuntimeName = DotNetSdk.GetHostRuntimeIdentifier(targetPlatform, architecture), buildArgsBase = string.Empty; - bool setupVersion = false; - string[] hostRuntimeFiles = Array.Empty(); + string os, arch, runtimeFlavor, hostRuntimeName = DotNetSdk.GetHostRuntimeIdentifier(targetPlatform, architecture), buildArgs = string.Empty, buildMonoAotCrossArgs = string.Empty; + bool setupVersion = false, buildMonoAotCross = false; var envVars = new Dictionary(); + envVars.Add("VisualStudioVersion", null); // Unset this so 'init-vs-env.cmd' will properly init VS Environment switch (architecture) { case TargetArchitecture.x86: @@ -83,36 +84,39 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Windows: os = "windows"; runtimeFlavor = "CoreCLR"; - subset = "clr"; break; case TargetPlatform.Linux: - os = "Linux"; + os = "linux"; runtimeFlavor = "CoreCLR"; - subset = "clr"; break; case TargetPlatform.Mac: - os = "OSX"; + os = "osx"; runtimeFlavor = "CoreCLR"; - subset = "clr"; break; case TargetPlatform.Android: - os = "Android"; + os = "android"; runtimeFlavor = "Mono"; - subset = "mono+libs"; break; case TargetPlatform.PS4: - os = "PS4"; + case TargetPlatform.PS5: + case TargetPlatform.Switch: runtimeFlavor = "Mono"; - subset = "mono+libs"; setupVersion = true; - buildArgsBase = " /p:RuntimeOS=ps4 -cmakeargs \"-DCLR_CROSS_COMPONENTS_BUILD=1\""; - hostRuntimeFiles = new[] + buildMonoAotCross = true; + switch (targetPlatform) { - "coreclr_delegates.h", - "hostfxr.h", - "nethost.h", - "libnethost.a", - }; + case TargetPlatform.PS4: + os = "ps4"; + break; + case TargetPlatform.PS5: + os = "ps5"; + break; + case TargetPlatform.Switch: + os = "switch"; + break; + default: throw new InvalidPlatformException(targetPlatform); + } + buildArgs = $" /p:RuntimeOS={os} -subset mono+libs -cmakeargs \"-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1\" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; break; default: throw new InvalidPlatformException(targetPlatform); } @@ -175,74 +179,85 @@ namespace Flax.Deps.Dependencies } // Build - buildArgsBase = $"-os {os} -a {arch} -f {framework} -c {configuration} -lc {configuration} -rc {configuration} -rf {runtimeFlavor}{buildArgsBase}"; - //foreach (var buildStep in new[] { subset, "host.pkg", "packs.product" }) - /*var buildStep = "host.pkg"; + if (runtimeFlavor == "CoreCLR") + buildArgs = $"-os {os} -a {arch} -f {framework} -c {configuration} -lc {configuration} -rc {configuration} -rf {runtimeFlavor}{buildArgs}"; + else if (runtimeFlavor == "Mono") + buildArgs = $"-os {os} -a {arch} -c {configuration} -rf {runtimeFlavor}{buildArgs}"; + Utilities.Run(Path.Combine(root, buildScript), buildArgs, null, root, Utilities.RunOptions.DefaultTool, envVars); + if (buildMonoAotCross) { - var buildArgs = $"{buildArgsBase} -s {buildStep}"; - if (BuildPlatform == TargetPlatform.Windows) - { - // For some reason running from Visual Studio fails the build so use command shell - //buildArgs = $"/C {buildScript} {buildArgs}"; - //buildApp = "cmd.exe"; - // TODO: maybe write command line into bat file and run it here? - WinAPI.SetClipboard($"{buildScript} {buildArgs}"); - WinAPI.MessageBox.Show($"Open console command in folder '{root}' and run command from clipboard. Then close this dialog.", "Run command manually", WinAPI.MessageBox.Buttons.Ok); - } - else - { - //Utilities.Run(Path.Combine(root, buildScript), buildArgs, null, root, Utilities.RunOptions.ThrowExceptionOnError, envVars); - } - }*/ - Utilities.Run(Path.Combine(root, buildScript), buildArgsBase, null, root, Utilities.RunOptions.ThrowExceptionOnError, envVars); + buildMonoAotCrossArgs = $"-c {configuration} -rf {runtimeFlavor} -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true /p:TargetOS={os} /p:HostOS=windows -cmakeargs \"-DCMAKE_CROSSCOMPILING=True\"{buildMonoAotCrossArgs}"; + Utilities.Run(Path.Combine(root, buildScript), buildMonoAotCrossArgs, null, root, Utilities.RunOptions.DefaultTool, envVars); + } // Deploy build products + var privateCoreLib = "System.Private.CoreLib.dll"; var dstBinaries = GetThirdPartyFolder(options, targetPlatform, architecture); - var srcHostRuntime = Path.Combine(artifacts, "bin", $"{hostRuntimeName}.{configuration}", "corehost"); - foreach (var file in hostRuntimeFiles) - { - Utilities.FileCopy(Path.Combine(srcHostRuntime, file), Path.Combine(dstBinaries, file)); - } - var dstDotnet = Path.Combine(GetBinariesFolder(options, targetPlatform), "Dotnet"); - var dstClassLibrary = Path.Combine(dstDotnet, "shared", "Microsoft.NETCore.App", version); - SetupDirectory(dstClassLibrary, true); - foreach (var file in new[] - { - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - }) - { + var dstPlatform = Path.Combine(options.PlatformsFolder, targetPlatform.ToString()); + var dstDotnet = Path.Combine(dstPlatform, "Dotnet"); + foreach (var file in new[] { "LICENSE.TXT", "THIRD-PARTY-NOTICES.TXT" }) Utilities.FileCopy(Path.Combine(root, file), Path.Combine(dstDotnet, file)); - } - var srcDotnetLibsPkg = Path.Combine(artifacts, "packages", "Release", "Shipping", $"Microsoft.NETCore.App.Runtime.Mono.{hostRuntimeName}.{version}.nupkg"); - if (!File.Exists(srcDotnetLibsPkg)) - throw new Exception($"Missing .NET Core App class library package at '{srcDotnetLibsPkg}'"); - var unpackTemp = Path.Combine(Path.GetDirectoryName(srcDotnetLibsPkg), "UnpackTemp"); - SetupDirectory(unpackTemp, true); - using (var zip = ZipFile.Open(srcDotnetLibsPkg, ZipArchiveMode.Read)) + if (runtimeFlavor == "CoreCLR") { - zip.ExtractToDirectory(unpackTemp); - } - var privateCorelib = "System.Private.CoreLib.dll"; - Utilities.FileCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "native", privateCorelib), Path.Combine(dstClassLibrary, privateCorelib)); - Utilities.DirectoryCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "lib", "net8.0"), dstClassLibrary, false, true); - // TODO: host/fxr//hostfxr.dll - // TODO: shared/Microsoft.NETCore.App//hostpolicy.dl - // TODO: shared/Microsoft.NETCore.App//System.IO.Compression.Native.dll - if (runtimeFlavor == "Mono") - { - // TODO: implement automatic deployment based on the setup: - // PS4 outputs mono into artifacts\obj\mono\PS4.x64.Release\out - // PS4 outputs native libs into artifacts\bin\native\net7.0-PS4-Release-x64\lib - // PS4 outputs System.Private.CoreLib lib into artifacts\bin\mono\PS4.x64.Release - // PS4 outputs C# libs into artifacts\bin\runtime\net7.0-PS4.Release.x64 - // PS4 outputs AOT compiler into artifacts\bin\mono\PS4.x64.Release\cross\ps4-x64 + var srcHostRuntime = Path.Combine(artifacts, "bin", $"{hostRuntimeName}.{configuration}", "corehost"); + var dstClassLibrary = Path.Combine(dstDotnet, "shared", "Microsoft.NETCore.App", version); + SetupDirectory(dstClassLibrary, true); + var srcDotnetLibsPkg = Path.Combine(artifacts, "packages", "Release", "Shipping", $"Microsoft.NETCore.App.Runtime.Mono.{hostRuntimeName}.{version}.nupkg"); + if (!File.Exists(srcDotnetLibsPkg)) + throw new Exception($"Missing .NET Core App class library package at '{srcDotnetLibsPkg}'"); + var unpackTemp = Path.Combine(Path.GetDirectoryName(srcDotnetLibsPkg), "UnpackTemp"); + SetupDirectory(unpackTemp, true); + using (var zip = ZipFile.Open(srcDotnetLibsPkg, ZipArchiveMode.Read)) + zip.ExtractToDirectory(unpackTemp); + Utilities.FileCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "native", privateCoreLib), Path.Combine(dstClassLibrary, privateCoreLib)); + Utilities.DirectoryCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "lib", framework), dstClassLibrary, false, true); + // TODO: host/fxr//hostfxr.dll + // TODO: shared/Microsoft.NETCore.App//hostpolicy.dl + // TODO: shared/Microsoft.NETCore.App//System.IO.Compression.Native.dll Utilities.DirectoryCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "native"), Path.Combine(dstDotnet, "native"), true, true); - Utilities.FileDelete(Path.Combine(dstDotnet, "native", privateCorelib)); + Utilities.FileDelete(Path.Combine(dstDotnet, "native", privateCoreLib)); + Utilities.DirectoriesDelete(unpackTemp); + } + else if (runtimeFlavor == "Mono") + { + // Native libs + var src1 = Path.Combine(artifacts, "obj", "mono", $"{os}.{arch}.{configuration}", "out"); + if (!Directory.Exists(src1)) + throw new DirectoryNotFoundException(src1); + var src2 = Path.Combine(artifacts, "bin", "native", $"{framework}-{os}-{configuration}-{arch}"); + if (!Directory.Exists(src1)) + throw new DirectoryNotFoundException(src2); + foreach (var file in new[] + { + "libmonosgen-2.0.a", + "libmono-profiler-aot.a", + }) + Utilities.FileCopy(Path.Combine(src1, "lib", file), Path.Combine(dstBinaries, file)); + foreach (var file in new[] + { + "libSystem.Globalization.Native.a", + "libSystem.IO.Compression.Native.a", + "libSystem.IO.Ports.Native.a", + "libSystem.Native.a", + }) + Utilities.FileCopy(Path.Combine(src2, "lib", file), Path.Combine(dstBinaries, file)); + + // Include headers + Utilities.DirectoryDelete(Path.Combine(dstBinaries, "include")); + Utilities.DirectoryCopy(Path.Combine(src1, "include"), Path.Combine(dstBinaries, "include"), true, true); + + if (buildMonoAotCross) + { + // AOT compiler + Utilities.FileCopy(Path.Combine(artifacts, "bin", "mono", $"{os}.x64.{configuration}", "cross", $"{os}-x64", "mono-aot-cross.exe"), Path.Combine(dstPlatform, "Binaries", "Tools", "mono-aot-cross.exe")); + } + + // Class library + var dstDotnetLib = Path.Combine(dstPlatform, "Dotnet", "lib", framework); + SetupDirectory(dstDotnetLib, true); + Utilities.FileCopy(Path.Combine(artifacts, "bin", "mono", $"{os}.{arch}.{configuration}", privateCoreLib), Path.Combine(dstDotnetLib, privateCoreLib)); + Utilities.DirectoryCopy(Path.Combine(artifacts, "bin", "runtime", $"{framework}-{os}-{configuration}-{arch}"), dstDotnetLib, false, true, "*.dll"); } - else - throw new InvalidPlatformException(targetPlatform); - Utilities.DirectoriesDelete(unpackTemp); } /// @@ -255,9 +270,12 @@ namespace Flax.Deps.Dependencies Utilities.Run("cmake", "--version", null, null, Utilities.RunOptions.ThrowExceptionOnError); // Get the source - CloneGitRepo(root, "https://github.com/FlaxEngine/dotnet-runtime.git", null, null, true); - GitCheckout(root, "flax-master"); - SetupDirectory(Path.Combine(root, "src", "external"), false); + if (!Directory.Exists(Path.Combine(root, ".git"))) + { + CloneGitRepo(root, "https://github.com/FlaxEngine/dotnet-runtime.git", null, null, true); + GitCheckout(root, "flax-master-8"); + SetupDirectory(Path.Combine(root, "src", "external"), false); + } /* * Mono AOT for Windows: @@ -267,10 +285,10 @@ namespace Flax.Deps.Dependencies * .\build.cmd -c release -runtimeFlavor mono -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true * * Mono AOT for PS4: - * .\build.cmd -os PS4 -a x64 /p:RuntimeOS=ps4 -c release -runtimeFlavor mono -subset mono+libs -cmakeargs "-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false + * .\build.cmd -os ps4 -a x64 /p:RuntimeOS=ps4 -c release -runtimeFlavor mono -subset mono+libs -cmakeargs "-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false * * Mono AOT cross-compiler for PS4: - * .\build.cmd -c release -runtimeFlavor mono -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true /p:TargetOS=PS4 /p:HostOS=windows -cmakeargs "-DCMAKE_CROSSCOMPILING=True" + * .\build.cmd -c release -runtimeFlavor mono -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true /p:TargetOS=ps4 /p:HostOS=windows -cmakeargs "-DCMAKE_CROSSCOMPILING=True" */ foreach (var platform in options.Platforms) diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index bf7c24e1a..02019b30d 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -320,6 +320,11 @@ namespace Flax.Build /// ConsoleLogOutput = 1 << 7, + /// + /// Enables to run process via Shell instead of standard process startup. + /// + Shell = 1 << 8, + /// /// The default options. /// @@ -398,11 +403,12 @@ namespace Flax.Build } bool redirectStdOut = (options & RunOptions.NoStdOutRedirect) != RunOptions.NoStdOutRedirect; + bool shell = options.HasFlag(RunOptions.Shell); Process proc = new Process(); proc.StartInfo.FileName = app; proc.StartInfo.Arguments = commandLine != null ? commandLine : ""; - proc.StartInfo.UseShellExecute = false; + proc.StartInfo.UseShellExecute = shell; proc.StartInfo.RedirectStandardInput = input != null; proc.StartInfo.CreateNoWindow = true; @@ -411,7 +417,7 @@ namespace Flax.Build proc.StartInfo.WorkingDirectory = workspace; } - if (redirectStdOut) + if (redirectStdOut && !shell) { proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.RedirectStandardError = true; @@ -427,16 +433,24 @@ namespace Flax.Build } } - if (envVars != null) + if (envVars != null && !shell) { foreach (var env in envVars) { if (env.Key == "PATH") { + // Append to path proc.StartInfo.EnvironmentVariables[env.Key] = env.Value + ';' + proc.StartInfo.EnvironmentVariables[env.Key]; } + else if (env.Value == null) + { + // Remove variable + if (proc.StartInfo.EnvironmentVariables.ContainsKey(env.Key)) + proc.StartInfo.EnvironmentVariables.Remove(env.Key); + } else { + // Set variable proc.StartInfo.EnvironmentVariables[env.Key] = env.Value; } }