Add automatic building of nethost for consoles

This commit is contained in:
Wojtek Figat
2024-01-15 17:30:45 +01:00
parent ec6fb459bb
commit 3bdc70a4c6
2 changed files with 122 additions and 90 deletions

View File

@@ -9,8 +9,6 @@ using Flax.Build.Platforms;
using Flax.Deploy;
using System.IO.Compression;
#pragma warning disable 0219
namespace Flax.Deps.Dependencies
{
/// <summary>
@@ -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>();
string os, arch, runtimeFlavor, hostRuntimeName = DotNetSdk.GetHostRuntimeIdentifier(targetPlatform, architecture), buildArgs = string.Empty, buildMonoAotCrossArgs = string.Empty;
bool setupVersion = false, buildMonoAotCross = false;
var envVars = new Dictionary<string, string>();
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/<version>/hostfxr.dll
// TODO: shared/Microsoft.NETCore.App/<version>/hostpolicy.dl
// TODO: shared/Microsoft.NETCore.App/<version>/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/<version>/hostfxr.dll
// TODO: shared/Microsoft.NETCore.App/<version>/hostpolicy.dl
// TODO: shared/Microsoft.NETCore.App/<version>/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);
}
/// <inheritdoc />
@@ -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)

View File

@@ -320,6 +320,11 @@ namespace Flax.Build
/// </summary>
ConsoleLogOutput = 1 << 7,
/// <summary>
/// Enables <see cref="ProcessStartInfo.UseShellExecute"/> to run process via Shell instead of standard process startup.
/// </summary>
Shell = 1 << 8,
/// <summary>
/// The default options.
/// </summary>
@@ -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;
}
}