I think I got it now.
Here is my PackageSimpleUGCPlugin.cs that creates .pak files for both Linux and Windows.
// Copyright Epic Games, Inc. All Rights Reserved.
using AutomationTool;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnrealBuildTool;
using Tools.DotNETCommon;
using Microsoft.Win32;
using System.Diagnostics;
namespace SimpleUGC.Automation
{
public class PackageUGC : BuildCommand
{
static public ProjectParams GetParams(BuildCommand Cmd, string ProjectFileName, out FileReference PluginFile)
{
string VersionString = Cmd.ParseParamValue("Version", "NOVERSION");
// Get the plugin filename
string PluginPath = Cmd.ParseParamValue("PluginPath");
if (PluginPath == null)
{
throw new AutomationException("Missing -PluginPath=... argument");
}
// Check it exists
PluginFile = new FileReference(PluginPath);
if (!FileReference.Exists(PluginFile))
{
throw new AutomationException("Plugin '{0}' not found", PluginFile.FullName);
}
string ReleaseVersion = Cmd.ParseParamValue("BasedOnReleaseVersion", "UGCExampleGame_v1");
FileReference ProjectFile = new FileReference(ProjectFileName);
ProjectParams Params = new ProjectParams(
RawProjectPath: ProjectFile,
Command: Cmd,
ClientTargetPlatforms: new List<TargetPlatformDescriptor>() { new TargetPlatformDescriptor(UnrealTargetPlatform.Win64) },
Build: false,
Cook: true,
Stage: true,
Pak: true,
Manifests: true,
DLCIncludeEngineContent: true, // Need this to allow engine content that wasn't cooked in the base game to be included in the PAK file
BasedOnReleaseVersion: ReleaseVersion,
DLCName: PluginFile.GetFileNameWithoutAnyExtensions(),
RunAssetNativization: true
);
Params.ValidateAndLog();
return Params;
}
static public ProjectParams GetBuild(BuildCommand Cmd, string ProjectFileName, out FileReference PluginFile)
{
string VersionString = Cmd.ParseParamValue("Version", "NOVERSION");
// Get the plugin filename
string PluginPath = Cmd.ParseParamValue("PluginPath");
if (PluginPath == null)
{
throw new AutomationException("Missing -PluginPath=... argument");
}
// Check it exists
PluginFile = new FileReference(PluginPath);
if (!FileReference.Exists(PluginFile))
{
throw new AutomationException("Plugin '{0}' not found", PluginFile.FullName);
}
string ReleaseVersion = Cmd.ParseParamValue("BasedOnReleaseVersion", "UGCExampleGame_v1");
FileReference ProjectFile = new FileReference(ProjectFileName);
ProjectParams LinuxParams = new ProjectParams(
RawProjectPath: ProjectFile,
Command: Cmd,
ClientTargetPlatforms: new List<TargetPlatformDescriptor>() { new TargetPlatformDescriptor(UnrealTargetPlatform.Linux) },
Build: false,
Cook: true,
Stage: true,
Pak: true,
Manifests: true,
DLCIncludeEngineContent: true, // Need this to allow engine content that wasn't cooked in the base game to be included in the PAK file
BasedOnReleaseVersion: ReleaseVersion,
DLCName: PluginFile.GetFileNameWithoutAnyExtensions(),
RunAssetNativization: true
);
LinuxParams.ValidateAndLog();
return LinuxParams;
}
public override void ExecuteBuild()
{
int WorkingCL = -1;
FileReference PluginFile = null;
string ProjectFileName = ParseParamValue("Project");
if (ProjectFileName == null)
{
ProjectFileName = CombinePaths(CmdEnv.LocalRoot, "SimpleGame", "SimpleGame.uproject");
}
LogInformation(ProjectFileName);
ProjectParams Params = GetParams(this, ProjectFileName, out PluginFile);
ProjectParams LinuxParams = GetBuild(this, ProjectFileName, out PluginFile);
// Check whether folder already exists so we know if we can delete it later
string PlatformStageDir = Path.Combine(Params.StageDirectoryParam, "WindowsNoEditor");
bool bPreExistingStageDir = Directory.Exists(PlatformStageDir);
string LinuxPlatformStageDir = Path.Combine(LinuxParams.StageDirectoryParam, "LinuxNoEditor");
bool bPreExistingLinuxStageDir = Directory.Exists(LinuxPlatformStageDir);
PluginDescriptor Plugin = PluginDescriptor.FromFile(PluginFile);
FileReference ProjectFile = new FileReference(ProjectFileName);
// Add Plugin to folders excluded for nativization in config file
FileReference UserEditorIni = new FileReference(Path.Combine(Path.GetDirectoryName(ProjectFileName), "Config", "UserEditor.ini"));
bool bPreExistingUserEditorIni = FileReference.Exists(UserEditorIni);
if (!bPreExistingUserEditorIni)
{
// Expect this most of the time so we will create and clean up afterwards
DirectoryReference.CreateDirectory(UserEditorIni.Directory);
CommandUtils.WriteAllText(UserEditorIni.FullName, "");
}
const string ConfigSection = "BlueprintNativizationSettings";
const string ConfigKey = "ExcludedFolderPaths";
string ConfigValue = "/" + PluginFile.GetFileNameWithoutAnyExtensions() + "/";
ConfigFile UserEditorConfig = new ConfigFile(UserEditorIni);
ConfigFileSection BPNSection = UserEditorConfig.FindOrAddSection(ConfigSection);
bool bUpdateConfigFile = !BPNSection.Lines.Exists(x => String.Equals(x.Key, ConfigKey, StringComparison.OrdinalIgnoreCase) && String.Equals(x.Value, ConfigValue, StringComparison.OrdinalIgnoreCase));
if (bUpdateConfigFile)
{
BPNSection.Lines.Add(new ConfigLine(ConfigLineAction.Add, ConfigKey, ConfigValue));
UserEditorConfig.Write(UserEditorIni);
}
Project.Cook(Params);
if (!bPreExistingUserEditorIni)
{
FileReference.Delete(UserEditorIni);
}
Project.Cook(LinuxParams);
if (!bPreExistingUserEditorIni)
{
FileReference.Delete(UserEditorIni);
}
Project.CopyBuildToStagingDirectory(Params);
Project.Package(LinuxParams);
Project.Archive(Params);
Project.Deploy(Params);
Project.CopyBuildToStagingDirectory(LinuxParams);
Project.Package(LinuxParams, WorkingCL);
Project.Archive(LinuxParams);
Project.Deploy(LinuxParams);
// Get path to where the plugin was staged
string StagedPluginDir = Path.Combine(PluginFile.GetFileNameWithoutAnyExtensions(), PlatformStageDir, Path.GetFileNameWithoutExtension(ProjectFileName));
string StagedLinuxPluginDir = Path.Combine(PluginFile.GetFileNameWithoutAnyExtensions(), LinuxPlatformStageDir, Path.GetFileNameWithoutExtension(ProjectFileName));
File.Delete(PlatformStageDir + "/" + Path.GetFileNameWithoutExtension(ProjectFileName) + "/" + "Mods/" + PluginFile.GetFileNameWithoutAnyExtensions() + "/" + PluginFile.GetFileNameWithoutAnyExtensions() + ".uplugin");
string ZipFile = Path.Combine(Params.StageDirectoryParam, "Win" + PluginFile.GetFileNameWithoutAnyExtensions());
string LinuxZipFile = Path.Combine(Params.StageDirectoryParam, "Linux" + PluginFile.GetFileNameWithoutAnyExtensions());
string ZipReleaseFile = Path.Combine(Params.StageDirectoryParam, PluginFile.GetFileNameWithoutAnyExtensions());
CommandUtils.DeleteFile(ZipFile);
CommandUtils.DeleteFile(LinuxZipFile);
System.IO.Compression.ZipFile.CreateFromDirectory(StagedPluginDir, ZipFile + ".zip");
System.IO.Compression.ZipFile.CreateFromDirectory(StagedLinuxPluginDir, LinuxZipFile + ".zip");
//Exracting the .Zip files to the same path so we can create one file for both Win and Linux.
System.IO.Compression.ZipFile.ExtractToDirectory(Params.StageDirectoryParam + "/Linux" + PluginFile.GetFileNameWithoutAnyExtensions() + ".zip", Params.StageDirectoryParam + "/");
System.IO.Compression.ZipFile.ExtractToDirectory(Params.StageDirectoryParam + "/Win" + PluginFile.GetFileNameWithoutAnyExtensions() + ".zip", Params.StageDirectoryParam + "/");
//Compresses the all the needed files for Win and Linux to one .Zip file
System.IO.Compression.ZipFile.CreateFromDirectory(Params.StageDirectoryParam + "/Mods", ZipReleaseFile + ".zip");
CommandUtils.DeleteDirectory(Params.StageDirectoryParam + "/Mods");
if (!bPreExistingStageDir)
{
CommandUtils.DeleteDirectory(PlatformStageDir);
}
if (!bPreExistingLinuxStageDir)
{
CommandUtils.DeleteDirectory(LinuxPlatformStageDir);
}
}
}
}
This plugin is great!