Skip to content

Commit 0fb1229

Browse files
committed
UnrealPlugin.BuildPlugin can deal with plugin dependencies
1 parent d885e0c commit 0fb1229

File tree

1 file changed

+105
-27
lines changed

1 file changed

+105
-27
lines changed

src/Nuke.Unreal/Plugins/UnrealPlugin.cs

Lines changed: 105 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Newtonsoft.Json.Linq;
99
using Nuke.Cola;
1010
using Nuke.Cola.FolderComposition;
11+
using Nuke.Cola.Search;
1112
using Nuke.Common;
1213
using Nuke.Common.IO;
1314
using Nuke.Common.Utilities;
@@ -49,21 +50,22 @@ public record class PluginDistributionOptions(
4950
public enum PluginBuildMethod
5051
{
5152
/// <summary>
52-
/// Use UAT BuildPlugin feature
53+
/// Use vanilla UAT BuildPlugin feature
5354
/// </summary>
5455
UAT,
5556

5657
/// <summary>
57-
/// Nuke.Unreal will use UBT directly in a similar way than UAT does. This is added because
58-
/// UBT has a problem with module rule files if both a project and a plugin contained in
59-
/// that project is passed to it. If you have problems with duplicate types in your module
60-
/// rules, then use this, otherwise use PluginBuildMethod.UAT
58+
/// <para>
59+
/// Nuke.Unreal will use UBT directly in a similar way than UAT does, but with extra
60+
/// features/bugfixes that UAT doesn't have, for example automatically handle plugin
61+
/// dependencies, but in a customizable way. This is the default method.
62+
/// </para>
6163
/// </summary>
6264
UBT
6365
}
6466

6567
/// <summary>
66-
/// Options for packaging plugins for binary distribution with UAT.
68+
/// Options for packaging plugins for binary distribution..
6769
/// </summary>
6870
/// <param name="OutputSubfolder">
6971
/// Relative path to the output folder indicated by `UnrealBuild.GetOutput()`.
@@ -88,10 +90,21 @@ public record class PluginBuildOptions(
8890
RelativePath? OutputSubfolder = null,
8991
AbsolutePath? OutputOverride = null,
9092
bool UseDistributedPlugin = true,
91-
PluginBuildMethod Method = PluginBuildMethod.UAT,
93+
PluginBuildMethod Method = PluginBuildMethod.UBT,
9294
UnrealPlatform[]? Platforms = null
9395
);
9496

97+
/// <summary>
98+
/// Arguments for BuildPlugin
99+
/// </summary>
100+
public record class PluginBuildArguments(
101+
Func<UatConfig, UatConfig>? UatConfig = null,
102+
Func<UbtConfig, UbtConfig>? UbtConfig = null,
103+
PluginBuildOptions? BuildOptions = null,
104+
PluginDistributionOptions? DistOptions = null,
105+
Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? DependencyHandler = null
106+
);
107+
95108
/// <summary>
96109
/// A class encapsulating information and tasks around one Unreal plugin.
97110
/// </summary>
@@ -428,6 +441,26 @@ private bool InjectBinaryPlumbing(out PluginDescriptor result)
428441
return (result.WithFilesExpanded(), outFolder);
429442
}
430443

