-
Notifications
You must be signed in to change notification settings - Fork 191
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use custom BuildCookRun command for Alpakit
- Loading branch information
1 parent
80d1ff4
commit 9df5785
Showing
12 changed files
with
859 additions
and
84 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
53 changes: 53 additions & 0 deletions
53
Mods/Alpakit/Source/Alpakit.Automation/Alpakit.Automation.csproj
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<Import Project="$(EngineDir)\Source\Programs\Shared\UnrealEngine.csproj.props" /> | ||
|
||
<PropertyGroup> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
<ProjectGuid>{A777BF44-017F-4E69-916A-A62D06D63556}</ProjectGuid> | ||
<OutputType>Library</OutputType> | ||
<AppDesignerFolder>Properties</AppDesignerFolder> | ||
<RootNamespace>Alpakit.Automation</RootNamespace> | ||
<AssemblyName>Alpakit.Automation</AssemblyName> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<FileAlignment>512</FileAlignment> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
<PlatformTarget>AnyCPU</PlatformTarget> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
<OutputPath>..\..\Binaries\DotNET\</OutputPath> | ||
<DefineConstants>DEBUG;TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
<PlatformTarget>AnyCPU</PlatformTarget> | ||
<DebugType>pdbonly</DebugType> | ||
<Optimize>true</Optimize> | ||
<OutputPath>..\..\Binaries\DotNET\</OutputPath> | ||
<DefineConstants>TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Development|AnyCPU' "> | ||
<OutputPath>..\..\Binaries\DotNET\</OutputPath> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>pdbonly</DebugType> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="$(EngineDir)\Source\Programs\AutomationTool\AutomationUtils\AutomationUtils.Automation.csproj" /> | ||
<ProjectReference Include="$(EngineDir)\Source\Programs\AutomationTool\Scripts\AutomationScripts.Automation.csproj" /> | ||
<ProjectReference Include="$(EngineDir)\Source\Programs\Shared\EpicGames.Build\EpicGames.Build.csproj" /> | ||
<ProjectReference Include="$(EngineDir)\Source\Programs\Shared\EpicGames.Core\EpicGames.Core.csproj" /> | ||
<ProjectReference Include="$(EngineDir)\Source\Programs\UnrealBuildTool\UnrealBuildTool.csproj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
using System.Diagnostics; | ||
using AutomationTool; | ||
|
||
namespace Alpakit.Automation; | ||
|
||
public class LaunchGame | ||
{ | ||
public enum LaunchType | ||
{ | ||
Steam, | ||
SteamDS, | ||
EpicEA, | ||
EpicExp, | ||
EpicDS, | ||
EpicDSExp, | ||
Custom | ||
} | ||
|
||
private static string GetGameLaunchUrl(LaunchType LaunchType) | ||
{ | ||
switch (LaunchType) | ||
{ | ||
case LaunchType.Steam: | ||
return "steam://rungameid/526870"; | ||
case LaunchType.SteamDS: | ||
return "steam://rungameid/1690800"; | ||
case LaunchType.EpicEA: | ||
return "com.epicgames.launcher://apps/CrabEA?action=launch&silent=true"; | ||
case LaunchType.EpicExp: | ||
return "com.epicgames.launcher://apps/CrabTest?action=launch&silent=true"; | ||
case LaunchType.EpicDS: | ||
return "com.epicgames.launcher://apps/CrabDedicatedServer?action=launch&silent=true"; | ||
case LaunchType.EpicDSExp: | ||
// No more nice names | ||
return "com.epicgames.launcher://apps/c509233193024c5f8124467d3aa36199?action=launch&silent=true"; | ||
default: | ||
throw new AutomationException("Invalid Launch Type {0}", LaunchType); | ||
} | ||
} | ||
|
||
public static void Launch(LaunchType Type, string? CustomLaunch) | ||
{ | ||
if (Type == LaunchType.Custom) | ||
{ | ||
if (CustomLaunch == null) | ||
throw new AutomationException("Custom Launch Type requested, but no program to launch was specified"); | ||
Process.Start(CustomLaunch); | ||
return; | ||
} | ||
|
||
Process.Start(new ProcessStartInfo(GetGameLaunchUrl(Type)) { UseShellExecute = true }); | ||
} | ||
} |
170 changes: 170 additions & 0 deletions
170
Mods/Alpakit/Source/Alpakit.Automation/PackagePlugin.cs
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO.Compression; | ||
using System.Linq; | ||
using AutomationTool; | ||
using EpicGames.Core; | ||
using UnrealBuildTool; | ||
using AutomationScripts; | ||
|
||
namespace Alpakit.Automation; | ||
|
||
public class PackagePlugin : BuildCookRun | ||
{ | ||
public override void ExecuteBuild() | ||
{ | ||
var projectParams = SetupParams(); | ||
projectParams.PreModifyDeploymentContextCallback = (ProjectParams, SC) => | ||
{ | ||
RemapCookedPluginsContentPaths(ProjectParams, SC); | ||
}; | ||
projectParams.ModifyDeploymentContextCallback = (ProjectParams, SC) => | ||
{ | ||
ModifyModules(ProjectParams, SC); | ||
}; | ||
|
||
DoBuildCookRun(projectParams); | ||
|
||
var deploymentContexts = new List<DeploymentContext>(); | ||
if (!projectParams.NoClient) | ||
deploymentContexts.AddRange(Project.CreateDeploymentContext(projectParams, false, false)); | ||
if (projectParams.DedicatedServer) | ||
deploymentContexts.AddRange(Project.CreateDeploymentContext(projectParams, true, false)); | ||
|
||
foreach (var SC in deploymentContexts) | ||
{ | ||
ArchiveStagedPlugin(projectParams, SC); | ||
} | ||
|
||
foreach (var SC in deploymentContexts) | ||
{ | ||
var copyToGameDirectory = ParseOptionalDirectoryReferenceParam($"CopyToGameDirectory_{SC.FinalCookPlatform}"); | ||
var launchGameType = ParseOptionalEnumParam<LaunchGame.LaunchType>($"LaunchGame_{SC.FinalCookPlatform}"); | ||
var customLaunch = ParseOptionalStringParam($"CustomLaunch_{SC.FinalCookPlatform}"); | ||
|
||
if (copyToGameDirectory != null) | ||
DeployStagedPlugin(projectParams, SC, copyToGameDirectory); | ||
|
||
if (launchGameType != null) | ||
LaunchGame.Launch(launchGameType.Value, customLaunch); | ||
} | ||
} | ||
|
||
protected ProjectParams SetupParams() | ||
{ | ||
LogInformation("Setting up ProjectParams for {0}", ProjectPath); | ||
|
||
var Params = new ProjectParams | ||
( | ||
Command: this, | ||
RawProjectPath: ProjectPath, | ||
|
||
// Alpakit shared configuration | ||
Cook: true, | ||
AdditionalCookerOptions: "-AllowUncookedAssetReferences", | ||
DLCIncludeEngineContent: false, | ||
Compressed: true, | ||
Pak: true, | ||
Stage: true, | ||
|
||
// TODO @SML: I would like to pass an empty based on release version, but the cooker checks against it | ||
BasedOnReleaseVersion: "SMLNonExistentBasedOnReleaseVersion" | ||
); | ||
|
||
return Params; | ||
} | ||
|
||
private static void DeployStagedPlugin(ProjectParams ProjectParams, DeploymentContext SC, DirectoryReference GameDir) | ||
{ | ||
// We only want to archive the staged files of the plugin, not the entire stage directory | ||
var stagedPluginDirectory = Project.ApplyDirectoryRemap(SC, SC.GetStagedFileLocation(ProjectParams.DLCFile)); | ||
var fullStagedPluginDirectory = DirectoryReference.Combine(SC.StageDirectory, stagedPluginDirectory.Directory.Name); | ||
|
||
var projectName = ProjectParams.RawProjectPath.GetFileNameWithoutAnyExtensions(); | ||
|
||
// Mods go into <GameDir>/<ProjectName>/Mods/<PluginName> | ||
var gameModsDir = DirectoryReference.Combine(GameDir, projectName, "Mods"); | ||
var pluginName = ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions(); | ||
var modDir = DirectoryReference.Combine(gameModsDir, pluginName); | ||
|
||
if (DirectoryReference.Exists(modDir)) | ||
DirectoryReference.Delete(modDir, true); | ||
|
||
CopyDirectory_NoExceptions(fullStagedPluginDirectory, modDir); | ||
} | ||
|
||
private static void ArchiveStagedPlugin(ProjectParams ProjectParams, DeploymentContext SC) | ||
{ | ||
// Plugins will be archived under <Project>/Saved/ArchivedPlugins/<DLCName> | ||
var baseArchiveDirectory = CombinePaths(SC.ProjectRoot.FullName, "Saved", "ArchivedPlugins"); | ||
var archiveDirectory = CombinePaths(baseArchiveDirectory, ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions()); | ||
|
||
CreateDirectory(archiveDirectory); | ||
|
||
var dlcName = ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions(); | ||
|
||
var zipFileName = $"{dlcName}-{SC.FinalCookPlatform}.zip"; | ||
|
||
var zipFilePath = CombinePaths(archiveDirectory, zipFileName); | ||
|
||
if (FileExists(zipFilePath)) | ||
DeleteFile(zipFilePath); | ||
|
||
// We only want to archive the staged files of the plugin, not the entire stage directory | ||
var stagedPluginDirectory = Project.ApplyDirectoryRemap(SC, SC.GetStagedFileLocation(ProjectParams.DLCFile)); | ||
var fullStagedPluginDirectory = DirectoryReference.Combine(SC.StageDirectory, stagedPluginDirectory.Directory.Name); | ||
|
||
ZipFile.CreateFromDirectory(fullStagedPluginDirectory.FullName, zipFilePath); | ||
} | ||
|
||
private static string GetPluginPathRelativeToStageRoot(ProjectParams ProjectParams) | ||
{ | ||
// All DLC paths are remapped to projectName/Mods/DLCName during RemapCookedPluginsContentPaths, regardless of nesting | ||
// so the relative stage path is projectName/Mods/DLCName | ||
var projectName = ProjectParams.RawProjectPath.GetFileNameWithoutAnyExtensions(); | ||
var dlcName = ProjectParams.DLCFile.GetFileNameWithoutAnyExtensions(); | ||
return CombinePaths(projectName, "Mods", dlcName); | ||
} | ||
|
||
private static void RemapCookedPluginsContentPaths(ProjectParams ProjectParams, DeploymentContext SC) { | ||
//We need to make sure content paths will be relative to ../../../ProjectRoot/Mods/DLCFilename/Content, | ||
//because that's what will be used in runtime as a content path for /DLCFilename/ mount point. | ||
//But both project and engine plugins are actually cooked by different paths: | ||
//Project plugins expect to be mounted under ../../../ProjectRoot/Plugins/DLCFilename/Content, | ||
//and engine plugins expect to be mounted under ../../../Engine/Plugins/DLCFilename/Content | ||
//Since changing runtime content path is pretty complicated and impractical, | ||
//we remap cooked filenames to match runtime expectations. Since cooked assets only reference other assets | ||
//using mount point-relative paths, it doesn't need any changes inside of the cooked assets | ||
//deploymentContext.RemapDirectories.Add(Tuple.Create()); | ||
|
||
SC.RemapDirectories.Add(Tuple.Create( | ||
SC.GetStagedFileLocation(ProjectParams.DLCFile).Directory, | ||
new StagedDirectoryReference(GetPluginPathRelativeToStageRoot(ProjectParams)) | ||
)); | ||
} | ||
|
||
private void ModifyModules(ProjectParams ProjectParams, DeploymentContext SC) | ||
{ | ||
foreach (var target in SC.StageTargets) | ||
{ | ||
// Handle .modules files explicitly by nulling out their BuildId since DLC cooks for mods are supposed to have relaxed compatibility requirements | ||
var manifests = target.Receipt.BuildProducts.Where((Product => Product.Type == BuildProductType.RequiredResource && Product.Path.GetExtension() == ".modules" && Product.Path.IsUnderDirectory(ProjectParams.DLCFile.Directory))); | ||
foreach (var manifest in manifests) | ||
{ | ||
if (!ModuleManifest.TryRead(manifest.Path, out var moduleManifestFile)) | ||
throw new AutomationException( | ||
$"Unable to read .modules build file at {manifest.Path} for DLC staging"); | ||
|
||
// Null out BuildId for Mod DLC cooks because they will be loaded by different game builds potentially | ||
// The game specifically allows SML as BuildId to be loaded by any game build | ||
moduleManifestFile.BuildId = "SML"; | ||
|
||
var intermediateModuleFilePath = FileReference.Combine(ProjectParams.DLCFile.Directory, "Intermediate", | ||
"Staging", SC.FinalCookPlatform, manifest.Path.MakeRelativeTo(ProjectParams.DLCFile.Directory)); | ||
var outputFilePath = SC.GetStagedFileLocation(manifest.Path); | ||
moduleManifestFile.Write(intermediateModuleFilePath); | ||
SC.StageFile(StagedFileType.NonUFS, intermediateModuleFilePath, outputFilePath); | ||
} | ||
} | ||
} | ||
} |
This file contains 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
This file contains 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
Oops, something went wrong.