// Copyright (c) Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Xml; using Flax.Build; using Flax.Build.Platforms; using Flax.Deploy; using System.IO.Compression; namespace Flax.Deps.Dependencies { /// /// .NET runtime /// /// class nethost : Dependency { /// public override TargetPlatform[] Platforms { get { switch (BuildPlatform) { case TargetPlatform.Windows: return new[] { TargetPlatform.PS4, TargetPlatform.PS5, TargetPlatform.Switch, TargetPlatform.XboxOne, TargetPlatform.XboxScarlett, }; case TargetPlatform.Linux: return new[] { TargetPlatform.Android, }; default: return new TargetPlatform[0]; } } } private string root; private bool cleanArtifacts; private void Build(BuildOptions options, TargetPlatform targetPlatform, TargetArchitecture architecture) { // Build configuration (see build.cmd -help) string configuration = "Release"; string framework = "net8.0"; // Clean output directory var artifacts = Path.Combine(root, "artifacts"); SetupDirectory(artifacts, cleanArtifacts); cleanArtifacts = true; // Peek options 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: arch = "x86"; break; case TargetArchitecture.x64: arch = "x64"; break; case TargetArchitecture.ARM: arch = "arm"; break; case TargetArchitecture.ARM64: arch = "arm64"; break; default: throw new InvalidArchitectureException(architecture); } switch (targetPlatform) { case TargetPlatform.Windows: os = "windows"; runtimeFlavor = "CoreCLR"; break; case TargetPlatform.XboxOne: case TargetPlatform.XboxScarlett: os = "windows"; runtimeFlavor = "Mono"; buildMonoAotCross = true; var defines = "-D_GAMING_XBOX=1-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1"; defines += targetPlatform == TargetPlatform.XboxScarlett ? "-D_GAMING_XBOX_SCARLETT=1" : "-D_GAMING_XBOX_XBOXONE=1"; defines += "-DDISABLE_EXECUTABLES=1-DDISABLE_SHARED_LIBS=1"; buildArgs = $" -subset mono+libs -cmakeargs \"{defines}\" /p:FeaturePerfTracing=false /p:FeatureWin32Registry=false /p:FeatureCominteropApartmentSupport=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; envVars.Add("_GAMING_XBOX", "1"); break; case TargetPlatform.Linux: os = "linux"; runtimeFlavor = "CoreCLR"; break; case TargetPlatform.Mac: os = "osx"; runtimeFlavor = "CoreCLR"; break; case TargetPlatform.Android: os = "android"; runtimeFlavor = "Mono"; break; case TargetPlatform.PS4: case TargetPlatform.PS5: case TargetPlatform.Switch: runtimeFlavor = "Mono"; setupVersion = true; buildMonoAotCross = true; switch (targetPlatform) { 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); } // Setup build environment variables for build system string buildScript = null; switch (BuildPlatform) { case TargetPlatform.Windows: { buildScript = "build.cmd"; if (setupVersion) { // Setup _version.c file manually SetupDirectory(Path.Combine(artifacts, "obj"), false); foreach (var file in new[] { "_version.c", "_version.h" }) File.Copy(Path.Combine(root, "eng", "native", "version", file), Path.Combine(artifacts, "obj", file), true); } var msBuild = VCEnvironment.MSBuildPath; if (File.Exists(msBuild)) { envVars.Add("PATH", Path.GetDirectoryName(msBuild)); } break; } case TargetPlatform.Linux: { buildScript = "build.sh"; break; } case TargetPlatform.Mac: { buildScript = "build.sh"; break; } default: throw new InvalidPlatformException(BuildPlatform); } if (AndroidNdk.Instance.IsValid) { var path = AndroidNdk.Instance.RootPath; envVars.Add("ANDROID_NDK", path); envVars.Add("ANDROID_NDK_HOME", path); envVars.Add("ANDROID_NDK_ROOT", path); } if (AndroidSdk.Instance.IsValid) { var path = AndroidSdk.Instance.RootPath; envVars.Add("ANDROID_SDK", path); envVars.Add("ANDROID_SDK_HOME", path); envVars.Add("ANDROID_SDK_ROOT", path); } // Print the runtime version string version; { var doc = new XmlDocument(); doc.Load(Path.Combine(root, "eng", "Versions.props")); version = doc["Project"]["PropertyGroup"]["ProductVersion"].InnerText; Log.Info("Building dotnet/runtime version " + version); } // Build 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) { 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 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)); if (runtimeFlavor == "CoreCLR") { 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.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(src2)) throw new DirectoryNotFoundException(src2); string[] libs1, libs2; switch (targetPlatform) { case TargetPlatform.Windows: libs1 = new[] { "lib/coreclr.dll", "lib/coreclr.import.lib", }; libs2 = new[] { "System.Globalization.Native.dll", "System.IO.Compression.Native.dll", }; break; case TargetPlatform.XboxOne: case TargetPlatform.XboxScarlett: libs1 = new[] { "lib/monosgen-2.0.lib", "lib/mono-profiler-aot.lib", }; libs2 = new[] { "lib/System.Globalization.Native-Static.lib", "lib/System.IO.Compression.Native-Static.lib", }; break; default: libs1 = new[] { "lib/libmonosgen-2.0.a", "lib/libmono-profiler-aot.a", }; libs2 = new[] { "lib/libSystem.Globalization.Native.a", "lib/libSystem.IO.Compression.Native.a", "lib/libSystem.IO.Ports.Native.a", "lib/libSystem.Native.a", }; break; } foreach (var file in libs1) Utilities.FileCopy(Path.Combine(src1, file), Path.Combine(dstBinaries, Path.GetFileName(file))); foreach (var file in libs2) Utilities.FileCopy(Path.Combine(src2, file), Path.Combine(dstBinaries, Path.GetFileName(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 == "windows" ? "win" : 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); foreach (var subDir in Directory.GetDirectories(Path.Combine(dstPlatform, "Dotnet", "lib"))) Utilities.DirectoryDelete(subDir); 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"); } } /// public override void Build(BuildOptions options) { root = options.IntermediateFolder; // On Windows MAX_PATH=260 might cause some build issues with CMake+Ninja, even when LongPathsEnabled=1 // To solve this, simply use a drive root folder instead of Deps directory if (BuildPlatform == TargetPlatform.Windows && root.Length > 30) { root = Path.Combine(Path.GetPathRoot(root), "nethost"); Log.Info($"Using custom rooted build directory: {root} (due to path size limit)"); SetupDirectory(root, false); } // Ensure to have dependencies installed Utilities.Run("ninja", "--version", null, null, Utilities.RunOptions.DefaultTool); Utilities.Run("cmake", "--version", null, null, Utilities.RunOptions.DefaultTool); Utilities.Run("python", "--version", null, null, Utilities.RunOptions.DefaultTool); // Get the source 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: * .\build.cmd -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 * * Mono AOT cross-compiler for Windows: * .\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 /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" */ foreach (var platform in options.Platforms) { BuildStarted(platform); var platformData = Path.Combine(GetBinariesFolder(options, platform), "Data", "nethost"); if (Directory.Exists(platformData)) Utilities.DirectoryCopy(platformData, root, true, true); switch (platform) { case TargetPlatform.PS4: case TargetPlatform.PS5: case TargetPlatform.XboxOne: case TargetPlatform.XboxScarlett: Build(options, platform, TargetArchitecture.x64); break; case TargetPlatform.Android: Build(options, platform, TargetArchitecture.ARM64); break; case TargetPlatform.Switch: Build(options, platform, TargetArchitecture.ARM64); break; } } // Copy license var dstIncludePath = Path.Combine(options.ThirdPartyFolder, "nethost"); Utilities.FileCopy(Path.Combine(root, "LICENSE.TXT"), Path.Combine(dstIncludePath, "LICENSE.TXT")); } } }