444+
/// <summary>
445+
/// Get the project plugin dependencies of this plugin as UnrealPlugin objects.
446+
/// </summary>
447+
/// <param name="build"></param>
448+
/// <returns></returns>
449+
IEnumerable<UnrealPlugin> GetProjectPluginDependencies(UnrealBuild build)
450+
=> Descriptor.Plugins?
451+
.Where(p => p.Enabled ?? false)
452+
.Select(p =>
453+
{
454+
var depFIle = build.PluginsFolder.SearchFiles($"**/{p.Name}.uplugin").FirstOrDefault();
455+
return depFIle != null ? Get(depFIle) : null;
456+
})
457+
.Where(p => p != null)
458+
.Select(p => p!)
459+
?? []
460+
;
461+
462+
private AbsolutePath? _buildOutput = null;
463+
431464
/// <summary>
432465
/// Make a prebuilt release of this plugin for end-users. Globally set UAT and UBT arguments
433466
/// are used from the input UnrealBuild
@@ -437,13 +470,15 @@ private bool InjectBinaryPlumbing(out PluginDescriptor result)
437470
/// <param name="ubtConfig">Configurator for UBT (only when UBT method is used)</param>
438471
/// <param name="buildOptions">Optional arguments for packaging</param>
439472
/// <param name="distOptions">Optional arguments for distribution</param>
473+
/// <param name="dependencyHandler">Customize the build option for plugin dependencies</param>
440474
/// <returns>Output folder of the packaged plugin</returns>
441475
public AbsolutePath BuildPlugin(
442476
UnrealBuild build,
443477
Func<UatConfig, UatConfig>? uatConfig = null,
444478
Func<UbtConfig, UbtConfig>? ubtConfig = null,
445479
PluginBuildOptions? buildOptions = null,
446-
PluginDistributionOptions? distOptions = null
480+
PluginDistributionOptions? distOptions = null,
481+
Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? dependencyHandler = null
447482
) {
448483
var outFolder = GetBuildOutput(build, buildOptions);
449484
buildOptions ??= _buildOptionsCache;
@@ -462,6 +497,35 @@ public AbsolutePath BuildPlugin(
462497
hostProjectDir.ExistingDirectory()?.DeleteDirectory();
463498

464499
var platforms = (buildOptions.Platforms ?? []).Union([build.Platform]);
500+
501+
var dependencies = GetProjectPluginDependencies(build).ToList();
502+
var thisArgs = new PluginBuildArguments(
503+
uatConfig,
504+
ubtConfig,
505+
buildOptions,
506+
distOptions,
507+
dependencyHandler
508+
);
509+
510+
foreach (var unbuiltDep in dependencies.Where(d => d._buildOutput == null))
511+
{
512+
Log.Information("Building dependency plugin {0}", unbuiltDep.Name);
513+
var args = dependencyHandler?.Invoke(unbuiltDep, thisArgs) ?? thisArgs;
514+
unbuiltDep.BuildPlugin(
515+
build,
516+
args.UatConfig,
517+
args.UbtConfig,
518+
args.BuildOptions,
519+
args.DistOptions,
520+
args.DependencyHandler
521+
);
522+
}
523+
var enginePluginsDir = build.UnrealEnginePath / "Engine" / "Plugins" / "Marketplace";
524+
foreach (var dep in dependencies)
525+
{
526+
Log.Information("Copying dependency plugin {0} to {1}", dep.Name, enginePluginsDir);
527+
dep._buildOutput.Copy(enginePluginsDir / dep.Name);
528+
}
465529

466530
switch (buildOptions.Method)
467531
{
@@ -484,68 +548,82 @@ public AbsolutePath BuildPlugin(
484548
.Apply(build.UatGlobal)
485549
.Apply(uatConfig)
486550
)("");
551+
_buildOutput = outFolder;
487552
}
488553
catch (Exception) { throw; }
489554
finally
490555
{
491556
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
492557
{
493558
shortSource.DeleteDirectory();
494-
shortOut.DeleteDirectory();
559+
shortOut.AsLinkInfo()?.Delete();
495560
}
496561
}
497562

498563
break;
499564

500565
case PluginBuildMethod.UBT:
566+
501567
hostProjectDir.CreateDirectory();
502568
var hostPluginDir = hostProjectDir / "Plugins" / Name;
503-
(hostProjectDir / "HostProject.uproject").WriteAllText(
504-
$$"""
505-
{ "FileVersion": 3, "Plugins": [ { "Name": "{{Name}}", "Enabled": true } ] }
506-
"""
569+
var hostProject = new ProjectDescriptor(
570+
Plugins: [
571+
new (Name: Name, Enabled: true),
572+
..
573+
dependencies.Select(d => new PluginReferenceDescriptor(
574+
Name: d.Name,
575+
Enabled: true
576+
))
577+
]
507578
);
579+
Unreal.WriteJson(hostProject, hostProjectDir / "HostProject.uproject");
508580
sourceFolder.Copy(hostPluginDir);
509-
var shortPluginDir = hostPluginDir.Shorten();
510581

582+
var shortPluginDir = hostPluginDir.Shorten();
511583
try
512584
{
585+
UbtConfig Common(UbtConfig _) => _
586+
.Plugin(shortPluginDir / PluginPath.Name)
587+
.NoUBTMakefiles()
588+
.NoHotReload()
589+
.Apply(ubtConfig)
590+
.Apply(build.UbtGlobal);
591+
513592
foreach(var platform in platforms)
514593
{
515-
Log.Information("Building UnrealGame binaries for {0}", platform);
594+
Log.Information("Building UnrealGame binaries for {0} @ {1}", Name, platform);
516595
Unreal.BuildTool(build, _ => _
517596
.Target("UnrealGame", platform,
518597
[
519598
UnrealConfig.Development,
520599
UnrealConfig.Shipping
521600
])
522-
.Plugin(shortPluginDir / PluginPath.Name)
523-
.NoUBTMakefiles()
524-
.NoHotReload()
525-
.Apply(ubtConfig)
526-
.Apply(build.UbtGlobal)
601+
.Apply(Common)
527602
)("");
528603
if (platform.IsDevelopment)
529604
{
530-
Log.Information("Building UnrealEditor binaries for {0}", platform);
605+
Log.Information("Building UnrealEditor binaries for {0} @ {1}", Name, platform);
531606
Unreal.BuildTool(build, _ => _
532607
.Target("UnrealEditor", platform, [UnrealConfig.Development])
533-
.Plugin(shortPluginDir / PluginPath.Name)
534-
.NoUBTMakefiles()
535-
.NoHotReload()
536-
.Apply(ubtConfig)
537-
.Apply(build.UbtGlobal)
608+
.Apply(Common)
538609
)("");
539610
}
540611
}
541612
hostPluginDir.Copy(outFolder, ExistsPolicy.MergeAndOverwrite);
542613
hostProjectDir.DeleteDirectory();
614+
_buildOutput = outFolder;
543615
}
544616
catch(Exception) { throw; }
545617
finally
546618
{
547619
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
548-
shortPluginDir.DeleteDirectory();
620+
shortPluginDir.AsLinkInfo()?.Delete();
621+
622+
foreach (var dep in dependencies)
623+
{
624+
Log.Debug("Deleting temporary installed plugin {0}", enginePluginsDir / dep.Name);
625+
(enginePluginsDir / dep.Name).ExistingDirectory()?.DeleteDirectory();
626+
}
549627
}
550628
break;
551629

0 commit comments

Comments
 (0)