Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs
branden turner 62183aea6d Changing logic of BuildPlugin slightly such that it correctly outputs all of the Target Platforms that will be built, accounting for SupportedTargetPlatforms. Still works the same, but now with correct output.
#jira UE-115672
[FYI] ben.marsh
#rb none

#ROBOMERGE-SOURCE: CL 16318920 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v804-16311228)

[CL 16318925 by branden turner in ue5-release-engine-test branch]
2021-05-13 17:03:00 -04:00

350 lines
16 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Linq;
using System.Reflection;
using AutomationTool;
using UnrealBuildTool;
using EpicGames.Core;
using System.Text;
[Help("Builds a plugin, and packages it for distribution")]
[Help("Plugin", "Specify the path to the descriptor file for the plugin that should be packaged")]
[Help("NoHostPlatform", "Prevent compiling for the editor platform on the host")]
[Help("TargetPlatforms", "Specify a list of target platforms to build, separated by '+' characters (eg. -TargetPlatforms=Win32+Win64). Default is all the Rocket target platforms.")]
[Help("Package", "The path which the build artifacts should be packaged to, ready for distribution.")]
[Help("StrictIncludes", "Disables precompiled headers and unity build in order to check all source files have self-contained headers.")]
[Help("Unversioned", "Do not embed the current engine version into the descriptor")]
class BuildPlugin : BuildCommand
{
const string AndroidArchitectures = "armv7+arm64";
const string HoloLensArchitecture = "arm64+x64";
public override void ExecuteBuild()
{
// Get the plugin filename
string PluginParam = ParseParamValue("Plugin");
if(PluginParam == null)
{
throw new AutomationException("Missing -Plugin=... argument");
}
// Check it exists
FileReference PluginFile = new FileReference(PluginParam);
if (!FileReference.Exists(PluginFile))
{
throw new AutomationException("Plugin '{0}' not found", PluginFile.FullName);
}
// Get the output directory
string PackageParam = ParseParamValue("Package");
if (PackageParam == null)
{
throw new AutomationException("Missing -Package=... argument");
}
// Option for verifying that all include directive s
bool bStrictIncludes = ParseParam("StrictIncludes");
// Make sure the packaging directory is valid
DirectoryReference PackageDir = new DirectoryReference(PackageParam);
if (PluginFile.IsUnderDirectory(PackageDir))
{
throw new AutomationException("Packaged plugin output directory must be different to source");
}
if (PackageDir.IsUnderDirectory(DirectoryReference.Combine(CommandUtils.RootDirectory, "Engine")))
{
throw new AutomationException("Output directory for packaged plugin must be outside engine directory");
}
// Clear the output directory of existing stuff
if (DirectoryReference.Exists(PackageDir))
{
CommandUtils.DeleteDirectoryContents(PackageDir.FullName);
}
else
{
DirectoryReference.CreateDirectory(PackageDir);
}
// Create a placeholder FilterPlugin.ini with instructions on how to use it
FileReference SourceFilterFile = FileReference.Combine(PluginFile.Directory, "Config", "FilterPlugin.ini");
if (!FileReference.Exists(SourceFilterFile))
{
List<string> Lines = new List<string>();
Lines.Add("[FilterPlugin]");
Lines.Add("; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and");
Lines.Add("; may include \"...\", \"*\", and \"?\" wildcards to match directories, files, and individual characters respectively.");
Lines.Add(";");
Lines.Add("; Examples:");
Lines.Add("; /README.txt");
Lines.Add("; /Extras/...");
Lines.Add("; /Binaries/ThirdParty/*.dll");
DirectoryReference.CreateDirectory(SourceFilterFile.Directory);
CommandUtils.WriteAllLines_NoExceptions(SourceFilterFile.FullName, Lines.ToArray());
}
// Create a host project for the plugin. For script generator plugins, we need to have UHT be able to load it, which can only happen if it's enabled in a project.
FileReference HostProjectFile = FileReference.Combine(PackageDir, "HostProject", "HostProject.uproject");
FileReference HostProjectPluginFile = CreateHostProject(HostProjectFile, PluginFile);
// Read the plugin
CommandUtils.LogInformation("Reading plugin from {0}...", HostProjectPluginFile);
PluginDescriptor Plugin = PluginDescriptor.FromFile(HostProjectPluginFile);
// Get the arguments for the compile
StringBuilder AdditionalArgs = new StringBuilder();
if (bStrictIncludes)
{
CommandUtils.LogInformation("Building with precompiled headers and unity disabled");
AdditionalArgs.Append(" -NoPCH -NoSharedPCH -DisableUnity");
}
// Compile the plugin for all the target platforms
List<UnrealTargetPlatform> HostPlatforms = ParseParam("NoHostPlatform")? new List<UnrealTargetPlatform>() : new List<UnrealTargetPlatform> { BuildHostPlatform.Current.Platform };
List<UnrealTargetPlatform> TargetPlatforms = GetTargetPlatforms(this, BuildHostPlatform.Current.Platform);
FileReference[] BuildProducts = CompilePlugin(HostProjectFile, HostProjectPluginFile, Plugin, HostPlatforms, TargetPlatforms, AdditionalArgs.ToString());
// Package up the final plugin data
PackagePlugin(HostProjectPluginFile, BuildProducts, PackageDir, ParseParam("unversioned"));
// Remove the host project
if(!ParseParam("NoDeleteHostProject"))
{
CommandUtils.DeleteDirectory(HostProjectFile.Directory.FullName);
}
}
FileReference CreateHostProject(FileReference HostProjectFile, FileReference PluginFile)
{
DirectoryReference HostProjectDir = HostProjectFile.Directory;
DirectoryReference.CreateDirectory(HostProjectDir);
// Create the new project descriptor
File.WriteAllText(HostProjectFile.FullName, "{ \"FileVersion\": 3, \"Plugins\": [ { \"Name\": \"" + PluginFile.GetFileNameWithoutExtension() + "\", \"Enabled\": true } ] }");
// Get the plugin directory in the host project, and copy all the files in
DirectoryReference HostProjectPluginDir = DirectoryReference.Combine(HostProjectDir, "Plugins", PluginFile.GetFileNameWithoutExtension());
CommandUtils.ThreadedCopyFiles(PluginFile.Directory.FullName, HostProjectPluginDir.FullName);
CommandUtils.DeleteDirectory(true, DirectoryReference.Combine(HostProjectPluginDir, "Intermediate").FullName);
// Return the path to the plugin file in the host project
return FileReference.Combine(HostProjectPluginDir, PluginFile.GetFileName());
}
FileReference[] CompilePlugin(FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, List<UnrealTargetPlatform> HostPlatforms, List<UnrealTargetPlatform> TargetPlatforms, string AdditionalArgs)
{
List<FileReference> ManifestFileNames = new List<FileReference>();
// Build the host platforms
if(HostPlatforms.Count > 0)
{
CommandUtils.LogInformation("Building plugin for host platforms: {0}", String.Join(", ", HostPlatforms));
foreach (UnrealTargetPlatform HostPlatform in HostPlatforms)
{
if (Plugin.SupportedPrograms != null && Plugin.SupportedPrograms.Contains("UnrealHeaderTool"))
{
CompilePluginWithUBT(HostProjectFile, HostProjectPluginFile, Plugin, "UnrealHeaderTool", TargetType.Program, HostPlatform, UnrealTargetConfiguration.Development, ManifestFileNames, String.Format("{0} -plugin={1}", AdditionalArgs, CommandUtils.MakePathSafeToUseWithCommandLine(HostProjectPluginFile.FullName)));
}
CompilePluginWithUBT(HostProjectFile, HostProjectPluginFile, Plugin, "UnrealEditor", TargetType.Editor, HostPlatform, UnrealTargetConfiguration.Development, ManifestFileNames, AdditionalArgs);
}
}
// Add the supported game targets
if(TargetPlatforms.Count > 0)
{
List<UnrealTargetPlatform> SupportedTargetPlatforms = TargetPlatforms.FindAll(Plugin.SupportsTargetPlatform);
CommandUtils.LogInformation("Building plugin for target platforms: {0}", String.Join(", ", SupportedTargetPlatforms));
foreach (UnrealTargetPlatform TargetPlatform in SupportedTargetPlatforms)
{
string AdditionalTargetArgs = AdditionalArgs;
CompilePluginWithUBT(HostProjectFile, HostProjectPluginFile, Plugin, "UnrealGame", TargetType.Game, TargetPlatform, UnrealTargetConfiguration.Development, ManifestFileNames, AdditionalTargetArgs);
CompilePluginWithUBT(HostProjectFile, HostProjectPluginFile, Plugin, "UnrealGame", TargetType.Game, TargetPlatform, UnrealTargetConfiguration.Shipping, ManifestFileNames, AdditionalTargetArgs);
}
}
// Package the plugin to the output folder
HashSet<FileReference> BuildProducts = new HashSet<FileReference>();
foreach(FileReference ManifestFileName in ManifestFileNames)
{
BuildManifest Manifest = CommandUtils.ReadManifest(ManifestFileName);
BuildProducts.UnionWith(Manifest.BuildProducts.Select(x => new FileReference(x)));
}
return BuildProducts.ToArray();
}
void CompilePluginWithUBT(FileReference HostProjectFile, FileReference HostProjectPluginFile, PluginDescriptor Plugin, string TargetName, TargetType TargetType, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, List<FileReference> ManifestFileNames, string InAdditionalArgs)
{
// Find a list of modules that need to be built for this plugin
bool bCompilePlatform = false;
if (Plugin.Modules != null)
{
bool bBuildDeveloperTools = (TargetType == TargetType.Editor || TargetType == TargetType.Program || (Configuration != UnrealTargetConfiguration.Test && Configuration != UnrealTargetConfiguration.Shipping));
bool bBuildRequiresCookedData = (TargetType != TargetType.Editor && TargetType != TargetType.Program);
foreach (ModuleDescriptor Module in Plugin.Modules)
{
if (Module.IsCompiledInConfiguration(Platform, Configuration, TargetName, TargetType, bBuildDeveloperTools, bBuildRequiresCookedData))
{
bCompilePlatform = true;
}
}
}
// Add these modules to the build agenda
if(bCompilePlatform)
{
if (Platform == UnrealTargetPlatform.HoloLens)
{
// Make sure to save the manifests for each architecture with unique names so they don't get overwritten.
// This fixes packaging issues when building from binary engine releases, where the build produces a manifest for the plugin for ARM64, which
// then gets overwritten by the manifest for x64. Then during packaging, the plugin is referencing a manifest for the wrong architecture.
foreach (string Arch in HoloLensArchitecture.Split('+'))
{
FileReference ManifestFileName = FileReference.Combine(HostProjectFile.Directory, "Saved", String.Format("Manifest-{0}-{1}-{2}-{3}.xml", TargetName, Platform, Configuration, Arch));
ManifestFileNames.Add(ManifestFileName);
string Arguments = String.Format("-plugin={0} -iwyu -noubtmakefiles -manifest={1} -nohotreload", CommandUtils.MakePathSafeToUseWithCommandLine(HostProjectPluginFile.FullName), CommandUtils.MakePathSafeToUseWithCommandLine(ManifestFileName.FullName));
Arguments += String.Format(" -Architecture={0}", Arch);
if (!String.IsNullOrEmpty(InAdditionalArgs))
{
Arguments += InAdditionalArgs;
}
CommandUtils.RunUBT(CmdEnv, UE4Build.GetUBTExecutable(), HostProjectFile, TargetName, Platform, Configuration, Arguments);
}
}
else
{
FileReference ManifestFileName = FileReference.Combine(HostProjectFile.Directory, "Saved", String.Format("Manifest-{0}-{1}-{2}.xml", TargetName, Platform, Configuration));
ManifestFileNames.Add(ManifestFileName);
string Arguments = String.Format("-plugin={0} -iwyu -noubtmakefiles -manifest={1} -nohotreload", CommandUtils.MakePathSafeToUseWithCommandLine(HostProjectPluginFile.FullName), CommandUtils.MakePathSafeToUseWithCommandLine(ManifestFileName.FullName));
if (Platform == UnrealTargetPlatform.Android)
{
Arguments += String.Format(" -architectures={0}", AndroidArchitectures);
}
if (!String.IsNullOrEmpty(InAdditionalArgs))
{
Arguments += InAdditionalArgs;
}
CommandUtils.RunUBT(CmdEnv, UE4Build.GetUBTExecutable(), HostProjectFile, TargetName, Platform, Configuration, Arguments);
}
}
}
static void PackagePlugin(FileReference SourcePluginFile, IEnumerable<FileReference> BuildProducts, DirectoryReference TargetDir, bool bUnversioned)
{
DirectoryReference SourcePluginDir = SourcePluginFile.Directory;
// Copy all the files to the output directory
FileReference[] SourceFiles = FilterPluginFiles(SourcePluginFile, BuildProducts).ToArray();
foreach(FileReference SourceFile in SourceFiles)
{
FileReference TargetFile = FileReference.Combine(TargetDir, SourceFile.MakeRelativeTo(SourcePluginDir));
CommandUtils.CopyFile(SourceFile.FullName, TargetFile.FullName);
CommandUtils.SetFileAttributes(TargetFile.FullName, ReadOnly: false);
}
// Get the output plugin filename
FileReference TargetPluginFile = FileReference.Combine(TargetDir, SourcePluginFile.GetFileName());
PluginDescriptor NewDescriptor = PluginDescriptor.FromFile(TargetPluginFile);
NewDescriptor.bEnabledByDefault = null;
NewDescriptor.bInstalled = true;
if(!bUnversioned)
{
BuildVersion Version;
if(BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version))
{
NewDescriptor.EngineVersion = String.Format("{0}.{1}.0", Version.MajorVersion, Version.MinorVersion);
}
}
NewDescriptor.Save(TargetPluginFile.FullName);
}
static IEnumerable<FileReference> FilterPluginFiles(FileReference PluginFile, IEnumerable<FileReference> BuildProducts)
{
// Set up the default filter
FileFilter Filter = new FileFilter();
Filter.AddRuleForFile(PluginFile, PluginFile.Directory, FileFilterType.Include);
Filter.AddRuleForFiles(BuildProducts, PluginFile.Directory, FileFilterType.Include);
Filter.Include("/Binaries/ThirdParty/...");
Filter.Include("/Resources/...");
Filter.Include("/Content/...");
Filter.Include("/Intermediate/Build/.../Inc/...");
Filter.Include("/Shaders/...");
Filter.Include("/Source/...");
// Add custom rules for each platform
FileReference FilterFile = FileReference.Combine(PluginFile.Directory, "Config", "FilterPlugin.ini");
if(FileReference.Exists(FilterFile))
{
CommandUtils.LogInformation("Reading filter rules from {0}", FilterFile);
Filter.ReadRulesFromFile(FilterFile, "FilterPlugin");
}
// Apply the standard exclusion rules
foreach (string RestrictedFolderName in RestrictedFolder.GetNames())
{
Filter.AddRule(String.Format(".../{0}/...", RestrictedFolderName), FileFilterType.Exclude);
}
// Apply the filter to the plugin directory
return Filter.ApplyToDirectory(PluginFile.Directory, true);
}
static List<UnrealTargetPlatform> GetTargetPlatforms(BuildCommand Command, UnrealTargetPlatform HostPlatform)
{
List<UnrealTargetPlatform> TargetPlatforms = new List<UnrealTargetPlatform>();
if(!Command.ParseParam("NoTargetPlatforms"))
{
// Only interested in building for Platforms that support code projects
TargetPlatforms = PlatformExports.GetRegisteredPlatforms().Where(x => InstalledPlatformInfo.IsValidPlatform(x, EProjectType.Code)).ToList();
// only build Mac on Mac
if (HostPlatform != UnrealTargetPlatform.Mac && TargetPlatforms.Contains(UnrealTargetPlatform.Mac))
{
TargetPlatforms.Remove(UnrealTargetPlatform.Mac);
}
// only build Windows on Windows
if (HostPlatform != UnrealTargetPlatform.Win64 && TargetPlatforms.Contains(UnrealTargetPlatform.Win64))
{
TargetPlatforms.Remove(UnrealTargetPlatform.Win64);
}
// build Linux on Windows and Linux
if (HostPlatform != UnrealTargetPlatform.Win64 && HostPlatform != UnrealTargetPlatform.Linux)
{
if (TargetPlatforms.Contains(UnrealTargetPlatform.Linux))
TargetPlatforms.Remove(UnrealTargetPlatform.Linux);
if (TargetPlatforms.Contains(UnrealTargetPlatform.LinuxAArch64))
TargetPlatforms.Remove(UnrealTargetPlatform.LinuxAArch64);
}
// Remove any platforms that aren't enabled on the command line
string TargetPlatformFilter = Command.ParseParamValue("TargetPlatforms", null);
if(TargetPlatformFilter != null)
{
List<UnrealTargetPlatform> NewTargetPlatforms = new List<UnrealTargetPlatform>();
foreach (string TargetPlatformName in TargetPlatformFilter.Split(new char[]{ '+' }, StringSplitOptions.RemoveEmptyEntries))
{
UnrealTargetPlatform TargetPlatform;
if (!UnrealTargetPlatform.TryParse(TargetPlatformName, out TargetPlatform))
{
throw new AutomationException("Unknown target platform '{0}' specified on command line", TargetPlatformName);
}
if(TargetPlatforms.Contains(TargetPlatform))
{
NewTargetPlatforms.Add(TargetPlatform);
}
}
TargetPlatforms = NewTargetPlatforms;
}
}
return TargetPlatforms;
}
}