Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/Scripts/BuildPluginCommand.Automation.cs
jonathan adamczewski 4ece24e65a AutomationTool, BuildUtilities:
UnrealBuild -> Unreal for EngineDirectory, RootDirectory, IsEngineInstalled, UnrealBuildToolPath
Remove CommandUtils EngineDirectory, RootDirectory, IsEngineInstalled - use equvalents from UnrealBuildBase.Unreal

#jira none

[CL 16648181 by jonathan adamczewski in ue5-main branch]
2021-06-11 18:20:44 -04:00

351 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;
using UnrealBuildBase;
[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(Unreal.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;
}
}