Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save regner/44ea3ecd8e91537df53742b99dfd1e17 to your computer and use it in GitHub Desktop.

Select an option

Save regner/44ea3ecd8e91537df53742b99dfd1e17 to your computer and use it in GitHub Desktop.
BuildGraph C# examples
// 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";
}
}
// 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);
}
}
// 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);
}
}
// 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");
}
}
// 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);
}
}
// 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);
}
}
// 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);
}
}
// 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();
}
// 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}";
}
}
// 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}";
}
}
// 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