2018-12-17 06:31:16 -05:00
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
2015-05-01 10:58:14 -04:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Threading ;
using System.Linq ;
using System.Reflection ;
using AutomationTool ;
using UnrealBuildTool ;
2017-08-31 12:08:38 -04:00
using Tools.DotNETCommon ;
2019-01-22 06:48:04 -05:00
using System.Text ;
2015-05-01 10:58:14 -04:00
[Help("Builds a plugin, and packages it for distribution")]
2015-05-06 11:06:35 -04:00
[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")]
2015-05-01 15:01:10 -04:00
[Help("TargetPlatforms", "Specify a list of target platforms to build, separated by '+' characters (eg. -TargetPlatforms=Win32+Win64). Default is all the Rocket target platforms.")]
2015-05-06 11:07:47 -04:00
[Help("Package", "The path which the build artifacts should be packaged to, ready for distribution.")]
2019-01-22 06:48:04 -05:00
[Help("StrictIncludes", "Disables precompiled headers and unity build in order to check all source files have self-contained headers.")]
2017-04-10 11:00:33 -04:00
[Help("Unversioned", "Do not embed the current engine version into the descriptor")]
2015-05-01 10:58:14 -04:00
class BuildPlugin : BuildCommand
{
public override void ExecuteBuild ( )
{
// Get the plugin filename
2016-03-11 09:55:03 -05:00
string PluginParam = ParseParamValue ( "Plugin" ) ;
if ( PluginParam = = null )
2015-05-01 10:58:14 -04:00
{
2016-05-11 11:05:13 -04:00
throw new AutomationException ( "Missing -Plugin=... argument" ) ;
2015-05-01 10:58:14 -04:00
}
2015-05-06 11:06:35 -04:00
2016-05-11 11:05:13 -04:00
// Check it exists
FileReference PluginFile = new FileReference ( PluginParam ) ;
2017-01-30 16:52:08 -05:00
if ( ! FileReference . Exists ( PluginFile ) )
2016-05-11 11:05:13 -04:00
{
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" ) ;
}
2019-01-22 06:48:04 -05:00
// Option for verifying that all include directive s
bool bStrictIncludes = ParseParam ( "StrictIncludes" ) ;
2016-05-11 11:05:13 -04:00
// 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
2017-01-30 16:52:08 -05:00
if ( DirectoryReference . Exists ( PackageDir ) )
2016-05-11 11:05:13 -04:00
{
CommandUtils . DeleteDirectoryContents ( PackageDir . FullName ) ;
}
else
{
2017-01-30 16:52:08 -05:00
DirectoryReference . CreateDirectory ( PackageDir ) ;
2016-05-11 11:05:13 -04:00
}
// Create a placeholder FilterPlugin.ini with instructions on how to use it
FileReference SourceFilterFile = FileReference . Combine ( PluginFile . Directory , "Config" , "FilterPlugin.ini" ) ;
2017-01-30 16:52:08 -05:00
if ( ! FileReference . Exists ( SourceFilterFile ) )
2016-05-11 11:05:13 -04:00
{
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" ) ;
2017-01-30 16:52:08 -05:00
DirectoryReference . CreateDirectory ( SourceFilterFile . Directory ) ;
2016-05-11 11:05:13 -04:00
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 ) ;
2015-05-01 10:58:14 -04:00
// Read the plugin
2018-08-14 18:32:34 -04:00
CommandUtils . LogInformation ( "Reading plugin from {0}..." , HostProjectPluginFile ) ;
2017-07-21 12:42:36 -04:00
PluginDescriptor Plugin = PluginDescriptor . FromFile ( HostProjectPluginFile ) ;
2015-05-01 10:58:14 -04:00
2019-01-22 06:48:04 -05:00
// 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" ) ;
}
2016-05-11 11:05:13 -04:00
// Compile the plugin for all the target platforms
List < UnrealTargetPlatform > HostPlatforms = ParseParam ( "NoHostPlatform" ) ? new List < UnrealTargetPlatform > ( ) : new List < UnrealTargetPlatform > { BuildHostPlatform . Current . Platform } ;
2016-11-23 15:34:07 -05:00
List < UnrealTargetPlatform > TargetPlatforms = GetTargetPlatforms ( this , BuildHostPlatform . Current . Platform ) ;
2019-01-22 06:48:04 -05:00
FileReference [ ] BuildProducts = CompilePlugin ( HostProjectFile , HostProjectPluginFile , Plugin , HostPlatforms , TargetPlatforms , AdditionalArgs . ToString ( ) ) ;
2016-05-11 11:05:13 -04:00
// Package up the final plugin data
2017-04-10 11:00:33 -04:00
PackagePlugin ( HostProjectPluginFile , BuildProducts , PackageDir , ParseParam ( "unversioned" ) ) ;
2016-05-11 11:05:13 -04:00
// Remove the host project
if ( ! ParseParam ( "NoDeleteHostProject" ) )
2015-05-01 10:58:14 -04:00
{
2016-05-11 11:05:13 -04:00
CommandUtils . DeleteDirectory ( HostProjectFile . Directory . FullName ) ;
2015-05-01 10:58:14 -04:00
}
2016-05-11 11:05:13 -04:00
}
2015-05-01 10:58:14 -04:00
2016-05-11 11:05:13 -04:00
FileReference CreateHostProject ( FileReference HostProjectFile , FileReference PluginFile )
{
DirectoryReference HostProjectDir = HostProjectFile . Directory ;
2017-01-30 16:52:08 -05:00
DirectoryReference . CreateDirectory ( HostProjectDir ) ;
2016-03-11 09:55:03 -05:00
2016-05-11 11:05:13 -04:00
// Create the new project descriptor
2016-03-11 09:55:03 -05:00
File . WriteAllText ( HostProjectFile . FullName , "{ \"FileVersion\": 3, \"Plugins\": [ { \"Name\": \"" + PluginFile . GetFileNameWithoutExtension ( ) + "\", \"Enabled\": true } ] }" ) ;
2016-05-11 11:05:13 -04:00
// 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 )
{
2018-10-12 16:52:09 -04:00
List < FileReference > ManifestFileNames = new List < FileReference > ( ) ;
2015-05-05 18:52:17 -04:00
2015-05-01 15:01:10 -04:00
// Build the host platforms
2016-05-11 11:05:13 -04:00
if ( HostPlatforms . Count > 0 )
2015-05-01 15:01:10 -04:00
{
2018-08-14 18:32:34 -04:00
CommandUtils . LogInformation ( "Building plugin for host platforms: {0}" , String . Join ( ", " , HostPlatforms ) ) ;
2016-05-11 11:05:13 -04:00
foreach ( UnrealTargetPlatform HostPlatform in HostPlatforms )
2016-03-11 09:55:03 -05:00
{
2018-10-03 22:08:11 -04:00
if ( Plugin . SupportedPrograms ! = null & & Plugin . SupportedPrograms . Contains ( "UnrealHeaderTool" ) )
2016-05-11 11:05:13 -04:00
{
2019-01-22 06:48:04 -05:00
CompilePluginWithUBT ( HostProjectFile , HostProjectPluginFile , Plugin , "UnrealHeaderTool" , TargetType . Program , HostPlatform , UnrealTargetConfiguration . Development , ManifestFileNames , String . Format ( "{0} -plugin={1}" , AdditionalArgs , CommandUtils . MakePathSafeToUseWithCommandLine ( HostProjectPluginFile . FullName ) ) ) ;
2016-05-11 11:05:13 -04:00
}
2018-10-12 16:52:09 -04:00
CompilePluginWithUBT ( HostProjectFile , HostProjectPluginFile , Plugin , "UE4Editor" , TargetType . Editor , HostPlatform , UnrealTargetConfiguration . Development , ManifestFileNames , AdditionalArgs ) ;
2016-03-11 09:55:03 -05:00
}
2015-05-01 15:01:10 -04:00
}
// Add the game targets
2016-05-11 11:05:13 -04:00
if ( TargetPlatforms . Count > 0 )
2015-05-01 15:01:10 -04:00
{
2018-08-14 18:32:34 -04:00
CommandUtils . LogInformation ( "Building plugin for target platforms: {0}" , String . Join ( ", " , TargetPlatforms ) ) ;
2016-05-11 11:05:13 -04:00
foreach ( UnrealTargetPlatform TargetPlatform in TargetPlatforms )
2015-05-01 15:01:10 -04:00
{
2018-07-09 04:44:44 -04:00
if ( Plugin . SupportsTargetPlatform ( TargetPlatform ) )
{
2019-01-22 06:48:04 -05:00
CompilePluginWithUBT ( HostProjectFile , HostProjectPluginFile , Plugin , "UE4Game" , TargetType . Game , TargetPlatform , UnrealTargetConfiguration . Development , ManifestFileNames , AdditionalArgs ) ;
CompilePluginWithUBT ( HostProjectFile , HostProjectPluginFile , Plugin , "UE4Game" , TargetType . Game , TargetPlatform , UnrealTargetConfiguration . Shipping , ManifestFileNames , AdditionalArgs ) ;
2018-07-09 04:44:44 -04:00
}
2015-05-01 15:01:10 -04:00
}
}
2015-05-01 10:58:14 -04:00
// Package the plugin to the output folder
2018-01-20 11:19:29 -05:00
HashSet < FileReference > BuildProducts = new HashSet < FileReference > ( ) ;
2018-10-12 16:52:09 -04:00
foreach ( FileReference ManifestFileName in ManifestFileNames )
2018-01-20 11:19:29 -05:00
{
2018-10-12 16:52:09 -04:00
BuildManifest Manifest = CommandUtils . ReadManifest ( ManifestFileName ) ;
BuildProducts . UnionWith ( Manifest . BuildProducts . Select ( x = > new FileReference ( x ) ) ) ;
2018-01-20 11:19:29 -05:00
}
return BuildProducts . ToArray ( ) ;
2015-05-01 10:58:14 -04:00
}
2018-10-12 16:52:09 -04:00
void CompilePluginWithUBT ( FileReference HostProjectFile , FileReference HostProjectPluginFile , PluginDescriptor Plugin , string TargetName , TargetType TargetType , UnrealTargetPlatform Platform , UnrealTargetConfiguration Configuration , List < FileReference > ManifestFileNames , string InAdditionalArgs )
2015-05-01 10:58:14 -04:00
{
// Find a list of modules that need to be built for this plugin
2018-01-20 11:19:29 -05:00
bool bCompilePlatform = false ;
2016-12-14 09:22:22 -05:00
if ( Plugin . Modules ! = null )
2015-05-01 10:58:14 -04:00
{
2016-12-14 09:22:22 -05:00
foreach ( ModuleDescriptor Module in Plugin . Modules )
2015-05-01 10:58:14 -04:00
{
2017-01-30 16:52:08 -05:00
bool bBuildDeveloperTools = ( TargetType = = TargetType . Editor | | TargetType = = TargetType . Program ) ;
bool bBuildEditor = ( TargetType = = TargetType . Editor ) ;
2017-04-04 20:49:52 -04:00
bool bBuildRequiresCookedData = ( TargetType ! = TargetType . Editor & & TargetType ! = TargetType . Program ) ;
2018-08-14 18:32:34 -04:00
if ( Module . IsCompiledInConfiguration ( Platform , Configuration , TargetName , TargetType , bBuildDeveloperTools , bBuildEditor , bBuildRequiresCookedData ) )
2016-12-14 09:22:22 -05:00
{
2018-01-20 11:19:29 -05:00
bCompilePlatform = true ;
2016-12-14 09:22:22 -05:00
}
2015-05-01 10:58:14 -04:00
}
}
// Add these modules to the build agenda
2018-01-20 11:19:29 -05:00
if ( bCompilePlatform )
2015-05-01 10:58:14 -04:00
{
2018-10-12 16:52:09 -04:00
FileReference ManifestFileName = FileReference . Combine ( HostProjectFile . Directory , "Saved" , String . Format ( "Manifest-{0}-{1}-{2}.xml" , TargetName , Platform , Configuration ) ) ;
ManifestFileNames . Add ( ManifestFileName ) ;
2018-02-27 17:30:35 -05:00
2019-04-16 17:10:21 -04:00
string Arguments = String . Format ( "-plugin={0} -iwyu -noubtmakefiles -manifest={1} -nohotreload" , CommandUtils . MakePathSafeToUseWithCommandLine ( HostProjectPluginFile . FullName ) , CommandUtils . MakePathSafeToUseWithCommandLine ( ManifestFileName . FullName ) ) ;
2015-05-05 18:52:17 -04:00
if ( ! String . IsNullOrEmpty ( InAdditionalArgs ) )
{
Arguments + = InAdditionalArgs ;
}
2015-05-01 10:58:14 -04:00
2018-01-20 11:19:29 -05:00
CommandUtils . RunUBT ( CmdEnv , UE4Build . GetUBTExecutable ( ) , String . Format ( "{0} {1} {2} {3}" , TargetName , Platform , Configuration , Arguments ) ) ;
2015-05-01 10:58:14 -04:00
}
}
2017-04-10 11:00:33 -04:00
static void PackagePlugin ( FileReference SourcePluginFile , IEnumerable < FileReference > BuildProducts , DirectoryReference TargetDir , bool bUnversioned )
2015-05-01 10:58:14 -04:00
{
2016-05-11 11:05:13 -04:00
DirectoryReference SourcePluginDir = SourcePluginFile . Directory ;
2015-05-01 10:58:14 -04:00
// Copy all the files to the output directory
2016-05-11 11:05:13 -04:00
FileReference [ ] SourceFiles = FilterPluginFiles ( SourcePluginFile , BuildProducts ) . ToArray ( ) ;
foreach ( FileReference SourceFile in SourceFiles )
2015-05-01 10:58:14 -04:00
{
2016-05-11 11:05:13 -04:00
FileReference TargetFile = FileReference . Combine ( TargetDir , SourceFile . MakeRelativeTo ( SourcePluginDir ) ) ;
CommandUtils . CopyFile ( SourceFile . FullName , TargetFile . FullName ) ;
CommandUtils . SetFileAttributes ( TargetFile . FullName , ReadOnly : false ) ;
2015-05-01 10:58:14 -04:00
}
// Get the output plugin filename
2016-05-11 11:05:13 -04:00
FileReference TargetPluginFile = FileReference . Combine ( TargetDir , SourcePluginFile . GetFileName ( ) ) ;
2017-07-21 12:42:36 -04:00
PluginDescriptor NewDescriptor = PluginDescriptor . FromFile ( TargetPluginFile ) ;
NewDescriptor . bEnabledByDefault = null ;
2015-05-01 10:58:14 -04:00
NewDescriptor . bInstalled = true ;
2017-04-10 11:00:33 -04:00
if ( ! bUnversioned )
{
BuildVersion Version ;
2017-08-31 12:08:38 -04:00
if ( BuildVersion . TryRead ( BuildVersion . GetDefaultFileName ( ) , out Version ) )
2017-04-10 11:00:33 -04:00
{
NewDescriptor . EngineVersion = String . Format ( "{0}.{1}.0" , Version . MajorVersion , Version . MinorVersion ) ;
}
}
2017-07-21 12:42:36 -04:00
NewDescriptor . Save ( TargetPluginFile . FullName ) ;
2015-05-01 10:58:14 -04:00
}
2016-05-11 11:05:13 -04:00
static IEnumerable < FileReference > FilterPluginFiles ( FileReference PluginFile , IEnumerable < FileReference > BuildProducts )
2015-05-01 10:58:14 -04:00
{
// Set up the default filter
FileFilter Filter = new FileFilter ( ) ;
2017-02-21 15:51:42 -05:00
Filter . AddRuleForFile ( PluginFile , PluginFile . Directory , FileFilterType . Include ) ;
2016-05-11 11:05:13 -04:00
Filter . AddRuleForFiles ( BuildProducts , PluginFile . Directory , FileFilterType . Include ) ;
2015-08-19 11:27:49 -04:00
Filter . Include ( "/Binaries/ThirdParty/..." ) ;
2015-05-01 10:58:14 -04:00
Filter . Include ( "/Resources/..." ) ;
Filter . Include ( "/Content/..." ) ;
Filter . Include ( "/Intermediate/Build/.../Inc/..." ) ;
2018-10-04 17:37:57 -04:00
Filter . Include ( "/Shaders/..." ) ;
2015-05-01 10:58:14 -04:00
Filter . Include ( "/Source/..." ) ;
// Add custom rules for each platform
2016-05-11 11:05:13 -04:00
FileReference FilterFile = FileReference . Combine ( PluginFile . Directory , "Config" , "FilterPlugin.ini" ) ;
2017-01-30 16:52:08 -05:00
if ( FileReference . Exists ( FilterFile ) )
2015-05-01 10:58:14 -04:00
{
2018-08-14 18:32:34 -04:00
CommandUtils . LogInformation ( "Reading filter rules from {0}" , FilterFile ) ;
2017-03-14 15:48:33 -04:00
Filter . ReadRulesFromFile ( FilterFile , "FilterPlugin" ) ;
2015-05-01 10:58:14 -04:00
}
// Apply the standard exclusion rules
2019-05-31 18:06:13 -04:00
foreach ( string RestrictedFolderName in RestrictedFolder . GetNames ( ) )
{
Filter . AddRule ( String . Format ( ".../{0}/..." , RestrictedFolderName ) , FileFilterType . Exclude ) ;
}
2015-05-01 10:58:14 -04:00
// Apply the filter to the plugin directory
2016-05-11 11:05:13 -04:00
return Filter . ApplyToDirectory ( PluginFile . Directory , true ) ;
2015-05-01 10:58:14 -04:00
}
2016-09-30 08:50:19 -04:00
static List < UnrealTargetPlatform > GetTargetPlatforms ( BuildCommand Command , UnrealTargetPlatform HostPlatform )
{
List < UnrealTargetPlatform > TargetPlatforms = new List < UnrealTargetPlatform > ( ) ;
if ( ! Command . ParseParam ( "NoTargetPlatforms" ) )
{
2016-11-23 15:34:07 -05:00
// Only interested in building for Platforms that support code projects
2017-08-31 12:08:38 -04:00
TargetPlatforms = PlatformExports . GetRegisteredPlatforms ( ) . Where ( x = > InstalledPlatformInfo . IsValidPlatform ( x , EProjectType . Code ) ) . ToList ( ) ;
2016-09-30 08:50:19 -04:00
2016-11-23 15:34:07 -05:00
// only build Mac on Mac
if ( HostPlatform ! = UnrealTargetPlatform . Mac & & TargetPlatforms . Contains ( UnrealTargetPlatform . Mac ) )
2016-09-30 08:50:19 -04:00
{
2016-11-23 15:34:07 -05:00
TargetPlatforms . Remove ( UnrealTargetPlatform . Mac ) ;
2016-09-30 08:50:19 -04:00
}
2016-11-23 15:34:07 -05:00
// only build Windows on Windows
if ( HostPlatform ! = UnrealTargetPlatform . Win64 & & TargetPlatforms . Contains ( UnrealTargetPlatform . Win64 ) )
2016-09-30 08:50:19 -04:00
{
2016-11-23 15:34:07 -05:00
TargetPlatforms . Remove ( UnrealTargetPlatform . Win64 ) ;
TargetPlatforms . Remove ( UnrealTargetPlatform . Win32 ) ;
2016-09-30 08:50:19 -04:00
}
2016-11-23 15:34:07 -05:00
// build Linux on Windows and Linux
if ( HostPlatform ! = UnrealTargetPlatform . Win64 & & HostPlatform ! = UnrealTargetPlatform . Linux & & TargetPlatforms . Contains ( UnrealTargetPlatform . Linux ) )
2016-09-30 08:50:19 -04:00
{
2016-11-23 15:34:07 -05:00
TargetPlatforms . Remove ( UnrealTargetPlatform . Linux ) ;
2016-09-30 08:50:19 -04:00
}
// 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 ;
2019-05-31 18:06:13 -04:00
if ( ! UnrealTargetPlatform . TryParse ( TargetPlatformName , out TargetPlatform ) )
2016-09-30 08:50:19 -04:00
{
throw new AutomationException ( "Unknown target platform '{0}' specified on command line" ) ;
}
2019-05-31 18:06:13 -04:00
if ( TargetPlatforms . Contains ( TargetPlatform ) )
2016-09-30 08:50:19 -04:00
{
NewTargetPlatforms . Add ( TargetPlatform ) ;
}
}
TargetPlatforms = NewTargetPlatforms ;
}
}
return TargetPlatforms ;
}
2015-05-01 10:58:14 -04:00
}
2016-09-30 08:50:19 -04:00