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;
48 RelativePath? OutputSubfolder =
null,
49 AbsolutePath? OutputOverride =
null,
50 bool GenerateFilterPluginIni =
true,
51 bool UPluginAssociateEngineVersion =
true,
52 bool UPluginIsInstalled =
true,
53 Func<PluginDescriptor, PluginDescriptor>? UPluginConfig = null
100 RelativePath? OutputSubfolder =
null,
101 AbsolutePath? OutputOverride =
null,
102 bool UseDistributedPlugin =
true,
112 Func<UbtConfig, UbtConfig>?
UbtConfig =
null,
115 Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? DependencyHandler = null
123 public static readonly Dictionary<AbsolutePath, UnrealPlugin> Instances = [];
124 internal static string RelativePathDistinction<T>(T path)
125 => path!.ToString()!.Trim().Trim(
'/');
148 var uplugin = from.FileExists() && from.HasExtension(
".uplugin") ? from : from.GetOwningPlugin();
149 Assert.NotNull(uplugin, $
"Given plugin context {from} didn't contain an Unreal plugin");
150 if (!Instances.ContainsKey(uplugin!))
152 Log.Debug(
"Handling plugin: {0}", uplugin!.NameWithoutExtension);
153 Instances.Add(uplugin!,
new (uplugin!));
155 return Instances[uplugin!];
162 if (FilterPluginIni.FileExists())
164 Log.Debug(
"Considering FilterPlugin.ini of {0}",
Name);
165 var inFiles = FilterPluginIni.ReadAllLines()
166 .Where(l => !l.Contains(
"[FilterPlugin]"))
167 .Where(l => !
string.IsNullOrWhiteSpace(l))
168 .Where(l => !l.StartsWith(
';'))
169 .Select(l => (RelativePath)l.Trim())
170 .DistinctBy(RelativePathDistinction)
191 public AbsolutePath ConfigFolder =>
Folder /
"Config";
192 public AbsolutePath FilterPluginIni => ConfigFolder /
"FilterPlugin.ini";
199 private Version? _versionCache =
null;
207 get => _versionCache ?? (
215 _versionCache = value;
220 private List<UnixRelativePath> _explicitPluginFiles = [];
242 => _explicitPluginFiles = [..
245 pluginRelativePaths.Select(f => f.ToUnixRelativePath()),
246 RelativePathDistinction
251 private RelativePath _defaultDistSubdir => (RelativePath) $
"Plugins/{Name}/Distribution";
263 options ??= _distributionOptionsCache;
264 _distributionOptionsCache = options;
266 var outputSubfolder = options.OutputSubfolder ?? _defaultDistSubdir;
267 return options.OutputOverride ?? (build.
GetOutput() / outputSubfolder);
271 private RelativePath _defaultBuildSubdir => (RelativePath) $
"Plugins/{Name}/Build";
283 options ??= _buildOptionsCache;
284 _buildOptionsCache = options;
286 var outputSubfolder = options.OutputSubfolder ?? _defaultBuildSubdir;
287 return options.OutputOverride ?? (build.
GetOutput() / outputSubfolder);
296 AddDefaultExplicitPluginFiles(build);
298 var configPath = FilterPluginIni;
299 Log.Debug(
"Generating FilterPlugin.ini: {0}", configPath);
301 var lines = _explicitPluginFiles
302 .Select(f => (
"/" + f.ToString()).Replace(
"//",
"/"))
305 if (!lines.IsEmpty())
306 configPath.WriteAllLines(lines.Prepend(
"[FilterPlugin]"));
308 Log.Debug(
"There were no files to list in FilterPlugin.ini");
311 private static readonly ExportManifest _filterExportManifest =
new()
313 Copy = {
new() { File =
"Config/**/*.ini" , Not = {
"FilterPlugin.ini"}} },
325 new() { File =
"**/PluginFiles.yml" }
329 private IEnumerable<AbsolutePath> GetDefaultExplicitPluginFiles(
IUnrealBuild build)
330 => build.ImportFolder((
Folder,
Folder.Parent /
"ShouldntExist",
"PluginFiles.yml"),
new(
333 ForceCopyLinks:
true,
334 AddToMain: [_filterExportManifest]
335 )).WithFilesExpanded().Select(f => f.From);
337 private void AddDefaultExplicitPluginFiles(
IUnrealBuild build)
342 var plumbingRootRelative = (RelativePath)
"Source" /
"ThirdParty" /
"BinaryPlumbing";
343 var plumbingRoot =
Folder / plumbingRootRelative;
344 if (!plumbingRoot.DirectoryExists())
350 Log.Information(
"{0} seemingly requires binary plumbing",
Name);
351 Log.Debug(
"Because {0} exists", plumbingRoot);
353 result = descriptor with {
354 PreBuildSteps = descriptor.PreBuildSteps?.ToDictionary() ?? []
356 result.PreBuildSteps.EnsureDevelopmentPlatforms();
360 result.PreBuildSteps[platform] = [..
361 result.PreBuildSteps[platform]
362 .FilterBuildStepBlock(
"BinaryPlumbing")
363 .StartBuildStepBlock(
"BinaryPlumbing"),
364 $
"echo Plumbing runtime dependencies for {Name} plugin",
368 foreach (var subdir
in plumbingRoot.GetDirectories())
370 Log.Debug(
"Plumbing from {0}", subdir);
373 $
"echo Copying {subdir.Name} runtime dependencies",
374 $
"robocopy /s /v /njh /njs /np /ndl \"$(PluginDir)\\{plumbingRootRelative.ToWinRelativePath()}\\{subdir.Name}\" \"$(PluginDir)\\Binaries\"",
375 "if %ERRORLEVEL% LSS 8 (exit /B 0) else (exit /B %ERRORLEVEL%)"
379 $
"echo Copying {subdir.Name} runtime dependencies",
380 $
"cp -vnpR \"$(PluginDir)/{plumbingRootRelative.ToUnixRelativePath()}/{subdir.Name}\" \"$(PluginDir)/Binaries\"",
381 "chmod -R +x \"$(PluginDir)/Binaries\""
385 $
"echo Copying {subdir.Name} runtime dependencies",
386 $
"cp -vnpR \"$(PluginDir)/{plumbingRootRelative.ToUnixRelativePath()}/{subdir.Name}\" \"$(PluginDir)/Binaries\"",
387 "chmod -R +x \"$(PluginDir)/Binaries\""
410 public (IEnumerable<ImportedItem>
result, AbsolutePath output) DistributeSource(
416 options ??= _distributionOptionsCache;
418 if (options.GenerateFilterPluginIni && !pretend)
421 var
result = build.ImportFolder((
Folder, outFolder,
"PluginFiles.yml"),
new(
423 ForceCopyLinks:
true,
425 AddToMain: [_filterExportManifest,
new()
428 new() { File =
"Content/**/*" , Not = {
"PluginFiles.yml" }},
429 new() { File =
"Shaders/**/*" , Not = {
"PluginFiles.yml" }},
430 new() { File =
"Source/**/*" , Not = {
"PluginFiles.yml" }},
431 new() { File =
"Resources/**/*" , Not = {
"PluginFiles.yml" }},
432 new() { File =
"Tests/**/*" , Not = {
"PluginFiles.yml" }},
433 new() { File =
"*.uplugin" , Not = {
"PluginFiles.yml" }},
442 if (!pretend && options.UPluginAssociateEngineVersion)
448 if (!pretend && InjectBinaryPlumbing(descriptor, out descriptor))
453 if (!pretend && options.UPluginIsInstalled)
455 descriptor = descriptor with { Installed =
true };
459 if (!pretend && options.UPluginConfig !=
null)
461 descriptor = options.UPluginConfig(descriptor);
465 return (
result.WithFilesExpanded(), outFolder);
475 .Where(p => p.Enabled ??
false)
478 var depFIle = build.
PluginsFolder.SearchFiles($
"**/{p.Name}.uplugin").FirstOrDefault();
479 return depFIle !=
null ?
Get(depFIle) :
null;
481 .Where(p => p !=
null)
486 private AbsolutePath? _buildOutput =
null;
501 Func<UatConfig, UatConfig>? uatConfig =
null,
502 Func<UbtConfig, UbtConfig>? ubtConfig =
null,
505 Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? dependencyHandler =
null
508 buildOptions ??= _buildOptionsCache;
509 uatConfig ??= _ => _;
510 ubtConfig ??= _ => _;
512 var sourceFolder =
Folder;
513 if (buildOptions.UseDistributedPlugin || buildOptions.Method ==
PluginBuildMethod.UBT)
515 var (_, distFolder) = DistributeSource(build, distOptions);
516 sourceFolder = distFolder;
519 var hostProjectDir = build.
GetOutput() /
"Plugins" /
Name /
"HostProject";
520 outFolder.CreateOrCleanDirectory();
521 hostProjectDir.ExistingDirectory()?.DeleteDirectory();
523 var platforms = (buildOptions.Platforms ?? [])
525 .Union(
Descriptor.SupportedTargetPlatforms ?? [])
529 foreach (var platform
in platforms)
531 var sdk = platform.GetSdk();
534 Assert.True(sdk.IsValid(build), $
"Attempting to use a platform which is not set up for cross compiling ({Unreal.GetHostPlatform()} -> {platform})");
535 sdk.Setup(build).Wait();
548 foreach (var unbuiltDep
in dependencies.Where(d => d._buildOutput ==
null))
550 Log.Information(
"Building dependency plugin {0}", unbuiltDep.Name);
551 var args = dependencyHandler?.Invoke(unbuiltDep, thisArgs) ?? thisArgs;
552 unbuiltDep.BuildPlugin(
558 args.DependencyHandler
561 var enginePluginsDir = build.UnrealEnginePath /
"Engine" /
"Plugins" /
"Marketplace";
562 foreach (var dep
in dependencies)
564 Log.Information(
"Copying dependency plugin {0} to {1}", dep.Name, enginePluginsDir);
565 dep._buildOutput.Copy(enginePluginsDir / dep.Name);
568 switch (buildOptions.Method)
572 var shortSource = sourceFolder.Shorten();
573 var shortOut = outFolder.Shorten();
580 .TargetPlatforms(
string.Join(
'+', platforms))
589 _buildOutput = outFolder;
591 catch (Exception) {
throw; }
594 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
596 shortSource.AsLinkInfo()?.Delete();
597 shortOut.AsLinkInfo()?.Delete();
605 hostProjectDir.CreateDirectory();
606 var hostPluginDir = hostProjectDir /
"Plugins" /
Name;
618 Unreal.
WriteJson(hostProject, hostProjectDir /
"HostProject.uproject");
619 sourceFolder.Copy(hostPluginDir);
621 var shortHostProjectDir = hostProjectDir.Shorten();
622 var shortPluginDir = shortHostProjectDir /
"Plugins" /
Name;
631 .
Project(shortHostProjectDir /
"HostProject.uproject")
639 foreach(var platform
in platforms)
641 Log.Information(
"Building UnrealGame binaries from UProject for {0} @ {1}",
Name, platform);
643 .Target(
"UnrealGame", platform,
648 .Apply(CommonProject)
650 if (platform.IsDevelopment)
652 Log.Information(
"Building UnrealEditor binaries for UProject {0} @ {1}",
Name, platform);
654 .Target(
"UnrealEditor", platform, [
UnrealConfig.Development])
655 .Apply(CommonProject)
658 Log.Information(
"Building UnrealGame binaries from UPlugin for {0} @ {1}",
Name, platform);
660 .Target(
"UnrealGame", platform,
667 if (platform.IsDevelopment)
669 Log.Information(
"Building UnrealEditor binaries for UPlugin {0} @ {1}",
Name, platform);
671 .Target(
"UnrealEditor", platform, [
UnrealConfig.Development])
676 hostPluginDir.Copy(outFolder, ExistsPolicy.MergeAndOverwrite);
677 hostProjectDir.DeleteDirectory();
678 _buildOutput = outFolder;
682 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
683 shortHostProjectDir.AsLinkInfo()?.Delete();
685 foreach (var dep
in dependencies)
687 Log.Debug(
"Deleting temporary installed plugin {0}", enginePluginsDir / dep.Name);
688 (enginePluginsDir / dep.Name).ExistingDirectory()?.DeleteDirectory();
701 internal static IEnumerable<string> FilterBuildStepBlock(
this IEnumerable<string>
self,
string name)
703 .TakeUntil(a => a ==
"echo GENERATED BUILD STEPS".AppendNonEmpty(name))
705 self.SkipUntil(a => a ==
"echo GENERATED BUILD STEPS".AppendNonEmpty(name))
707 .SkipUntil(a => a.StartsWith(
"echo GENERATED BUILD STEPS"))
710 internal static IEnumerable<string> StartBuildStepBlock(
this IEnumerable<string>
self,
string name)
711 =>
self.Append(
"echo GENERATED BUILD STEPS".AppendNonEmpty(name));
713 internal static void EnsureDevelopmentPlatforms(
this Dictionary<
UnrealPlatform, List<string>>
self)
717 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.
AbsolutePath GetBuildOutput(IUnrealBuild build, PluginBuildOptions? options=null)
Gets the output folder for packaging this plugin.
void GenerateFilterPluginIni(IUnrealBuild build)
Generate the FilterPlugin.ini file after all components have submitted their explicit files.
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
IEnumerable< UnrealPlugin > GetProjectPluginDependencies(IUnrealBuild build)
Get the project plugin dependencies of this plugin as UnrealPlugin objects.
void AddExplicitPluginFiles(IEnumerable< RelativePath > pluginRelativePaths)
Add explicit plugin files to be listed later in FilterPlugin.ini.
AbsolutePath BuildPlugin(IUnrealBuild 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...
AbsolutePath GetDistributionOutput(IUnrealBuild build, PluginDistributionOptions? options=null)
Gets the output folder for distribution.
static UnrealPlugin Get(AbsolutePath from)
AbsolutePath PluginPath
Path to the .uplugin file.
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...
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 EngineVersion Version(IUnrealBuild build)
Get high-level version of currently used Engine.
static ToolEx AutomationTool(EngineVersion ofVersion)
Prepare invocation for UAT.
static UnrealPlatform GetHostPlatform()
Get the current development platform Nuke.Unreal is ran on.
static ToolEx BuildTool(EngineVersion ofVersion)
Prepare invocation for UBT.
static readonly JsonSerializerSettings JsonReadSettings
Common JsonSerializerSettings for Unreal conventions of JSON format.
static bool Is4(IUnrealBuild build)
Are we working with UE4.
Base interface for build components which require an UnrealBuild main class.
UbtConfig UbtGlobal(UbtConfig _)
UBT arguments to be applied globally for all UBT invocations. Override this function in your main bui...
UatConfig UatGlobal(UatConfig _)
UAT arguments to be applied globally for all UAT invocations. Override this function in your main bui...
AbsolutePath PluginsFolder
Path to the Unreal plugins folder of this project.
ProjectDescriptor ProjectDescriptor
"Immutable" C# representation of the .uproject contents
AbsolutePath GetOutput()
Get an output folder where the targets should store their artifacts. Override this function in your m...
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.