3using System.Collections.Generic;
6using System.Runtime.InteropServices;
7using EverythingSearchClient;
8using Newtonsoft.Json.Linq;
10using Nuke.Cola.FolderComposition;
11using Nuke.Cola.Search;
14using Nuke.Common.Utilities;
15using Nuke.Common.Utilities.Collections;
47 RelativePath? OutputSubfolder =
null,
48 AbsolutePath? OutputOverride =
null,
49 bool GenerateFilterPluginIni =
true,
50 bool UPluginAssociateEngineVersion =
true,
51 bool UPluginIsInstalled =
true,
52 Func<PluginDescriptor, PluginDescriptor>? UPluginConfig = null
98 RelativePath? OutputSubfolder =
null,
99 AbsolutePath? OutputOverride =
null,
100 bool UseDistributedPlugin =
true,
110 Func<UbtConfig, UbtConfig>?
UbtConfig =
null,
113 Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? DependencyHandler = null
121 public static readonly Dictionary<AbsolutePath, UnrealPlugin> Instances = [];
122 internal static string RelativePathDistinction<T>(T path)
123 => path!.ToString()!.Trim().Trim(
'/');
146 var uplugin = from.FileExists() && from.HasExtension(
".uplugin") ? from : from.GetOwningPlugin();
147 Assert.NotNull(uplugin, $
"Given plugin context {from} didn't contain an Unreal plugin");
148 if (!Instances.ContainsKey(uplugin!))
150 Log.Debug(
"Handling plugin: {0}", uplugin!.NameWithoutExtension);
151 Instances.Add(uplugin!,
new (uplugin!));
153 return Instances[uplugin!];
160 if (FilterPluginIni.FileExists())
162 Log.Debug(
"Considering FilterPlugin.ini of {0}",
Name);
163 var inFiles = FilterPluginIni.ReadAllLines()
164 .Where(l => !l.Contains(
"[FilterPlugin]"))
165 .Where(l => !
string.IsNullOrWhiteSpace(l))
166 .Where(l => !l.StartsWith(
';'))
167 .Select(l => (RelativePath)l.Trim())
168 .DistinctBy(RelativePathDistinction)
189 public AbsolutePath ConfigFolder =>
Folder /
"Config";
190 public AbsolutePath FilterPluginIni => ConfigFolder /
"FilterPlugin.ini";
197 private Version? _versionCache =
null;
205 get => _versionCache ?? (
213 _versionCache = value;
218 private List<UnixRelativePath> _explicitPluginFiles = [];
240 => _explicitPluginFiles = [..
243 pluginRelativePaths.Select(f => f.ToUnixRelativePath()),
244 RelativePathDistinction
249 private RelativePath _defaultDistSubdir => (RelativePath) $
"Plugins/{Name}/Distribution";
261 options ??= _distributionOptionsCache;
262 _distributionOptionsCache = options;
264 var outputSubfolder = options.OutputSubfolder ?? _defaultDistSubdir;
265 return options.OutputOverride ?? (build.
GetOutput() / outputSubfolder);
269 private RelativePath _defaultBuildSubdir => (RelativePath) $
"Plugins/{Name}/Build";
281 options ??= _buildOptionsCache;
282 _buildOptionsCache = options;
284 var outputSubfolder = options.OutputSubfolder ?? _defaultBuildSubdir;
285 return options.OutputOverride ?? (build.
GetOutput() / outputSubfolder);
294 AddDefaultExplicitPluginFiles(build);
296 var configPath = FilterPluginIni;
297 Log.Debug(
"Generating FilterPlugin.ini: {0}", configPath);
299 var lines = _explicitPluginFiles
300 .Select(f => (
"/" + f.ToString()).Replace(
"//",
"/"))
303 if (!lines.IsEmpty())
304 configPath.WriteAllLines(lines.Prepend(
"[FilterPlugin]"));
306 Log.Debug(
"There were no files to list in FilterPlugin.ini");
309 private static readonly ExportManifest _filterExportManifest =
new()
311 Copy = {
new() { File =
"Config/**/*.ini" , Not = {
"FilterPlugin.ini"}} },
323 new() { File =
"**/PluginFiles.yml" }
327 private IEnumerable<AbsolutePath> GetDefaultExplicitPluginFiles(
UnrealBuild build)
328 => build.ImportFolder((
Folder,
Folder.Parent /
"ShouldntExist",
"PluginFiles.yml"),
new(
331 ForceCopyLinks:
true,
332 AddToMain: [_filterExportManifest]
333 )).WithFilesExpanded().Select(f => f.From);
335 private void AddDefaultExplicitPluginFiles(
UnrealBuild build)
340 var plumbingRootRelative = (RelativePath)
"Source" /
"ThirdParty" /
"BinaryPlumbing";
341 var plumbingRoot =
Folder / plumbingRootRelative;
342 if (!plumbingRoot.DirectoryExists())
348 Log.Information(
"{0} seemingly requires binary plumbing",
Name);
349 Log.Debug(
"Because {0} exists", plumbingRoot);
351 result = descriptor with {
352 PreBuildSteps = descriptor.PreBuildSteps?.ToDictionary() ?? []
354 result.PreBuildSteps.EnsureDevelopmentPlatforms();
358 result.PreBuildSteps[platform] = [..
359 result.PreBuildSteps[platform]
360 .FilterBuildStepBlock(
"BinaryPlumbing")
361 .StartBuildStepBlock(
"BinaryPlumbing"),
362 $
"echo Plumbing runtime dependencies for {Name} plugin",
366 foreach (var subdir
in plumbingRoot.GetDirectories())
368 Log.Debug(
"Plumbing from {0}", subdir);
371 $
"echo Copying {subdir.Name} runtime dependencies",
372 $
"robocopy /s /v /njh /njs /np /ndl \"$(PluginDir)\\{plumbingRootRelative.ToWinRelativePath()}\\{subdir.Name}\" \"$(PluginDir)\\Binaries\"",
373 "if %ERRORLEVEL% LSS 8 (exit /B 0) else (exit /B %ERRORLEVEL%)"
377 $
"echo Copying {subdir.Name} runtime dependencies",
378 $
"cp -vnpR \"$(PluginDir)/{plumbingRootRelative.ToUnixRelativePath()}/{subdir.Name}\" \"$(PluginDir)/Binaries\"",
379 "chmod -R +x \"$(PluginDir)/Binaries\""
383 $
"echo Copying {subdir.Name} runtime dependencies",
384 $
"cp -vnpR \"$(PluginDir)/{plumbingRootRelative.ToUnixRelativePath()}/{subdir.Name}\" \"$(PluginDir)/Binaries\"",
385 "chmod -R +x \"$(PluginDir)/Binaries\""
408 public (IEnumerable<ImportedItem>
result, AbsolutePath output) DistributeSource(
414 options ??= _distributionOptionsCache;
416 if (options.GenerateFilterPluginIni && !pretend)
419 var
result = build.ImportFolder((
Folder, outFolder,
"PluginFiles.yml"),
new(
421 ForceCopyLinks:
true,
423 AddToMain: [_filterExportManifest,
new()
426 new() { File =
"Content/**/*" , Not = {
"PluginFiles.yml" }},
427 new() { File =
"Shaders/**/*" , Not = {
"PluginFiles.yml" }},
428 new() { File =
"Source/**/*" , Not = {
"PluginFiles.yml" }},
429 new() { File =
"Resources/**/*" , Not = {
"PluginFiles.yml" }},
430 new() { File =
"Tests/**/*" , Not = {
"PluginFiles.yml" }},
431 new() { File =
"*.uplugin" , Not = {
"PluginFiles.yml" }},
440 if (!pretend && options.UPluginAssociateEngineVersion)
446 if (!pretend && InjectBinaryPlumbing(descriptor, out descriptor))
451 if (!pretend && options.UPluginIsInstalled)
453 descriptor = descriptor with { Installed =
true };
457 if (!pretend && options.UPluginConfig !=
null)
459 descriptor = options.UPluginConfig(descriptor);
463 return (
result.WithFilesExpanded(), outFolder);
473 .Where(p => p.Enabled ??
false)
476 var depFIle = build.PluginsFolder.SearchFiles($
"**/{p.Name}.uplugin").FirstOrDefault();
477 return depFIle !=
null ?
Get(depFIle) :
null;
479 .Where(p => p !=
null)
484 private AbsolutePath? _buildOutput =
null;
499 Func<UatConfig, UatConfig>? uatConfig =
null,
500 Func<UbtConfig, UbtConfig>? ubtConfig =
null,
503 Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? dependencyHandler =
null
506 buildOptions ??= _buildOptionsCache;
507 uatConfig ??= _ => _;
508 ubtConfig ??= _ => _;
510 var sourceFolder =
Folder;
511 if (buildOptions.UseDistributedPlugin || buildOptions.Method ==
PluginBuildMethod.UBT)
513 var (_, distFolder) = DistributeSource(build, distOptions);
514 sourceFolder = distFolder;
517 var hostProjectDir = build.
GetOutput() /
"Plugins" /
Name /
"HostProject";
518 outFolder.CreateOrCleanDirectory();
519 hostProjectDir.ExistingDirectory()?.DeleteDirectory();
521 var platforms = (buildOptions.Platforms ?? []).Union([build.Platform]);
532 foreach (var unbuiltDep
in dependencies.Where(d => d._buildOutput ==
null))
534 Log.Information(
"Building dependency plugin {0}", unbuiltDep.Name);
535 var args = dependencyHandler?.Invoke(unbuiltDep, thisArgs) ?? thisArgs;
536 unbuiltDep.BuildPlugin(
542 args.DependencyHandler
545 var enginePluginsDir = build.UnrealEnginePath /
"Engine" /
"Plugins" /
"Marketplace";
546 foreach (var dep
in dependencies)
548 Log.Information(
"Copying dependency plugin {0} to {1}", dep.Name, enginePluginsDir);
549 dep._buildOutput.Copy(enginePluginsDir / dep.Name);
552 switch (buildOptions.Method)
556 var shortSource = sourceFolder.Shorten();
557 var shortOut = outFolder.Shorten();
564 .TargetPlatforms(
string.Join(
'+', platforms))
573 _buildOutput = outFolder;
575 catch (Exception) {
throw; }
578 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
580 shortSource.DeleteDirectory();
581 shortOut.AsLinkInfo()?.Delete();
589 hostProjectDir.CreateDirectory();
590 var hostPluginDir = hostProjectDir /
"Plugins" /
Name;
602 Unreal.
WriteJson(hostProject, hostProjectDir /
"HostProject.uproject");
603 sourceFolder.Copy(hostPluginDir);
605 var shortHostProjectDir = hostProjectDir.Shorten();
606 var shortPluginDir = shortHostProjectDir /
"Plugins" /
Name;
615 .
Project(shortHostProjectDir /
"HostProject.uproject")
623 foreach(var platform
in platforms)
625 Log.Information(
"Building UnrealGame binaries from UProject for {0} @ {1}",
Name, platform);
627 .Target(
"UnrealGame", platform,
632 .Apply(CommonProject)
634 if (platform.IsDevelopment)
636 Log.Information(
"Building UnrealEditor binaries for UProject {0} @ {1}",
Name, platform);
638 .Target(
"UnrealEditor", platform, [
UnrealConfig.Development])
639 .Apply(CommonProject)
642 Log.Information(
"Building UnrealGame binaries from UPlugin for {0} @ {1}",
Name, platform);
644 .Target(
"UnrealGame", platform,
651 if (platform.IsDevelopment)
653 Log.Information(
"Building UnrealEditor binaries for UPlugin {0} @ {1}",
Name, platform);
655 .Target(
"UnrealEditor", platform, [
UnrealConfig.Development])
660 hostPluginDir.Copy(outFolder, ExistsPolicy.MergeAndOverwrite);
661 hostProjectDir.DeleteDirectory();
662 _buildOutput = outFolder;
664 catch(Exception) {
throw; }
667 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
668 shortPluginDir.AsLinkInfo()?.Delete();
670 foreach (var dep
in dependencies)
672 Log.Debug(
"Deleting temporary installed plugin {0}", enginePluginsDir / dep.Name);
673 (enginePluginsDir / dep.Name).ExistingDirectory()?.DeleteDirectory();
686 internal static IEnumerable<string> FilterBuildStepBlock(
this IEnumerable<string>
self,
string name)
688 .TakeUntil(a => a ==
"echo GENERATED BUILD STEPS".AppendNonEmpty(name))
690 self.SkipUntil(a => a ==
"echo GENERATED BUILD STEPS".AppendNonEmpty(name))
692 .SkipUntil(a => a.StartsWith(
"echo GENERATED BUILD STEPS"))
695 internal static IEnumerable<string> StartBuildStepBlock(
this IEnumerable<string>
self,
string name)
696 =>
self.Append(
"echo GENERATED BUILD STEPS".AppendNonEmpty(name));
698 internal static void EnsureDevelopmentPlatforms(
this Dictionary<
UnrealPlatform, List<string>>
self)
702 if (!
self.ContainsKey(platform))
self.Add(platform, []);
High level representation of an Unreal Engine version.
string VersionMinor
Only the Major.Minor version components, with extra information trimmed.
A class encapsulating information and tasks around one Unreal plugin.
IEnumerable< ImportedItem > result
Create a copy of this plugin which can be distributed to other developers or other tools who shouldn'...
void AddExplicitPluginFiles(IEnumerable< AbsolutePath > files)
Add explicit plugin files to be listed later in FilterPlugin.ini.
Version Version
Semantic version of the plugin. Parsed from the uplugin file. If set, it will modify the ....
AbsolutePath Folder
Path to folder containing the .uplugin file.
PluginDescriptor Descriptor
"Immutable" C# representation of the uplugin file
void GenerateFilterPluginIni(UnrealBuild build)
Generate the FilterPlugin.ini file after all components have submitted their explicit files.
AbsolutePath GetBuildOutput(UnrealBuild build, PluginBuildOptions? options=null)
Gets the output folder for packaging this plugin.
AbsolutePath GetDistributionOutput(UnrealBuild build, PluginDistributionOptions? options=null)
Gets the output folder for distribution.
void AddExplicitPluginFiles(IEnumerable< RelativePath > pluginRelativePaths)
Add explicit plugin files to be listed later in FilterPlugin.ini.
static UnrealPlugin Get(AbsolutePath from)
AbsolutePath PluginPath
Path to the .uplugin file.
IEnumerable< UnrealPlugin > GetProjectPluginDependencies(UnrealBuild build)
Get the project plugin dependencies of this plugin as UnrealPlugin objects.
string Name
Short name of the plugin.
IEnumerable< AbsolutePath > ExplicitPluginFiles
Return list of files which may need extra mention for Unreal/Epic tools. This is also usually the con...
AbsolutePath BuildPlugin(UnrealBuild build, Func< UatConfig, UatConfig >? uatConfig=null, Func< UbtConfig, UbtConfig >? ubtConfig=null, PluginBuildOptions? buildOptions=null, PluginDistributionOptions? distOptions=null, Func< UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? dependencyHandler=null)
Make a prebuilt release of this plugin for end-users. Globally set UAT and UBT arguments are used fro...
The main build class Unreal projects using Nuke.Unreal should inherit from. This class contains all b...
ProjectDescriptor ProjectDescriptor
"Immutable" C# representation of the .uproject contents
virtual UbtConfig UbtGlobal(UbtConfig _)
UBT arguments to be applied globally for all UBT invocations. Override this function in your main bui...
virtual AbsolutePath GetOutput()
Get an output folder where the targets should store their artifacts. Override this function in your m...
virtual UatConfig UatGlobal(UatConfig _)
UAT arguments to be applied globally for all UAT invocations. Override this function in your main bui...
Build configurations UBT supports.
A collection of utilities around basic functions regarding the environment of the Engine we're workin...
static void WriteJson(object input, AbsolutePath path)
Write data in JSON with Unreal conventions of JSON format.
static bool Is4(UnrealBuild build)
Are we working with UE4.
static Tool AutomationTool(EngineVersion ofVersion)
Prepare invocation for UAT.
static Tool BuildTool(EngineVersion ofVersion)
Prepare invocation for UBT.
static readonly JsonSerializerSettings JsonReadSettings
Common JsonSerializerSettings for Unreal conventions of JSON format.
static EngineVersion Version(UnrealBuild build)
Get high-level version of currently used Engine.
record class PluginReferenceDescriptor(string? Name=null, bool? Enabled=null, bool? Optional=null, string? Description=null, string? MarketplaceURL=null, List< string >? PlatformAllowList=null, List< string >? PlatformDenyList=null, List< UnrealConfig >? TargetConfigurationAllowList=null, List< UnrealConfig >? TargetConfigurationDenyList=null, List< UnrealTargetType >? TargetAllowList=null, List< UnrealTargetType >? TargetDenyList=null, List< string >? SupportedTargetPlatforms=null, bool? HasExplicitPlatforms=null, int? RequestedVersion=null)
Representation of a reference to a plugin from a project file.
record class PluginDescriptor(int FileVersion=3, int Version=1, string? VersionName=null, string? FriendlyName=null, string? Description=null, string? Category=null, string? CreatedBy=null, string? CreatedByURL=null, string? DocsURL=null, string? MarketplaceURL=null, string? SupportURL=null, string? EngineVersion=null, bool? IsPluginExtension=null, List< UnrealPlatform >? SupportedTargetPlatforms=null, List< string >? SupportedPrograms=null, List< ModuleDescriptor >? Modules=null, List< LocalizationTargetDescriptor >? LocalizationTargets=null, bool? EnabledByDefault=null, bool? CanContainContent=null, bool? CanContainVerse=null, bool? IsBetaVersion=null, bool? IsExperimentalVersion=null, bool? Installed=null, bool? RequiresBuildPlatform=null, bool? IsSealed=null, bool? NoCode=null, bool? ExplicitlyLoaded=null, bool? HasExplicitPlatforms=null, Dictionary< UnrealPlatform, List< string > >? PreBuildSteps=null, Dictionary< UnrealPlatform, List< string > >? PostBuildSteps=null, List< PluginReferenceDescriptor >? Plugins=null, List< string >? DisallowedPlugins=null)
In-memory representation of a .uplugin file.
record class PluginDistributionOptions(RelativePath? OutputSubfolder=null, AbsolutePath? OutputOverride=null, bool GenerateFilterPluginIni=true, bool UPluginAssociateEngineVersion=true, bool UPluginIsInstalled=true, Func< PluginDescriptor, PluginDescriptor >? UPluginConfig=null)
Options for creating a distributable version of this plugin, usually for making it independent of Nuk...
PluginBuildMethod
Method of packaging a plugin.
@ UAT
Use vanilla UAT BuildPlugin feature.
record class PluginBuildArguments(Func< UatConfig, UatConfig >? UatConfig=null, Func< UbtConfig, UbtConfig >? UbtConfig=null, PluginBuildOptions? BuildOptions=null, PluginDistributionOptions? DistOptions=null, Func< UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? DependencyHandler=null)
Arguments for BuildPlugin.
record class PluginBuildOptions(RelativePath? OutputSubfolder=null, AbsolutePath? OutputOverride=null, bool UseDistributedPlugin=true, PluginBuildMethod Method=PluginBuildMethod.UBT, UnrealPlatform[]? Platforms=null)
Options for packaging plugins for binary distribution..
record class ProjectDescriptor(int FileVersion=3, string? EngineAssociation=null, string? Category=null, string? Description=null, List< ModuleDescriptor >? Modules=null, List< PluginReferenceDescriptor >? Plugins=null, List< string >? AdditionalRootDirectories=null, List< string >? AdditionalPluginDirectories=null, List< UnrealPlatform >? TargetPlatforms=null, uint? EpicSampleNameHash=null, Dictionary< UnrealPlatform, List< string > >? InitSteps=null, Dictionary< UnrealPlatform, List< string > >? PreBuildSteps=null, Dictionary< UnrealPlatform, List< string > >? PostBuildSteps=null, bool? IsEnterpriseProject=null, bool? DisableEnginePluginsByDefault=null)
In-memory representation of a .uproject file.