Created
October 23, 2025 13:46
-
-
Save regner/44ea3ecd8e91537df53742b99dfd1e17 to your computer and use it in GitHub Desktop.
BuildGraph C# examples
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using SDBuildGraph.Automation.Builders; | |
| using SDBuildGraph.Automation.Data; | |
| using SDBuildGraph.Automation.Nodes; | |
| using SDBuildGraph.Automation.Utilities; | |
| namespace EngineTest.Automation.SplashDamage; | |
| public class EngineTestCommonCompileParameters() : CommonCompileParameters(windowsCompilerVersion: "2022", warningsAsError: true); | |
| public class EngineTestPlatformCookParameters( | |
| PlatformAndTarget platform | |
| ) : PlatformCookParameters(platform: platform, skipEditorContent: true, versionContent: true); | |
| public class EngineTestSymbolsParameters( | |
| SdBgGraphBuilder builder | |
| ) : UploadSymbolsParameters( | |
| projectName: builder.ProjectName, | |
| buildVersion: builder.GetBuildVersion(), | |
| pushSymbolsToNetwork: false, | |
| networkPath: "" | |
| ); | |
| public abstract class BaseGraphBuilder : SdBgGraphBuilder | |
| { | |
| protected BaseGraphBuilder() | |
| { | |
| ProjectNetworkName = "SplashUE5"; | |
| ProjectName = "EngineTest"; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Collections.Generic; | |
| using SDBuildGraph.Automation.Data; | |
| using UnrealBuildTool; | |
| using SDBuildGraph.Automation.Nodes; | |
| using SDBuildGraph.Automation.Utilities; | |
| namespace EngineTest.Automation.SplashDamage; | |
| public class ContinuousIntegration : BaseGraphBuilder | |
| { | |
| public override void CreateGraphInternal() | |
| { | |
| // Setup common variables | |
| const bool addUgsBadges = true; | |
| List<UnrealTargetConfiguration> configurations = | |
| [ | |
| UnrealTargetConfiguration.Development, | |
| UnrealTargetConfiguration.Test, | |
| ]; | |
| CommonCompileParameters commonCompileParameters = new EngineTestCommonCompileParameters(); | |
| commonCompileParameters.UseIncrementalWorkspace = true; | |
| UploadSymbolsParameters symbolsParameters = new EngineTestSymbolsParameters(builder: this); | |
| // Add relevant nodes | |
| ToolsAndEditorNode.AddEngineTools( | |
| builder: this, | |
| commonCompileParameters: commonCompileParameters | |
| ); | |
| ToolsAndEditorNode.AddEditor( | |
| builder: this, | |
| editorParameters: new EditorParameters(addUgsBadges: addUgsBadges), | |
| commonCompileParameters: commonCompileParameters, | |
| symbolsParameters: symbolsParameters | |
| ); | |
| GauntletTestNode.AddEditorBootTest(builder: this, addUgsBadges: addUgsBadges); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Collections.Generic; | |
| using SDBuildGraph.Automation.Data; | |
| using UnrealBuildTool; | |
| using SDBuildGraph.Automation.Nodes; | |
| using SDBuildGraph.Automation.Utilities; | |
| namespace EngineTest.Automation.SplashDamage; | |
| public class PackagedBuildNightly : BaseGraphBuilder | |
| { | |
| public override void CreateGraphInternal() | |
| { | |
| List<UnrealTargetConfiguration> configurations = | |
| [ | |
| UnrealTargetConfiguration.Development, | |
| UnrealTargetConfiguration.Shipping, | |
| UnrealTargetConfiguration.Test, | |
| ]; | |
| PackagedBuildCommon.Setup( | |
| builder: this, | |
| compileEditor: true, | |
| clients: PlatformUtils.TryParsePlatforms("Win64", "PS5", "XSX"), | |
| servers: PlatformUtils.TryParsePlatforms("Win64", "Linux"), | |
| useIncrementalWorkspace: false, | |
| configurations: configurations | |
| ); | |
| SetAggregateLabel("Nightly"); | |
| } | |
| } | |
| public static class PackagedBuildCommon | |
| { | |
| public static void Setup( | |
| BaseGraphBuilder builder, | |
| bool compileEditor, | |
| List<UnrealTargetPlatform> clients, | |
| List<UnrealTargetPlatform> servers, | |
| bool useIncrementalWorkspace, | |
| List<UnrealTargetConfiguration> configurations | |
| ) | |
| { | |
| // Setup common variables | |
| CommonCompileParameters commonCompileParameters = new EngineTestCommonCompileParameters(); | |
| commonCompileParameters.UseIncrementalWorkspace = useIncrementalWorkspace; | |
| UploadSymbolsParameters symbolsParameters = new EngineTestSymbolsParameters(builder: builder); | |
| symbolsParameters.PushSymbolsToNetwork = true; | |
| List<PlatformAndTarget> platforms = PlatformUtils.CombinePlatforms(builder: builder, clients: clients, servers: servers); | |
| // Add relevant nodes | |
| ToolsAndEditorNode.AddEngineTools( | |
| builder: builder, | |
| commonCompileParameters: commonCompileParameters | |
| ); | |
| ToolsAndEditorNode.AddEditor( | |
| builder: builder, | |
| editorParameters: new EditorParameters(), | |
| commonCompileParameters: commonCompileParameters, | |
| symbolsParameters: symbolsParameters | |
| ); | |
| foreach (PlatformAndTarget platform in platforms) | |
| { | |
| PlatformCompileNode.AddCompilePlatform( | |
| builder: builder, | |
| platformCompileParameters: new PlatformCompileParameters( | |
| platform: platform, | |
| configurations: configurations | |
| ), | |
| commonCompileParameters: commonCompileParameters, | |
| symbolsParameters: symbolsParameters | |
| ); | |
| PlatformCookParameters cookParameters = new EngineTestPlatformCookParameters(platform: platform); | |
| PlatformCookNode.AddCookPlatform( | |
| builder: builder, | |
| parameters: cookParameters | |
| ); | |
| PlatformPackageNode.AddPackagePlatform( | |
| builder: builder, | |
| parameters: new PlatformPackageParameters( | |
| platform: platform, | |
| configurations: configurations, | |
| createStagedFilesArtifact: true, | |
| useIncrementalWorkspace: useIncrementalWorkspace | |
| ) | |
| ); | |
| } | |
| GauntletTestNode.AddEditorBootTest(builder: builder); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| using AutomationTool; | |
| using AutomationTool.Tasks; | |
| using EpicGames.BuildGraph.Expressions; | |
| using EpicGames.Core; | |
| using SDBuildGraph.Automation.Nodes; | |
| using SDBuildGraph.Automation.Utilities; | |
| using UnrealBuildBase; | |
| using Splash.BgArgParser; | |
| using Splash.BgArgParser.OptionTypes; | |
| namespace SDBuildGraph.Automation.Builders; | |
| public class BuildHorde : BgGraphBuilder | |
| { | |
| private static readonly DirectoryReference DotNetDirectory = DirectoryReference.Combine(Unreal.RootDirectory, "Engine/Binaries/ThirdParty/DotNet"); | |
| private static readonly FileReference DotNetPath = FileReference.Combine(DotNetDirectory, "8.0.300/win-x64/dotnet.exe"); | |
| private static readonly DirectoryReference StagingDirectory = DirectoryReference.Combine(Unreal.RootDirectory, "LocalBuilds", "HordeStaging"); | |
| private static readonly DirectoryReference CmdDirectory = DirectoryReference.Combine(StagingDirectory, "cmd"); | |
| private static readonly DirectoryReference AgentDirectory = DirectoryReference.Combine(StagingDirectory, "agent"); | |
| public override BgGraph CreateGraph(BgEnvironment environment) | |
| { | |
| // Script arguments | |
| BooleanOption publishCmd = new BooleanOption("PublishCmd", false); | |
| BooleanOption publishAgent = new BooleanOption("PublishAgent", false); | |
| BooleanOption publishServer = new BooleanOption("PublishServer", false); | |
| Parser parser = Parser.NewFromEnvironment(); | |
| parser.Add(publishCmd); | |
| parser.Add(publishAgent); | |
| parser.Add(publishServer); | |
| parser.Parse(); | |
| // Agents | |
| BgAgent testAgent = new BgAgent("Test Horde", "TestWin64"); | |
| BgAgent compileAgent = new BgAgent("Build Horde Agent", "CompileWin64"); | |
| BgAgent dockerAgent = new BgAgent("Build Horde Server", "DockerLinuxIncremental"); | |
| // Labels | |
| BgLabel testsLabel = new(name: "Tests", category: "Other"); | |
| BgLabel cmdLabel = new(name: "CMD", category: "Other"); | |
| BgLabel agentLabel = new(name: "Agent", category: "Other"); | |
| BgLabel dashboardLabel = new(name: "Dashboard", category: "Other"); | |
| BgLabel serverLabel = new(name: "Server", category: "Other"); | |
| // Nodes | |
| BgNode dotnetTestsNode = testAgent | |
| .AddNode(name: "DotNet Tests", func: context => RunDotnetTests()) | |
| .AddLabel(testsLabel); | |
| BgNode documentationTestNode = testAgent | |
| .AddNode(name: "Validate Documentation", func: context => RunMarkdownTests()) | |
| .AddLabel(testsLabel); | |
| BgNode staticAnalysisNode = testAgent | |
| .AddNode(name: "Static Analysis", func: context => RunBuildAnalyzer()) | |
| .AddLabel(testsLabel); | |
| BgNode cmdNode = compileAgent | |
| .AddNode(name: "Horde (Command-Line)", func: context => BuildCmd()) | |
| .AddLabel(cmdLabel); | |
| BgNode cmdPublishNode = compileAgent | |
| .AddNode( | |
| name: "Publish Horde (Command-Line)", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "horde-cmd", | |
| Directory = CmdDirectory | |
| } | |
| ) | |
| ) | |
| .Requires(cmdNode) | |
| .AddLabel(cmdLabel); | |
| // We build and publish 3 types of agents: | |
| // - The cross-platform agent is how we used to deploy Horde and requires the host OS have a .NET runtime, it | |
| // is here to help the transition and in case we need it again | |
| // - The OS specific agents are for build machines and developers | |
| BgNode agentCrossplatformNode = compileAgent | |
| .AddNode(name: "Horde Agent (Cross-Platform)", func: context => BuildAgent("")) | |
| .AddLabel(agentLabel); | |
| BgNode agentPublishCrossplatformNode = compileAgent | |
| .AddNode( | |
| name: "Publish Horde Agent (Cross-Platform)", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "horde-agent", | |
| Directory = GetAgentDirectory(""), | |
| } | |
| ) | |
| ) | |
| .Requires(agentCrossplatformNode) | |
| .AddLabel(agentLabel); | |
| BgNode agentWindowsNode = compileAgent | |
| .AddNode(name: "Horde Agent (win-x64)", func: context => BuildAgent("win-x64")) | |
| .AddLabel(agentLabel); | |
| BgNode agentPublishWindowsNode = compileAgent | |
| .AddNode( | |
| name: "Publish Horde Agent (win-x64)", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "horde-agent-win64", | |
| Directory = GetAgentDirectory("win-x64"), | |
| } | |
| ) | |
| ) | |
| .Requires(agentWindowsNode) | |
| .AddLabel(agentLabel); | |
| BgNode agentLinuxNode = compileAgent | |
| .AddNode(name: "Horde Agent (linux-x64)", func: context => BuildAgent("linux-x64")) | |
| .AddLabel(agentLabel); | |
| BgNode agentPublishLinuxNode = compileAgent | |
| .AddNode( | |
| name: "Publish Horde Agent (linux-x64)", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "horde-agent-linux64", | |
| Directory = GetAgentDirectory("linux-x64"), | |
| } | |
| ) | |
| ) | |
| .Requires(agentLinuxNode) | |
| .AddLabel(agentLabel); | |
| BgNode dashboardNode = dockerAgent | |
| .AddNode(name: "Horde Dashboard", func: context => BuildDashboard()) | |
| .AddLabel(dashboardLabel); | |
| BgNode serverNode = dockerAgent | |
| .AddNode(name: "Horde Server", func: context => BuildServer()) | |
| .Requires(dashboardNode) | |
| .AddLabel(serverLabel); | |
| BgNode serverPublishNode = dockerAgent | |
| .AddNode(name: "Publish Horde Server", func: context => PublishServer()) | |
| .Requires(serverNode) | |
| .AddLabel(serverLabel); | |
| // Finally put it all together | |
| List<BgNode> nodes = | |
| [ | |
| dotnetTestsNode, | |
| documentationTestNode, | |
| staticAnalysisNode, | |
| cmdNode, | |
| agentCrossplatformNode, | |
| agentWindowsNode, | |
| agentLinuxNode, | |
| dashboardNode, | |
| serverNode | |
| ]; | |
| if (publishCmd.Value) | |
| { | |
| nodes.Add(cmdPublishNode); | |
| } | |
| if (publishAgent.Value) | |
| { | |
| nodes.Add(agentPublishCrossplatformNode); | |
| nodes.Add(agentPublishWindowsNode); | |
| nodes.Add(agentPublishLinuxNode); | |
| } | |
| if (publishServer.Value) | |
| { | |
| nodes.Add(serverPublishNode); | |
| } | |
| BgAggregate aggregate = new BgAggregate("Aggregate", nodes.ToList()); | |
| return new BgGraph(nodes, aggregate); | |
| } | |
| /// <summary> | |
| /// Runs the dotnet tests for Horde and related projects. | |
| /// </summary> | |
| private static async Task RunDotnetTests() | |
| { | |
| List<string> projectsToTest = | |
| [ | |
| "Engine/Source/Programs/Horde/HordeAgent.Tests/HordeAgent.Tests.csproj", | |
| "Engine/Source/Programs/Horde/HordeServer.Tests/HordeServer.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Drivers/JobDriver.Tests/JobDriver.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Plugins/Analytics/HordeServer.Analytics.Tests/HordeServer.Analytics.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Plugins/Build/HordeServer.Build.Tests/HordeServer.Build.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Plugins/Compute/HordeServer.Compute.Tests/HordeServer.Compute.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Plugins/Ddc/HordeServer.Ddc.Tests/HordeServer.Ddc.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Plugins/Storage/HordeServer.Storage.Tests/HordeServer.Storage.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Plugins/Tools/HordeServer.Tools.Tests/HordeServer.Tools.Tests.csproj", | |
| "Engine/Source/Programs/Shared/EpicGames.BuildGraph.Tests/EpicGames.BuildGraph.Tests.csproj", | |
| "Engine/Source/Programs/Shared/EpicGames.Core.Tests/EpicGames.Core.Tests.csproj", | |
| // "Engine/Source/Programs/Shared/EpicGames.Horde.Tests/EpicGames.Horde.Tests.csproj", tests work locally but fail on build machine as of 5.5.1 | |
| "Engine/Source/Programs/Shared/EpicGames.IoHash.Tests/EpicGames.IoHash.Tests.csproj", | |
| "Engine/Source/Programs/Shared/EpicGames.Perforce.Managed.Tests/EpicGames.Perforce.Managed.Tests.csproj", | |
| "Engine/Source/Programs/Shared/EpicGames.Perforce.Tests/EpicGames.Perforce.Tests.csproj", | |
| "Engine/Source/Programs/Shared/EpicGames.Redis.Tests/EpicGames.Redis.Tests.csproj", | |
| "Engine/Source/Programs/Shared/EpicGames.Serialization.Tests/EpicGames.Serialization.Tests.csproj", | |
| ]; | |
| foreach (string testProject in projectsToTest) | |
| { | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = $"test \"{testProject}\" --blame-hang-timeout 5m --blame-hang-dump-type mini --logger 'console;verbosity=normal'", | |
| Environment = "UE_DOTNET_VERSION=net8.0", | |
| })); | |
| } | |
| } | |
| /// <summary> | |
| /// Validate the Horde documentation. | |
| /// </summary> | |
| private static async Task RunMarkdownTests() | |
| { | |
| await StandardTasks.ExecuteAsync(new CheckMarkdownTask(new CheckMarkdownTaskParameters | |
| { | |
| Files = $"{Unreal.RootDirectory}/Engine/Source/Programs/Horde/README.md;{Unreal.RootDirectory}/Engine/Source/Programs/Horde/Docs/..." | |
| })); | |
| } | |
| /// <summary> | |
| /// Build the Horde projects with the analyzer enabled. | |
| /// </summary> | |
| private static async Task RunBuildAnalyzer() | |
| { | |
| List<string> projectsToTest = | |
| [ | |
| "Engine/Source/Programs/Horde/HordeAgent/HordeAgent.csproj", | |
| "Engine/Source/Programs/Horde/HordeAgent.Tests/HordeAgent.Tests.csproj", | |
| "Engine/Source/Programs/Horde/HordeCmd/HordeCmd.csproj", | |
| "Engine/Source/Programs/Horde/HordeServer/HordeServer.csproj", | |
| "Engine/Source/Programs/Horde/HordeServer.Tests/HordeServer.Tests.csproj", | |
| "Engine/Source/Programs/Horde/Drivers/JobDriver/JobDriver.csproj", | |
| "Engine/Source/Programs/Horde/Drivers/JobDriver.Tests/JobDriver.Tests.csproj" | |
| ]; | |
| foreach (string testProject in projectsToTest) | |
| { | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = $"build \"{testProject}\" -p:Configuration=Analyze", | |
| })); | |
| } | |
| } | |
| /// <summary> | |
| /// Builds the Horde project which produces the Horde CLI tool. | |
| /// </summary> | |
| private static async Task<BgFileSet> BuildCmd() | |
| { | |
| FileUtils.ForceDeleteDirectory(CmdDirectory); | |
| string arguments = "publish"; | |
| arguments += $" \"{Unreal.RootDirectory}/Engine/Source/Programs/Horde/HordeCmd/HordeCmd.csproj\""; | |
| arguments += $" --output \"{CmdDirectory}\""; | |
| arguments += " --runtime win-x64 --self-contained"; | |
| arguments += DotnetBuilds.GetVersionArguments(); | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = arguments, | |
| })); | |
| return FileSet.FromDirectory(CmdDirectory); | |
| } | |
| /// <summary> | |
| /// Builds the Horde agent itself. | |
| /// </summary> | |
| private static async Task<BgFileSet> BuildAgent(string runtime) | |
| { | |
| DirectoryReference outputDir = GetAgentDirectory(runtime); | |
| FileUtils.ForceDeleteDirectory(outputDir); | |
| string commonArguments = DotnetBuilds.GetVersionArguments(); | |
| if (!string.IsNullOrEmpty(runtime)) | |
| { | |
| commonArguments += $" --runtime={runtime} --self-contained true"; | |
| } | |
| string agentArguments = "publish"; | |
| agentArguments += $" \"{Unreal.RootDirectory.FullName}/Engine/Source/Programs/Horde/HordeAgent/HordeAgent.csproj\""; | |
| agentArguments += $" --output \"{outputDir}\""; | |
| agentArguments += commonArguments; | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = agentArguments, | |
| })); | |
| string driverArguments = "publish"; | |
| driverArguments += $" \"{Unreal.RootDirectory.FullName}/Engine/Source/Programs/Horde/Drivers/JobDriver/JobDriver.csproj\""; | |
| driverArguments += $" --output \"{outputDir}/JobDriver\""; | |
| driverArguments += commonArguments; | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = driverArguments, | |
| })); | |
| return FileSet.FromDirectory(outputDir); | |
| } | |
| /// <summary> | |
| /// Builds the Horde dashboard Docker image. | |
| /// | |
| /// We only intend to distribute the dashboard as part of the Horde server itself. We therefor | |
| /// do not support pushing the Horde dashboard Docker image to a remote registry. | |
| /// </summary> | |
| private static async Task BuildDashboard() | |
| { | |
| await StandardTasks.ExecuteAsync(new CopyTask(new CopyTaskParameters | |
| { | |
| From = "Engine/Source/Programs/Horde/Docs/...", | |
| To = "Engine/Source/Programs/Horde/HordeDashboard/documentation/Docs/...", | |
| })); | |
| await StandardTasks.ExecuteAsync(new CopyTask(new CopyTaskParameters | |
| { | |
| From = "Engine/Source/Programs/Horde/README.md", | |
| To = "Engine/Source/Programs/Horde/HordeDashboard/documentation/README.md", | |
| })); | |
| await StandardTasks.ExecuteAsync(new TagTask(new TagTaskParameters | |
| { | |
| Files = "Engine/Source/Programs/Horde/HordeDashboard/...", | |
| Except = "Engine/Source/Programs/Horde/HordeDashboard/node_modules/...", | |
| With = "#InputFiles", | |
| })); | |
| string buildArgs = $"--build-arg \"VersionInfo={DotnetBuilds.GetEngineVersion()}-{PerforceUtils.Change}\""; | |
| buildArgs += " --build-arg \"DashboardConfig=Production\""; | |
| await StandardTasks.ExecuteAsync(new DockerBuildTask(new DockerBuildTaskParameters | |
| { | |
| BaseDir = "Engine", | |
| Files = "#InputFiles", | |
| DockerFile = "Engine/Source/Programs/Horde/HordeDashboard/Dockerfile", | |
| Arguments = buildArgs, | |
| Tag = "hordedashboard-public", // This name is referenced in the Dockerfile.dashboard from Horde server | |
| })); | |
| } | |
| /// <summary> | |
| /// Builds the Horde server Docker image. | |
| /// | |
| /// This node must be built on the same agent as the dashboard as it relies on the dashboard image | |
| /// existing on the local machine. | |
| /// </summary> | |
| private static async Task BuildServer() | |
| { | |
| List<string> files = | |
| [ | |
| "Engine/Binaries/DotNET/EpicGames.Perforce.Native/...", | |
| "Engine/Source/Programs/Shared/...", | |
| "Engine/Source/Programs/Horde/...", | |
| "Engine/Source/Programs/AutomationTool/AutomationUtils/Matchers/...", | |
| "Engine/Source/Programs/UnrealBuildTool/Matchers/..." | |
| ]; | |
| await StandardTasks.ExecuteAsync(new TagTask(new TagTaskParameters | |
| { | |
| Files = string.Join(";", files), | |
| Except = ".../.vs/...;.../.git/...;.../bin/...;.../obj/...", | |
| With = "#InputFiles", | |
| })); | |
| // Build a Docker image with just the Horde server | |
| await StandardTasks.ExecuteAsync(new DockerBuildTask(new DockerBuildTaskParameters | |
| { | |
| BaseDir = "Engine", | |
| Files = "#InputFiles", | |
| DockerFile = "Engine/Source/Programs/Horde/HordeServer/Dockerfile", | |
| Arguments = $"--build-arg msbuild_args=\"{DotnetBuilds.GetVersionArguments()}\"", | |
| Tag = "horde-server-bare", // This name is referenced in the Dockerfile.dashboard from Horde server | |
| })); | |
| // Build a Docker image that combines the bare Horde server with the Horde dashboard | |
| await StandardTasks.ExecuteAsync(new DockerBuildTask(new DockerBuildTaskParameters | |
| { | |
| BaseDir = "Engine/Source/Programs/Horde/HordeServer", | |
| Files = "Dockerfile*", | |
| DockerFile = "Engine/Source/Programs/Horde/HordeServer/Dockerfile.dashboard", | |
| Tag = "horde-server", | |
| })); | |
| } | |
| /// <summary> | |
| /// Publishes the Horde server Docker image. | |
| /// </summary> | |
| private static async Task PublishServer() | |
| { | |
| if (!CommandUtils.IsBuildMachine) | |
| { | |
| return; | |
| } | |
| await StandardTasks.ExecuteAsync(new DockerPushTask(new DockerPushTaskParameters | |
| { | |
| Repository = "", | |
| Image = "horde-server", | |
| TargetImage = $"epic/horde/server:{PerforceUtils.Change}", | |
| RepositoryAuthFile = "" | |
| })); | |
| } | |
| private static DirectoryReference GetAgentDirectory(string runtime) | |
| { | |
| if (!string.IsNullOrEmpty(runtime)) | |
| { | |
| return DirectoryReference.Combine(AgentDirectory, runtime); | |
| } | |
| return DirectoryReference.Combine(AgentDirectory, "other"); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| using AutomationTool; | |
| using AutomationTool.Tasks; | |
| using EpicGames.BuildGraph.Expressions; | |
| using EpicGames.Core; | |
| using SDBuildGraph.Automation.Nodes; | |
| using SDBuildGraph.Automation.Utilities; | |
| using Splash.BgArgParser; | |
| using Splash.BgArgParser.OptionTypes; | |
| using UnrealBuildBase; | |
| namespace SDBuildGraph.Automation.Builders; | |
| public class BuildP4vUtils : BgGraphBuilder | |
| { | |
| private static readonly DirectoryReference StagingDir = DirectoryReference.Combine(Unreal.RootDirectory, "LocalBuilds", "P4vUtils"); | |
| private static readonly DirectoryReference ProjectDir = DirectoryReference.Combine(Unreal.RootDirectory, "Engine", "Extras", "P4VUtils"); | |
| private static readonly FileReference ProjectFile = FileReference.Combine(ProjectDir, "P4VUtils.csproj"); | |
| public override BgGraph CreateGraph(BgEnvironment environment) | |
| { | |
| // Script arguments | |
| BooleanOption publish = new BooleanOption("Publish", false); | |
| Parser parser = Parser.NewFromEnvironment(); | |
| parser.Add(publish); | |
| parser.Parse(); | |
| // Agent | |
| BgAgent agent = new BgAgent("Build P4VUtils", "CompileWin64"); | |
| // Labels | |
| BgLabel label = new BgLabel(name: "P4VUtils", category: "Other"); | |
| // Nodes | |
| BgNode buildNode = agent | |
| .AddNode(name: "Build P4VUtils", func: context => BuildAsync()) | |
| .AddLabel(label); | |
| BgNode analyzeNode = agent | |
| .AddNode(name: "Analyze P4VUtils", func: context => DotnetBuilds.AnalyzeAsync(ProjectFile)) | |
| .AddLabel(label); | |
| BgNode publishNode = agent | |
| .AddNode( | |
| name: "Publish P4VUtils", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "p4v-utils", | |
| Directory = StagingDir | |
| } | |
| ) | |
| ) | |
| .Requires(buildNode) | |
| .AddLabel(label); | |
| // Finalize | |
| List<BgNode> nodes = | |
| [ | |
| buildNode, | |
| analyzeNode | |
| ]; | |
| if (publish.Value) | |
| { | |
| nodes.Add(publishNode); | |
| } | |
| BgAggregate aggregate = new BgAggregate("Aggregate", nodes.ToList()); | |
| return new BgGraph(nodes, aggregate); | |
| } | |
| private static async Task<BgFileSet> BuildAsync() | |
| { | |
| FileUtils.ForceDeleteDirectory(StagingDir); | |
| string arguments = "publish"; | |
| arguments += $" \"{ProjectFile}\""; | |
| arguments += $" --output \"{StagingDir}\""; | |
| arguments += " --runtime win-x64 --self-contained --configuration Release"; | |
| arguments += " -p:IsWindows=true -p:IsOSX=false -p:IsLinux=false -p:WithRestricted=false"; | |
| arguments += DotnetBuilds.GetVersionArguments(); | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = arguments, | |
| BaseDir = ProjectDir.FullName, | |
| })); | |
| return FileSet.FromDirectory(StagingDir); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| using AutomationTool; | |
| using AutomationTool.Tasks; | |
| using EpicGames.BuildGraph.Expressions; | |
| using EpicGames.Core; | |
| using SDBuildGraph.Automation.Nodes; | |
| using Splash.BgArgParser; | |
| using Splash.BgArgParser.OptionTypes; | |
| using UnrealBuildBase; | |
| namespace SDBuildGraph.Automation.Builders; | |
| public class BuildUnrealGameSync : BgGraphBuilder | |
| { | |
| private static readonly DirectoryReference StagingDir = DirectoryReference.Combine(Unreal.RootDirectory, "LocalBuilds", "UnrealGameSync"); | |
| private static readonly DirectoryReference AppOutputDir = DirectoryReference.Combine(StagingDir, "App"); | |
| private static readonly DirectoryReference MsiOutputDir = DirectoryReference.Combine(StagingDir, "Msi"); | |
| private static readonly DirectoryReference CmdOutputDir = DirectoryReference.Combine(StagingDir, "Cmd"); | |
| private static readonly DirectoryReference UgsSourceDir = DirectoryReference.Combine(Unreal.RootDirectory, "Engine", "Source", "Programs", "UnrealGameSync"); | |
| public override BgGraph CreateGraph(BgEnvironment environment) | |
| { | |
| // Script arguments | |
| BooleanOption publishApp = new BooleanOption("PublishApp", false); | |
| BooleanOption publishMsi = new BooleanOption("PublishMsi", false); | |
| BooleanOption publishCmd = new BooleanOption("PublishCmd", false); | |
| Parser parser = Parser.NewFromEnvironment(); | |
| parser.Add(publishApp); | |
| parser.Add(publishMsi); | |
| parser.Add(publishCmd); | |
| parser.Parse(); | |
| // Agent | |
| BgAgent agent = new BgAgent("Build UnrealGameSync", "CompileWin64"); | |
| // Labels | |
| BgLabel appLabel = new(name: "Application", category: "Other"); | |
| BgLabel msiLabel = new(name: "Installer", category: "Other"); | |
| BgLabel cmdLabel = new(name: "Command-Line", category: "Other"); | |
| // Nodes | |
| BgNode buildUgsNode = agent.AddNode(name: "Build UnrealGameSync", func: context => BuildUgsAsync()) | |
| .AddLabel(appLabel); | |
| BgNode publishUgsNode = agent.AddNode( | |
| name: "Publish UnrealGameSync", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "ugs-win", | |
| Directory = AppOutputDir | |
| } | |
| ) | |
| ) | |
| .Requires(buildUgsNode) | |
| .AddLabel(appLabel); | |
| BgNode buildMsiNode = agent.AddNode(name: "Build Installer", func: context => BuildMsiAsync()) | |
| .Requires(buildUgsNode) | |
| .AddLabel(msiLabel); | |
| BgNode publishMsiNode = agent.AddNode( | |
| name: "Publish Installer", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "ugs-msi", | |
| Directory = MsiOutputDir | |
| } | |
| ) | |
| ) | |
| .Requires(buildMsiNode) | |
| .AddLabel(msiLabel); | |
| BgNode buildCmdNode = agent.AddNode(name: "Build Command-Line", func: context => BuildCmdAsync()) | |
| .AddLabel(cmdLabel); | |
| BgNode publishCmdNode = agent.AddNode( | |
| name: "Publish Command-Line", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "ugs-cmd", | |
| Directory = CmdOutputDir | |
| } | |
| ) | |
| ) | |
| .Requires(buildCmdNode) | |
| .AddLabel(cmdLabel); | |
| // Finalize | |
| List<BgNode> nodes = | |
| [ | |
| buildUgsNode, | |
| buildMsiNode, | |
| buildCmdNode | |
| ]; | |
| if (publishApp.Value) | |
| { | |
| nodes.Add(publishUgsNode); | |
| } | |
| if (publishMsi.Value) | |
| { | |
| nodes.Add(publishMsiNode); | |
| } | |
| if (publishCmd.Value) | |
| { | |
| nodes.Add(publishCmdNode); | |
| } | |
| BgAggregate aggregate = new BgAggregate("Aggregate", nodes.ToList()); | |
| return new BgGraph(nodes, aggregate); | |
| } | |
| private static async Task<BgFileSet> BuildUgsAsync() | |
| { | |
| FileUtils.ForceDeleteDirectory(AppOutputDir); | |
| // Build the UGS GUI application | |
| FileReference ugsProjectFile = FileReference.Combine(UgsSourceDir, "UnrealGameSync", "UnrealGameSync.csproj"); | |
| string arguments = "publish"; | |
| arguments += $" \"{ugsProjectFile}\""; | |
| arguments += $" --output \"{AppOutputDir}\""; | |
| arguments += " --runtime win-x64 --self-contained --configuration ReleaseAutoUpdate"; | |
| arguments += Utilities.DotnetBuilds.GetVersionArguments(); | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = arguments, | |
| })); | |
| // Build the UGS CMD application to be bundled with the GUI | |
| FileReference cmdProjectFile = FileReference.Combine(UgsSourceDir, "UnrealGameSyncCmd", "UnrealGameSyncCmd.csproj"); | |
| arguments = "publish"; | |
| arguments += $" \"{cmdProjectFile}\""; | |
| arguments += $" --output \"{AppOutputDir}\""; | |
| arguments += " --runtime win-x64 --self-contained --configuration Release"; | |
| arguments += Utilities.DotnetBuilds.GetVersionArguments(); | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = arguments, | |
| })); | |
| return FileSet.FromDirectory(AppOutputDir); | |
| } | |
| private static async Task<BgFileSet> BuildMsiAsync() | |
| { | |
| FileUtils.ForceDeleteDirectory(MsiOutputDir); | |
| DirectoryReference wixDir = DirectoryReference.Combine(Unreal.RootDirectory, "Engine", "Source", "ThirdParty", "WiX", "3.8"); | |
| DirectoryReference installerDir = DirectoryReference.Combine(UgsSourceDir, "Installer"); | |
| DirectoryReference installerBinDir = DirectoryReference.Combine(installerDir, "bin"); | |
| await StandardTasks.ExecuteAsync(new SpawnTask(new SpawnTaskParameters | |
| { | |
| Exe = FileReference.Combine(wixDir, "heat.exe").FullName, | |
| Arguments = $"dir \"{AppOutputDir}\" -cg UGSLauncher_Project -dr INSTALLFOLDER -scom -sreg -srd -var var.BasePath -gg -sfrag -out \"obj/UGSLauncher.wxs\"", | |
| WorkingDir = installerDir.FullName | |
| })); | |
| await StandardTasks.ExecuteAsync(new SpawnTask(new SpawnTaskParameters | |
| { | |
| Exe = FileReference.Combine(wixDir, "candle.exe").FullName, | |
| Arguments = $"-dBasePath=\"{AppOutputDir}\" -dConfiguration=Release -dPlatform=x64 -arch x86 -ext WixUtilExtension \"Product.wxs\" \"obj/UGSLauncher.wxs\" -out \"obj/\"", | |
| WorkingDir = installerDir.FullName | |
| })); | |
| await StandardTasks.ExecuteAsync(new SpawnTask(new SpawnTaskParameters | |
| { | |
| Exe = FileReference.Combine(wixDir, "light.exe").FullName, | |
| Arguments = "-cultures:null -ext WixUtilExtension -sice:ICE69 \"obj/Product.wixobj\" \"obj/UGSLauncher.wixobj\" -out \"bin/UnrealGameSync.msi\" -pdbout \"bin/UnrealGameSync.pdb\"", | |
| WorkingDir = installerDir.FullName | |
| })); | |
| FileSet msiFile = FileSet.FromFile(installerBinDir, "UnrealGameSync.msi"); | |
| await msiFile.CopyToAsync(MsiOutputDir); | |
| // If this is a build machine, then sign the MSI | |
| if (CommandUtils.IsBuildMachine) | |
| { | |
| List<FileReference> files = BgTaskImpl.ResolveFilespec(Unreal.RootDirectory, $"{MsiOutputDir}/...", new Dictionary<string, HashSet<FileReference>>()).OrderBy(x => x.FullName).ToList(); | |
| CodeSign.SignMultipleFilesIfEXEOrDLL(files, Description: "Description", bRunInParallel: false); | |
| } | |
| return FileSet.FromDirectory(MsiOutputDir); | |
| } | |
| private static async Task<BgFileSet> BuildCmdAsync() | |
| { | |
| FileUtils.ForceDeleteDirectory(CmdOutputDir); | |
| List<string> platforms = | |
| [ | |
| "osx-x64", | |
| "linux-x64", | |
| "win-x64" | |
| ]; | |
| FileReference cmdProjectFile = FileReference.Combine(UgsSourceDir, "UnrealGameSyncCmd", "UnrealGameSyncCmd.csproj"); | |
| foreach (string platform in platforms) | |
| { | |
| string arguments = "publish"; | |
| arguments += $" \"{cmdProjectFile}\""; | |
| arguments += $" --output \"{CmdOutputDir}\""; | |
| arguments += $" --runtime {platform} --no-self-contained --configuration Release"; | |
| arguments += Utilities.DotnetBuilds.GetVersionArguments(); | |
| await StandardTasks.ExecuteAsync(new DotNetTask(new DotNetTaskParameters | |
| { | |
| Arguments = arguments, | |
| })); | |
| } | |
| return FileSet.FromDirectory(CmdOutputDir); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| using AutomationTool; | |
| using AutomationTool.Tasks; | |
| using EpicGames.BuildGraph.Expressions; | |
| using EpicGames.Core; | |
| using SDBuildGraph.Automation.Nodes; | |
| using UnrealBuildBase; | |
| namespace SDBuildGraph.Automation.Builders; | |
| public class PublishZenServer : BgGraphBuilder | |
| { | |
| private static readonly DirectoryReference StagingDir = DirectoryReference.Combine(Unreal.RootDirectory, "LocalBuilds", "ZenServer"); | |
| public override BgGraph CreateGraph(BgEnvironment environment) | |
| { | |
| // Agent | |
| BgAgent agent = new BgAgent("Publish Zen server", "CompileWin64"); | |
| // Labels | |
| BgLabel label = new(name: "zen", category: "Other"); | |
| // Nodes | |
| BgNode stageNode = agent | |
| .AddNode( | |
| name: "Stage Zen server", | |
| func: context => StageZenServer() | |
| ) | |
| .AddLabel(label); | |
| BgNode publishNode = agent | |
| .AddNode( | |
| name: "Publish Zen server", | |
| func: context => PublishToolNode.PublishToolAsync( | |
| new PublishToolParameters | |
| { | |
| ToolId = "zenserver", | |
| Directory = StagingDir | |
| } | |
| ) | |
| ) | |
| .AddLabel(label); | |
| // Finalize | |
| List<BgNode> nodes = | |
| [ | |
| stageNode, | |
| publishNode, | |
| ]; | |
| BgAggregate aggregate = new BgAggregate("Aggregate", nodes.ToList()); | |
| return new BgGraph(nodes, aggregate); | |
| } | |
| private static async Task<BgFileSet> StageZenServer() | |
| { | |
| FileUtils.ForceDeleteDirectory(StagingDir); | |
| FileSet toolFiles = FileSet.Empty; | |
| toolFiles += FileSet.FromFile(DirectoryReference.Combine(Unreal.RootDirectory, "Engine", "Binaries", "Linux"), "zenserver"); | |
| toolFiles += FileSet.FromFile(DirectoryReference.Combine(Unreal.RootDirectory, "Engine", "Binaries", "Win64"), "zenserver.exe"); | |
| await toolFiles.CopyToAsync(StagingDir); | |
| return FileSet.FromDirectory(StagingDir); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.Linq; | |
| using AutomationTool; | |
| using EpicGames.BuildGraph; | |
| using EpicGames.BuildGraph.Expressions; | |
| using EpicGames.Core; | |
| using Microsoft.Extensions.Logging; | |
| using SDBuildGraph.Automation.Utilities; | |
| using Splash.BgArgParser; | |
| using Splash.BgArgParser.OptionTypes; | |
| using UnrealBuildBase; | |
| namespace SDBuildGraph.Automation.Builders; | |
| /// <summary> | |
| /// Abstract class that contains common functionality useful to all projects. | |
| /// | |
| /// Each project is expected to create another abstract class which subclasses this. The project | |
| /// version of this should set configuration options which are consistent across all build scripts | |
| /// within the project. Things such as ProjectName. | |
| /// | |
| /// Each build script should then implement the project specific class. | |
| /// | |
| /// Note that we do things a little different than what Epic expect with this class. The expectation | |
| /// from Epic is that every implementation of BgGraphBuilder implements a CreateGraph method and that | |
| /// is what the actual BuildGraph execution code calls. While that is still the case here, we don't | |
| /// want to modify the engine code, we implement the CreateGraph method in this class and expect each | |
| /// build script to implement a CreateGraphInternal method. | |
| /// | |
| /// The CreateGraphInternal method does not return anything itself. Anything that a build script wants | |
| /// made available in the graph must be added to the AllNodes property. When executed the CreateGraph | |
| /// method contained in this class will create the final graph based on what is contained in the | |
| /// AllNodes property. | |
| /// </summary> | |
| public abstract class SdBgGraphBuilder : BgGraphBuilder | |
| { | |
| /// <summary> | |
| /// Directory reference for the root of the current workspace. | |
| /// </summary> | |
| public static DirectoryReference RootDirectory => Unreal.RootDirectory; | |
| /// <summary> | |
| /// FileSet containing the entire current workspace. | |
| /// </summary> | |
| public static FileSet Workspace => FileSet.FromDirectory(Unreal.RootDirectory); | |
| /// <summary> | |
| /// DirectoryReference of the location on local disk to store build artifacts. | |
| /// Is an expression bodied member as users are not meant to change this, only here for easy access. | |
| /// </summary> | |
| public static DirectoryReference LocalOutputDirectory => new(Path.Combine(RootDirectory.ToString(), "LocalBuilds")); | |
| /// <summary> | |
| /// A set of BuildGraph file specs that can be used to filter debug symbols. | |
| /// </summary> | |
| public static string SymbolsFilter = "*.pdb;*.psym;*.sym;*.debug;*.dSYM;*.dSYM.zip"; | |
| /// <summary> | |
| /// The shelved changelist number of a preflight build. | |
| /// | |
| /// Horde automatically passes this argument anytime that it runs a preflight build. | |
| /// </summary> | |
| public int PreflightChange; | |
| /// <summary> | |
| /// Is the current running job a Horde preflight build. | |
| /// </summary> | |
| public bool IsPreflight => PreflightChange > 0; | |
| /// <summary> | |
| /// Simple string representation of the projects name. | |
| /// </summary> | |
| public string ProjectName | |
| { | |
| get => !string.IsNullOrEmpty(_projectName) ? _projectName : throw new InvalidOperationException("ProjectName is required to be set!"); | |
| set => _projectName = value; | |
| } | |
| /// <summary> | |
| /// Name of the client target. Defaults to just the project name but can be overriden. | |
| /// </summary> | |
| public string PlatformTargetClient | |
| { | |
| get => string.IsNullOrEmpty(_platformTargetClient) ? ProjectName : _platformTargetClient; | |
| set => _platformTargetClient = value; | |
| } | |
| /// <summary> | |
| /// Name of the server target. Defaults to the project name + Server but can be overriden. | |
| /// </summary> | |
| public string PlatformTargetServer | |
| { | |
| get => string.IsNullOrEmpty(_platformTargetServer) ? $"{ProjectName}Server" : _platformTargetServer; | |
| set => _platformTargetServer = value; | |
| } | |
| /// <summary> | |
| /// Name of the editor target. Defaults to the project name + Editor but can be overriden. | |
| /// </summary> | |
| public string PlatformTargetEditor | |
| { | |
| get => string.IsNullOrEmpty(_platformTargetEditor) ? $"{ProjectName}Editor" : _platformTargetEditor; | |
| set => _platformTargetEditor = value; | |
| } | |
| /// <summary> | |
| /// The name of the project in UGS. | |
| /// | |
| /// If not set this will default to the same as ProjectName. You do not have to set it. Only required if | |
| /// the project uses a different name in UGS. | |
| /// </summary> | |
| public string ProjectUgsName | |
| { | |
| get => string.IsNullOrEmpty(_projectUgsName) ? ProjectName : _projectUgsName; | |
| set => _projectUgsName = value; | |
| } | |
| /// <summary> | |
| /// The name of the project as used in the network storage path. | |
| /// | |
| /// If not set this will default to the same as ProjectName. You do not have to set it. Only required if | |
| /// the project has a different name in the network path. | |
| /// | |
| /// Note that this whole thing assumes all projects use the same basic structure for network paths. If that | |
| /// changes for some reason we will need to adjust how we form the network path. | |
| /// </summary> | |
| public string ProjectNetworkName | |
| { | |
| get => string.IsNullOrEmpty(_projectNetworkName) ? ProjectName : _projectNetworkName; | |
| set => _projectNetworkName = value; | |
| } | |
| /// <summary> | |
| /// Base path on the network for the project. | |
| /// | |
| /// This is not the full path of where we store things on the network. This is the base path and is used | |
| /// by other code to calculate the full path of different network storage things. | |
| /// | |
| /// Is an expression bodied member as users are not meant to change this, only here for easy access. | |
| /// </summary> | |
| public string NetworkBaseDirectory => $@"\\networkpath\{ProjectNetworkName}"; | |
| /// <summary> | |
| /// Stores all of the generated nodes. | |
| /// | |
| /// We use a dictionary so that we can get and reference nodes at anytime. For example the | |
| /// gauntlet testing extension needs to be able to add packaging dependencies. To avoid developers | |
| /// having to constantly manage generated nodes and pass them along we can instead have each | |
| /// extension that needs a node from another extension call the relevant get node method. | |
| /// </summary> | |
| private readonly Dictionary<string, BgNode> _allNodes = new(); | |
| /// <summary> | |
| /// Stores all generated labels for a graph. | |
| /// | |
| /// It's important to reuse the same label object for everything that you want within a label of the same | |
| /// name. If you create multiple label objects, even if they have the exact same settings, you will get | |
| /// multiple labels generated within Horde. Furthermore all of those labels will point to whatever the | |
| /// first label was. As we want to split out BuildGraph logic into different methods and possible even classes | |
| /// we need a central place to store labels that can be accessed. | |
| /// </summary> | |
| private readonly Dictionary<string, BgLabel> _allLabels = new(); | |
| /// <summary> | |
| /// Stores all the generated agents for a graph. | |
| /// | |
| /// This is useful in circumstances where it is unknown which nodes will be added to an agent, those nodes can be | |
| /// added from multiple places, but those nodes should all run on the same agent. | |
| /// </summary> | |
| private readonly Dictionary<string, BgAgent> _allAgents = new(); | |
| private string _projectUgsName; | |
| private string _projectName; | |
| private string _platformTargetClient; | |
| private string _platformTargetServer; | |
| private string _platformTargetEditor; | |
| private string _projectNetworkName; | |
| /// <summary> | |
| /// Optional label to be assigned the aggregate. If this is null then no label will be added the aggregate. | |
| /// </summary> | |
| private BgLabel _aggregateLabel; | |
| protected SdBgGraphBuilder() | |
| { | |
| IntegerOption preflightChange = new IntegerOption("PreflightChange", -1); | |
| Parser parser = Parser.NewFromEnvironment(); | |
| parser.Add(preflightChange); | |
| parser.Parse(); | |
| PreflightChange = preflightChange.Value; | |
| } | |
| /// <summary> | |
| /// Returns a standardized string representing the version of a build. | |
| /// </summary> | |
| public virtual string GetBuildVersion() | |
| { | |
| string buildVersion = $"{ProjectName}-CL-{PerforceUtils.Change}"; | |
| if (CommandUtils.IsBuildMachine) | |
| { | |
| buildVersion += $"-{HordeEnvironmentUtils.HordeJobId}"; | |
| } | |
| if (IsPreflight) | |
| { | |
| buildVersion += $"-PF-{PreflightChange}"; | |
| } | |
| return buildVersion; | |
| } | |
| public FileReference GetProjectFile() | |
| { | |
| return new FileReference(Path.Combine(RootDirectory.FullName, ProjectName, $"{ProjectName}.uproject")); | |
| } | |
| public ProjectProperties GetProjectProperties() | |
| { | |
| return ProjectUtils.GetProjectProperties(GetProjectFile()); | |
| } | |
| public SingleTargetProperties GetClientTarget() | |
| { | |
| try | |
| { | |
| return GetProjectProperties().Targets.First(t => t.TargetName.Equals(PlatformTargetClient, StringComparison.OrdinalIgnoreCase)); | |
| } | |
| catch (Exception) | |
| { | |
| Logger.LogError($"Failed to find a valid target for '{PlatformTargetClient}'"); | |
| throw; | |
| } | |
| } | |
| public SingleTargetProperties GetServerTarget() | |
| { | |
| try | |
| { | |
| return GetProjectProperties().Targets.First(t => t.TargetName.Equals(PlatformTargetServer, StringComparison.OrdinalIgnoreCase)); | |
| } | |
| catch (Exception) | |
| { | |
| Logger.LogError($"Failed to find a valid target for '{PlatformTargetServer}'"); | |
| throw; | |
| } | |
| } | |
| public SingleTargetProperties GetEditorTarget() | |
| { | |
| try | |
| { | |
| return GetProjectProperties().Targets.First(t => t.TargetName.Equals(PlatformTargetEditor, StringComparison.OrdinalIgnoreCase)); | |
| } | |
| catch (Exception) | |
| { | |
| Logger.LogError($"Failed to find a valid target for '{PlatformTargetEditor}'"); | |
| throw; | |
| } | |
| } | |
| /// <summary> | |
| /// Adds the node with a specified name. If the node exists, an exception will be thrown. | |
| /// </summary> | |
| public void AddNode(string nodeName, BgNode node) | |
| { | |
| if (!_allNodes.TryAdd(nodeName, node)) | |
| { | |
| throw new Exception($"The '{nodeName}' node already exists!"); | |
| } | |
| } | |
| /// <summary> | |
| /// Gets the node with a specified name. If the node doesn't exist, an exception will be thrown. | |
| /// </summary> | |
| public BgNode GetNode(string nodeName) | |
| { | |
| if (!_allNodes.TryGetValue(nodeName, out BgNode node)) | |
| { | |
| throw new Exception($"The '{nodeName}' node does not exist! Make sure you add it first."); | |
| } | |
| return node; | |
| } | |
| /// <summary> | |
| /// Gets all the nodes | |
| /// </summary> | |
| public List<BgNode> GetNodes() => _allNodes.Values.ToList(); | |
| /// <summary> | |
| /// Gets all the node names | |
| /// </summary> | |
| public List<string> GetNodeNames() => _allNodes.Keys.ToList(); | |
| /// <summary> | |
| /// Gets an existing BgLabel from the AllLabels dictionary if it exists, otherwise creates one and stores it in AllLabels. | |
| /// It is super important we reuse the underlying object for BgLabel. See the documentation for AllLabels for more details. | |
| /// </summary> | |
| public BgLabel GetOrCreateLabel(string category, string name, bool addUgsBadge = false) | |
| { | |
| BgString ugsBadge = addUgsBadge ? name : BgString.Empty; | |
| BgString ugsProject = addUgsBadge ? ProjectUgsName : BgString.Empty; | |
| string key = $"{category}-{name}"; | |
| if (_allLabels.TryGetValue(key, out BgLabel createLabel)) | |
| { | |
| return createLabel; | |
| } | |
| BgLabel label = new(name, category, ugsBadge, ugsProject, BgLabelChange.Current); | |
| _allLabels[key] = label; | |
| return label; | |
| } | |
| /// <summary> | |
| /// Gets an existing BgAgent from the <see cref="_allAgents"/> member if it already exists, otherwise creates a new one | |
| /// and stores it in <see cref="_allAgents"/>. | |
| /// </summary> | |
| public BgAgent GetOrCreateAgent(string agentName, string agentType = null) | |
| { | |
| if (_allAgents.TryGetValue(agentName, out BgAgent createAgent)) | |
| { | |
| return createAgent; | |
| } | |
| if (string.IsNullOrWhiteSpace(agentType)) | |
| { | |
| throw new ArgumentNullException(nameof(agentType)); | |
| } | |
| BgAgent agent = new BgAgent(agentName, agentType); | |
| _allAgents[agentName] = agent; | |
| return agent; | |
| } | |
| /// <summary> | |
| /// Sets the name to be used for the aggregate label. This is optional. If this method is never called then there will not | |
| /// be a label created for the aggregate. | |
| /// </summary> | |
| public void SetAggregateLabel(string graphLabelName) | |
| { | |
| _aggregateLabel = GetOrCreateLabel("Other", graphLabelName, addUgsBadge: true); | |
| } | |
| /// <summary> | |
| /// The Splash Damage implementation of CreateGraph. | |
| /// | |
| /// Build scripts must implement the CreateGraphInternal method, which will be called from here. This method | |
| /// itself allows us to do some standard setup and finalization that is consistent across all of our build | |
| /// scripts. | |
| /// </summary> | |
| public sealed override BgGraph CreateGraph(BgEnvironment env) | |
| { | |
| CreateGraphInternal(); | |
| BgAggregate aggregate = new BgAggregate("Aggregate", GetNodes(), _aggregateLabel); | |
| return new BgGraph(GetNodes(), aggregate); | |
| } | |
| /// <summary> | |
| /// Every build script must implement this method. | |
| /// </summary> | |
| public abstract void CreateGraphInternal(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Collections.Generic; | |
| using System.Threading.Tasks; | |
| using EpicGames.BuildGraph.Expressions; | |
| using EpicGames.Core; | |
| using UnrealBuildTool; | |
| using AutomationTool; | |
| using AutomationTool.Tasks; | |
| using SDBuildGraph.Automation.Builders; | |
| using SDBuildGraph.Automation.Data; | |
| using SDBuildGraph.Automation.Utilities; | |
| namespace SDBuildGraph.Automation.Nodes; | |
| public class PlatformCompileParameters( | |
| PlatformAndTarget platform, | |
| List<UnrealTargetConfiguration> configurations, | |
| bool addUgsBadges = false | |
| ) | |
| { | |
| public SingleTargetProperties Target { get; set; } = platform.Target; | |
| public UnrealTargetPlatform Platform { get; set; } = platform.Platform; | |
| public List<UnrealTargetConfiguration> Configurations { get; set; } = configurations; | |
| public bool AddUgsBadges { get; set; } = addUgsBadges; | |
| } | |
| public static class PlatformCompileNode | |
| { | |
| /// <summary> | |
| /// Handles the specifics for creating individual compile nodes. | |
| /// </summary> | |
| public static void AddCompilePlatform( | |
| SdBgGraphBuilder builder, | |
| PlatformCompileParameters platformCompileParameters, | |
| CommonCompileParameters commonCompileParameters, | |
| UploadSymbolsParameters symbolsParameters | |
| ) | |
| { | |
| BgAgent agent = builder.GetOrCreateAgent( | |
| agentName: GetCompileAgentName(platformCompileParameters.Target, platformCompileParameters.Platform), | |
| BgAgentUtils.GetCompileAgentType(commonCompileParameters.UseIncrementalWorkspace) | |
| ); | |
| string compileNodeName = GetCompileNodeName(platformCompileParameters.Target.Rules.Type, platformCompileParameters.Platform); | |
| BgNode<(BgFileSet, BgFileSet)> platformNode = agent | |
| .AddNode( | |
| name: compileNodeName, | |
| func: context => PlatformCompileAsync( | |
| platformCompileParameters.Target, | |
| platformCompileParameters.Platform, | |
| platformCompileParameters.Configurations, | |
| builder.GetBuildVersion(), | |
| commonCompileParameters.WindowsCompilerVersion, | |
| commonCompileParameters.WarningsAsError, | |
| commonCompileParameters.StaticAnalysis, | |
| commonCompileParameters.CompileNonUnity, | |
| symbolsParameters | |
| ) | |
| ) | |
| .Requires(ToolsAndEditorNode.GetSetVersionNode(builder)) | |
| .AddLabel(PlatformUtils.GetOrCreatePlatformLabel( | |
| builder, | |
| platformCompileParameters.Target.Rules.Type, | |
| platformCompileParameters.Platform, | |
| platformCompileParameters.AddUgsBadges | |
| )); | |
| builder.AddNode(compileNodeName, platformNode); | |
| } | |
| public static string GetCompileAgentName(SingleTargetProperties target, UnrealTargetPlatform platform) | |
| { | |
| return $"Compile {target.TargetName} {platform}"; | |
| } | |
| /// <summary> | |
| /// Gets the compile platform node for a specified platform. | |
| /// </summary> | |
| public static BgNode GetPlatformCompileNode(SdBgGraphBuilder builder, TargetType targetType, UnrealTargetPlatform platform) | |
| { | |
| return builder.GetNode(GetCompileNodeName(targetType, platform)); | |
| } | |
| /// <summary> | |
| /// Returns a BgFileSet representing the platform binaries. | |
| /// </summary> | |
| public static BgFileSet GetPlatformBinaries(SdBgGraphBuilder builder, TargetType targetType, UnrealTargetPlatform platform) | |
| { | |
| return new BgFileSetFromNodeOutputExpr(GetPlatformCompileNode(builder, targetType, platform), 1); | |
| } | |
| /// <summary> | |
| /// Returns a BgFileSet representing the platform symbols. | |
| /// </summary> | |
| public static BgFileSet GetPlatformSymbols(SdBgGraphBuilder builder, TargetType targetType, UnrealTargetPlatform platform | |
| ) | |
| { | |
| return new BgFileSetFromNodeOutputExpr(GetPlatformCompileNode(builder, targetType, platform), 2); | |
| } | |
| /// <summary> | |
| /// Compile a target platform. | |
| /// </summary> | |
| private static async Task<(BgFileSet, BgFileSet)> PlatformCompileAsync( | |
| SingleTargetProperties target, | |
| UnrealTargetPlatform platform, | |
| List<UnrealTargetConfiguration> configurations, | |
| string buildVersion, | |
| string windowsCompilerVersion, | |
| bool warningsAsError, | |
| bool staticAnalysis, | |
| bool compileNonUnity, | |
| UploadSymbolsParameters symbolsParameters | |
| ) | |
| { | |
| string arguments = $"-{windowsCompilerVersion}"; | |
| arguments += warningsAsError ? " -WarningsAsErrors" : ""; | |
| arguments += compileNonUnity ? " -NoPCH -NoSharedPCH -DisableUnity -NoLink" : ""; | |
| arguments += staticAnalysis ? " -StaticAnalyzer=Default" : ""; | |
| // @SPLASH_DAMAGE_CHANGE [TEMP] #SDBuilds - BEGIN: Fix static analysis and UBA | |
| // Disable UBA and VFS during static analysis as it currently fails on 5.6.1, we tried disabling just VFS | |
| // and UBA, but this combination appeared to be the only way to consistently fix the issue. | |
| arguments += staticAnalysis ? " -NoUBA -NoUBALocal -NoVFS" : ""; | |
| // @SPLASH_DAMAGE_CHANGE [TEMP] #SDBuilds - END | |
| arguments += $" -BuildVersion=\"{buildVersion}\""; | |
| FileSet allFiles = FileSet.Empty; | |
| foreach (UnrealTargetConfiguration configuration in configurations) | |
| { | |
| allFiles += await StandardTasks.ExecuteAsync(new CompileTask(new CompileTaskParameters | |
| { | |
| Target = target.TargetName, | |
| Platform = platform, | |
| Configuration = configuration, | |
| Arguments = arguments | |
| })); | |
| } | |
| await SymbolUtils.PushSymbolsAsync(symbolsParameters, allFiles, platform); | |
| FileSet binaries = allFiles.Except(SdBgGraphBuilder.SymbolsFilter); | |
| FileSet symbols = allFiles.Filter(SdBgGraphBuilder.SymbolsFilter); | |
| return (binaries, symbols); | |
| } | |
| /// <summary> | |
| /// Returns the node name for compiling target platforms. | |
| /// </summary> | |
| private static string GetCompileNodeName(TargetType targetType, UnrealTargetPlatform platform) | |
| { | |
| return $"Compile {targetType} {platform}"; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Threading.Tasks; | |
| using EpicGames.BuildGraph.Expressions; | |
| using UnrealBuildTool; | |
| using AutomationTool.Tasks; | |
| using SDBuildGraph.Automation.Builders; | |
| using SDBuildGraph.Automation.Utilities; | |
| namespace SDBuildGraph.Automation.Nodes; | |
| public class PlatformCookParameters( | |
| PlatformAndTarget platform, | |
| bool skipEditorContent, | |
| bool versionContent, | |
| bool addUgsBadges = false | |
| ) | |
| { | |
| public TargetType TargetType { get; set; } = platform.Target.Rules.Type; | |
| public UnrealTargetPlatform Platform { get; set; } = platform.Platform; | |
| public bool SkipEditorContent { get; set; } = skipEditorContent; | |
| public bool VersionContent { get; set; } = versionContent; | |
| public bool AddUgsBadges { get; set; } = addUgsBadges; | |
| } | |
| public static class PlatformCookNode | |
| { | |
| /// <summary> | |
| /// Handles the specifics for creating individual cook nodes. | |
| /// </summary> | |
| public static void AddCookPlatform(SdBgGraphBuilder builder, PlatformCookParameters parameters) | |
| { | |
| BgAgent agent = builder.GetOrCreateAgent( | |
| agentName: GetCookAndPackageAgentName(parameters.TargetType, parameters.Platform), | |
| agentType: BgAgentUtils.GetCookAgentType() | |
| ); | |
| string nodeName = GetCookNodeName(parameters.TargetType, parameters.Platform); | |
| BgNode<BgFileSet> cookNode = agent | |
| .AddNode( | |
| name: nodeName, | |
| func: context => PlatformCookAsync( | |
| builder, | |
| parameters.TargetType, | |
| parameters.Platform, | |
| parameters.VersionContent, | |
| parameters.SkipEditorContent | |
| ) | |
| ) | |
| .Requires(ToolsAndEditorNode.GetEngineToolsBinaries(builder)) | |
| .Requires(ToolsAndEditorNode.GetEditorBinaries(builder)) | |
| .RunEarly() | |
| .AddLabel(PlatformUtils.GetOrCreatePlatformLabel(builder, parameters.TargetType, parameters.Platform, parameters.AddUgsBadges)); | |
| builder.AddNode(nodeName, cookNode); | |
| } | |
| /// <summary> | |
| /// Returns the agent name for cooking and packaging target platforms. | |
| /// </summary> | |
| public static string GetCookAndPackageAgentName(TargetType targetType, UnrealTargetPlatform platform) | |
| { | |
| return $"Cook and package {targetType} {platform}"; | |
| } | |
| /// <summary> | |
| /// Gets the cook platform node for a specified platform. | |
| /// </summary> | |
| public static BgNode GetPlatformCookNode(SdBgGraphBuilder builder, TargetType targetType, UnrealTargetPlatform platform | |
| ) | |
| { | |
| return builder.GetNode(GetCookNodeName(targetType, platform)); | |
| } | |
| /// <summary> | |
| /// Returns a BgFileSet representing the platform binaries. | |
| /// </summary> | |
| public static BgFileSet GetPlatformCookFiles(SdBgGraphBuilder builder, TargetType targetType, UnrealTargetPlatform platform | |
| ) | |
| { | |
| return new BgFileSetFromNodeOutputExpr(GetPlatformCookNode(builder, targetType, platform), 1); | |
| } | |
| /// <summary> | |
| /// Cooks a target platform. | |
| /// </summary> | |
| private static async Task<BgFileSet> PlatformCookAsync( | |
| SdBgGraphBuilder builder, | |
| TargetType targetType, | |
| UnrealTargetPlatform platform, | |
| bool versionContent, | |
| bool skipEditorContent | |
| ) | |
| { | |
| return await StandardTasks.ExecuteAsync(new CookTask(new CookTaskParameters | |
| { | |
| Project = builder.GetProjectFile().FullName, | |
| Arguments = skipEditorContent ? " -SkipEditorContent" : "", | |
| Platform = platform.GetCookName(targetType), | |
| Versioned = versionContent | |
| })); | |
| } | |
| /// <summary> | |
| /// Returns the node name used for cooking target platforms. | |
| /// </summary> | |
| private static string GetCookNodeName(TargetType targetType, UnrealTargetPlatform platform) | |
| { | |
| return $"Cook {targetType} {platform}"; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Copyright Splash Damage - All Rights Reserved. | |
| using System.Threading.Tasks; | |
| using AutomationTool; | |
| using AutomationTool.Tasks; | |
| using EpicGames.Core; | |
| using SDBuildGraph.Automation.Utilities; | |
| namespace SDBuildGraph.Automation.Nodes; | |
| public class PublishToolParameters | |
| { | |
| public string ToolId { get; set; } | |
| public DirectoryReference Directory { get; set; } | |
| } | |
| public static class PublishToolNode | |
| { | |
| public static async Task PublishToolAsync(PublishToolParameters parameters) | |
| { | |
| if (!CommandUtils.IsBuildMachine) | |
| { | |
| return; | |
| } | |
| await StandardTasks.ExecuteAsync(new DeployToolTask(new DeployToolTaskParameters | |
| { | |
| Id = parameters.ToolId, | |
| Version = $"{DotnetBuilds.GetEngineVersion()}-{PerforceUtils.Change}", | |
| Directory = parameters.Directory.FullName, | |
| })); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment