Nuke.Unreal
Build Unreal apps in Style.
Loading...
Searching...
No Matches
UnrealPlugin.cs
1
2using System;
3using System.Collections.Generic;
4using System.IO;
5using System.Linq;
6using System.Runtime.InteropServices;
7using EverythingSearchClient;
8using Newtonsoft.Json.Linq;
9using Nuke.Cola;
10using Nuke.Cola.FolderComposition;
11using Nuke.Cola.Search;
12using Nuke.Common;
13using Nuke.Common.IO;
14using Nuke.Common.Utilities;
15using Nuke.Common.Utilities.Collections;
18using Serilog;
19
20namespace Nuke.Unreal.Plugins;
21
22/// <summary>
23/// Options for creating a distributable version of this plugin, usually for making it
24/// independent of Nuke.
25/// </summary>
26/// <param name="OutputSubfolder">
27/// Relative path to the output folder indicated by `UnrealBuild.GetOutput()`.
28/// Default is `Plugins/{Name}/Distribution`
29/// </param>
30/// <param name="OutputOverride">
31/// If set, this will disregard both `UnrealBuild.GetOutput()` and `OutputSubfolder`, and use this
32/// as the output of plugin distribution tasks
33/// </param>
34/// <param name="GenerateFilterPluginIni">
35/// Generate a FilterPlugin.ini in the plugin's Config folder, so Unreal tools will not ignore
36/// extra files handled with Nuke.
37/// </param>
38/// <param name="UPluginAssociateEngineVersion">
39/// When set to true, distributing sources of plugins will write engine version to UPlugin file.
40/// </param>
41/// <param name="UPluginIsInstalled">
42/// Mark plugin as Installed when distributing. Default is true
43/// </param>
44/// <param name="UPluginConfig">
45/// Modify plugin descriptor for the distributed plugin after all other configuration is done.
46/// </param>
47public record class PluginDistributionOptions(
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
54);
55
56/// <summary>
57/// Method of packaging a plugin.
58/// </summary>
60{
61 /// <summary>
62 /// Use vanilla UAT BuildPlugin feature
63 /// </summary>
64 UAT,
65
66 /// <summary>
67 /// <para>
68 /// Nuke.Unreal will use UBT directly in a similar way than UAT does, but with extra
69 /// features/bugfixes that UAT doesn't have, for example automatically handle plugin
70 /// dependencies, but in a customizable way. This is the default method.
71 /// </para>
72 /// </summary>
73 UBT
74}
75
76/// <summary>
77/// Options for packaging plugins for binary distribution..
78/// </summary>
79/// <param name="OutputSubfolder">
80/// Relative path to the output folder indicated by `UnrealBuild.GetOutput()`.
81/// Default is `Plugins/{Name}/Build`
82/// </param>
83/// <param name="OutputOverride">
84/// If set, this will disregard both `UnrealBuild.GetOutput()` and `OutputSubfolder`, and use this
85/// as the output of plugin distribution tasks
86/// </param>
87/// <param name="UseDistributedPlugin">
88/// If set to true, first make a distributed copy of this plugin and then package it with UAT
89/// from that.
90/// </param>
91/// <param name="Method">
92/// Select the method of packaging the plugin. If you have problems with duplicate types in your
93/// module rules, then set this to PluginBuildMethod.UBT.
94/// </param>
95/// <param name="Platforms">
96/// Extra platforms to build this plugin for. UnrealBuild.Platform and explicit SupportedTargetPlatforms
97/// is always added to this list.
98/// </param>
99public record class PluginBuildOptions(
100 RelativePath? OutputSubfolder = null,
101 AbsolutePath? OutputOverride = null,
102 bool UseDistributedPlugin = true,
104 UnrealPlatform[]? Platforms = null
105);
106
107/// <summary>
108/// Arguments for BuildPlugin
109/// </summary>
110public record class PluginBuildArguments(
111 Func<UatConfig, UatConfig>? UatConfig = null,
112 Func<UbtConfig, UbtConfig>? UbtConfig = null,
113 PluginBuildOptions? BuildOptions = null,
114 PluginDistributionOptions? DistOptions = null,
115 Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? DependencyHandler = null
116);
117
118/// <summary>
119/// A class encapsulating information and tasks around one Unreal plugin.
120/// </summary>
121public class UnrealPlugin
122{
123 public static readonly Dictionary<AbsolutePath, UnrealPlugin> Instances = [];
124 internal static string RelativePathDistinction<T>(T path)
125 => path!.ToString()!.Trim().Trim('/');
126
127 /// <summary>
128 /// <para>
129 /// Get a `UnrealPlugin` instance from the path to its `.uplugin` file, or a file/folder
130 /// belonging to that plugin (in its subtree of files and folders). If a UnrealPlugin
131 /// doesn't exist yet, one will be created given that the associated plugin exists.
132 /// </para>
133 /// <para>
134 /// For example:
135 /// </para>
136 /// <code>
137 /// // In a local `MyPlugin.nuke.cs`
138 /// UnrealPlugin.Get(this.ScriptFolder()); // Gets or creates an `UnrealPlugin` about `MyPlugin.uplugin`
139 /// </code>
140 /// </summary>
141 /// <remarks>
142 /// Referring to non-existing plugins is a runtime error.
143 /// </remarks>
144 /// <param name="from"></param>
145 /// <returns></returns>
146 public static UnrealPlugin Get(AbsolutePath from)
147 {
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!))
151 {
152 Log.Debug("Handling plugin: {0}", uplugin!.NameWithoutExtension);
153 Instances.Add(uplugin!, new (uplugin!));
154 }
155 return Instances[uplugin!];
156 }
157
158 internal UnrealPlugin(AbsolutePath uplugin)
159 {
160 PluginPath = uplugin;
162 if (FilterPluginIni.FileExists())
163 {
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)
171 ;
172 AddExplicitPluginFiles(inFiles);
173 }
174 }
175
176 /// <summary>
177 /// Path to the `.uplugin` file
178 /// </summary>
179 public AbsolutePath PluginPath { get; private set; }
180
181 /// <summary>
182 /// "Immutable" C# representation of the uplugin file
183 /// </summary>
184 public PluginDescriptor Descriptor { get; private set; }
185
186 /// <summary>
187 /// Path to folder containing the `.uplugin` file
188 /// </summary>
189 public AbsolutePath Folder => PluginPath.Parent;
190
191 public AbsolutePath ConfigFolder => Folder / "Config";
192 public AbsolutePath FilterPluginIni => ConfigFolder / "FilterPlugin.ini";
193
194 /// <summary>
195 /// Short name of the plugin
196 /// </summary>
197 public string Name => PluginPath.NameWithoutExtension;
198
199 private Version? _versionCache = null;
200
201 /// <summary>
202 /// Semantic version of the plugin. Parsed from the `uplugin` file. If set, it will modify the
203 /// `.uplugin` file as well
204 /// </summary>
206 {
207 get => _versionCache ?? (
208 Version.TryParse(Descriptor.VersionName ?? "", out var version)
209 ? version
210 : new()
211 );
212 set
213 {
214 Descriptor = Descriptor with { VersionName = value.ToString(3) };
215 _versionCache = value;
217 }
218 }
219
220 private List<UnixRelativePath> _explicitPluginFiles = [];
221
222 /// <summary>
223 /// Return list of files which may need extra mention for Unreal/Epic tools. This is also
224 /// usually the contents of FilterPlugin.ini
225 /// </summary>
226 public IEnumerable<AbsolutePath> ExplicitPluginFiles => _explicitPluginFiles
227 .Select(f => Folder / f);
228
229 /// <summary>
230 /// Add explicit plugin files to be listed later in FilterPlugin.ini
231 /// </summary>
232 /// <param name="files">
233 /// Absolute paths to the files. Relative paths will be calculated based on plugin root.
234 /// </param>
235 public void AddExplicitPluginFiles(IEnumerable<AbsolutePath> files)
236 => AddExplicitPluginFiles(files.Select(f => Folder.GetRelativePathTo(f)));
237
238 /// <summary>
239 /// Add explicit plugin files to be listed later in FilterPlugin.ini
240 /// </summary>
241 public void AddExplicitPluginFiles(IEnumerable<RelativePath> pluginRelativePaths)
242 => _explicitPluginFiles = [..
243 _explicitPluginFiles
244 .UnionBy(
245 pluginRelativePaths.Select(f => f.ToUnixRelativePath()),
246 RelativePathDistinction
247 )
248 ];
249
250 private PluginDistributionOptions _distributionOptionsCache = new();
251 private RelativePath _defaultDistSubdir => (RelativePath) $"Plugins/{Name}/Distribution";
252
253 /// <summary>
254 /// Gets the output folder for distribution
255 /// </summary>
256 /// <param name="build"></param>
257 /// <param name="options">
258 /// Optional argument to get the output from. If another functionality has cached this
259 /// before, this is not needed.
260 /// </param>
261 public AbsolutePath GetDistributionOutput(IUnrealBuild build, PluginDistributionOptions? options = null)
262 {
263 options ??= _distributionOptionsCache;
264 _distributionOptionsCache = options;
265
266 var outputSubfolder = options.OutputSubfolder ?? _defaultDistSubdir;
267 return options.OutputOverride ?? (build.GetOutput() / outputSubfolder);
268 }
269
270 private PluginBuildOptions _buildOptionsCache = new();
271 private RelativePath _defaultBuildSubdir => (RelativePath) $"Plugins/{Name}/Build";
272
273 /// <summary>
274 /// Gets the output folder for packaging this plugin
275 /// </summary>
276 /// <param name="build"></param>
277 /// <param name="options">
278 /// Optional argument to get the output from. If another functionality has cached this
279 /// before, this is not needed.
280 /// </param>
281 public AbsolutePath GetBuildOutput(IUnrealBuild build, PluginBuildOptions? options = null)
282 {
283 options ??= _buildOptionsCache;
284 _buildOptionsCache = options;
285
286 var outputSubfolder = options.OutputSubfolder ?? _defaultBuildSubdir;
287 return options.OutputOverride ?? (build.GetOutput() / outputSubfolder);
288 }
289
290 /// <summary>
291 /// Generate the `FilterPlugin.ini` file after all components have submitted their
292 /// explicit files.
293 /// </summary>
295 {
296 AddDefaultExplicitPluginFiles(build);
297
298 var configPath = FilterPluginIni;
299 Log.Debug("Generating FilterPlugin.ini: {0}", configPath);
300
301 var lines = _explicitPluginFiles
302 .Select(f => ("/" + f.ToString()).Replace("//", "/"))
303 .Distinct()
304 ;
305 if (!lines.IsEmpty())
306 configPath.WriteAllLines(lines.Prepend("[FilterPlugin]"));
307 else
308 Log.Debug("There were no files to list in FilterPlugin.ini");
309 }
310
311 private static readonly ExportManifest _filterExportManifest = new()
312 {
313 Copy = { new() { File = "Config/**/*.ini" , Not = {"FilterPlugin.ini"}} },
314 Not = {
315 "PluginFiles.yml",
316 "*.nuke.cs",
317 "*.nuke.csx",
318 "Nuke.Targets",
319 ".Nuke",
320 ".git",
321 ".gitignore",
322 ".p4ignore",
323 },
324 Use = {
325 new() { File = "**/PluginFiles.yml" }
326 }
327 };
328
329 private IEnumerable<AbsolutePath> GetDefaultExplicitPluginFiles(IUnrealBuild build)
330 => build.ImportFolder((Folder, Folder.Parent / "ShouldntExist", "PluginFiles.yml"), new(
331 Pretend: true,
332 UseSubfolder: false,
333 ForceCopyLinks: true,
334 AddToMain: [_filterExportManifest]
335 )).WithFilesExpanded().Select(f => f.From);
336
337 private void AddDefaultExplicitPluginFiles(IUnrealBuild build)
338 => AddExplicitPluginFiles(GetDefaultExplicitPluginFiles(build));
339
340 private bool InjectBinaryPlumbing(PluginDescriptor descriptor, out PluginDescriptor result)
341 {
342 var plumbingRootRelative = (RelativePath) "Source" / "ThirdParty" / "BinaryPlumbing";
343 var plumbingRoot = Folder / plumbingRootRelative;
344 if (!plumbingRoot.DirectoryExists())
345 {
346 result = descriptor;
347 return false;
348 }
349
350 Log.Information("{0} seemingly requires binary plumbing", Name);
351 Log.Debug("Because {0} exists", plumbingRoot);
352
353 result = descriptor with {
354 PreBuildSteps = descriptor.PreBuildSteps?.ToDictionary() ?? []
355 };
356 result.PreBuildSteps.EnsureDevelopmentPlatforms();
357
358 foreach (var platform in UnrealPlatform.DevelopmentPlatforms)
359 {
360 result.PreBuildSteps[platform] = [..
361 result.PreBuildSteps[platform]
362 .FilterBuildStepBlock("BinaryPlumbing")
363 .StartBuildStepBlock("BinaryPlumbing"),
364 $"echo Plumbing runtime dependencies for {Name} plugin",
365 ];
366 }
367
368 foreach (var subdir in plumbingRoot.GetDirectories())
369 {
370 Log.Debug("Plumbing from {0}", subdir);
371
372 result.PreBuildSteps[UnrealPlatform.Win64].AddRange([
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%)"
376 ]);
377
378 result.PreBuildSteps[UnrealPlatform.Linux].AddRange([
379 $"echo Copying {subdir.Name} runtime dependencies",
380 $"cp -vnpR \"$(PluginDir)/{plumbingRootRelative.ToUnixRelativePath()}/{subdir.Name}\" \"$(PluginDir)/Binaries\"",
381 "chmod -R +x \"$(PluginDir)/Binaries\""
382 ]);
383
384 result.PreBuildSteps[UnrealPlatform.Mac].AddRange([
385 $"echo Copying {subdir.Name} runtime dependencies",
386 $"cp -vnpR \"$(PluginDir)/{plumbingRootRelative.ToUnixRelativePath()}/{subdir.Name}\" \"$(PluginDir)/Binaries\"",
387 "chmod -R +x \"$(PluginDir)/Binaries\""
388 ]);
389 }
390
391 return true;
392 }
393
394 /// <summary>
395 /// Create a copy of this plugin which can be distributed to other developers or other tools
396 /// who shouldn't require extra non-unreal related steps to work with it.
397 /// </summary>
398 /// <param name="build"></param>
399 /// <param name="options">Optional arguments for distribution</param>
400 /// <param name="pretend">
401 /// Do not have side effects on files, just return a list of files which may be affected
402 /// by this operation.
403 /// </param>
404 /// <returns>
405 /// <list type="bullet">
406 /// <item>result: List of files which has been copied</item>
407 /// <item>output: Output folder of distribution</item>
408 /// </list>
409 /// </returns>
410 public (IEnumerable<ImportedItem> result, AbsolutePath output) DistributeSource(
411 IUnrealBuild build,
412 PluginDistributionOptions? options = null,
413 bool pretend = false
414 ) {
415 var outFolder = GetDistributionOutput(build, options);
416 options ??= _distributionOptionsCache;
417
418 if (options.GenerateFilterPluginIni && !pretend)
420
421 var result = build.ImportFolder((Folder, outFolder, "PluginFiles.yml"), new(
422 UseSubfolder: false,
423 ForceCopyLinks: true,
424 Pretend: pretend,
425 AddToMain: [_filterExportManifest, new()
426 {
427 Copy = {
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" }},
434 },
435 }]
436 ));
437
438 var outUPlugin = outFolder / PluginPath.Name;
439
440 var descriptor = Descriptor with {};
441
442 if (!pretend && options.UPluginAssociateEngineVersion)
443 {
444 descriptor = descriptor with { EngineVersion = Unreal.Version(build).VersionMinor + ".0" };
445 Unreal.WriteJson(descriptor, outUPlugin);
446 }
447
448 if (!pretend && InjectBinaryPlumbing(descriptor, out descriptor))
449 {
450 Unreal.WriteJson(descriptor, outUPlugin);
451 }
452
453 if (!pretend && options.UPluginIsInstalled)
454 {
455 descriptor = descriptor with { Installed = true };
456 Unreal.WriteJson(descriptor, outUPlugin);
457 }
458
459 if (!pretend && options.UPluginConfig != null)
460 {
461 descriptor = options.UPluginConfig(descriptor);
462 Unreal.WriteJson(descriptor, outUPlugin);
463 }
464
465 return (result.WithFilesExpanded(), outFolder);
466 }
467
468 /// <summary>
469 /// Get the project plugin dependencies of this plugin as UnrealPlugin objects.
470 /// </summary>
471 /// <param name="build"></param>
472 /// <returns></returns>
473 IEnumerable<UnrealPlugin> GetProjectPluginDependencies(IUnrealBuild build)
474 => Descriptor.Plugins?
475 .Where(p => p.Enabled ?? false)
476 .Select(p =>
477 {
478 var depFIle = build.PluginsFolder.SearchFiles($"**/{p.Name}.uplugin").FirstOrDefault();
479 return depFIle != null ? Get(depFIle) : null;
480 })
481 .Where(p => p != null)
482 .Select(p => p!)
483 ?? []
484 ;
485
486 private AbsolutePath? _buildOutput = null;
487
488 /// <summary>
489 /// Make a prebuilt release of this plugin for end-users. Globally set UAT and UBT arguments
490 /// are used from the input IUnrealBuild
491 /// </summary>
492 /// <param name="build"></param>
493 /// <param name="uatConfig">Configurator for UAT</param>
494 /// <param name="ubtConfig">Configurator for UBT (only when UBT method is used)</param>
495 /// <param name="buildOptions">Optional arguments for packaging</param>
496 /// <param name="distOptions">Optional arguments for distribution</param>
497 /// <param name="dependencyHandler">Customize the build option for plugin dependencies</param>
498 /// <returns>Output folder of the packaged plugin</returns>
499 public AbsolutePath BuildPlugin(
500 IUnrealBuild build,
501 Func<UatConfig, UatConfig>? uatConfig = null,
502 Func<UbtConfig, UbtConfig>? ubtConfig = null,
503 PluginBuildOptions? buildOptions = null,
504 PluginDistributionOptions? distOptions = null,
505 Func<UnrealPlugin, PluginBuildArguments, PluginBuildArguments?>? dependencyHandler = null
506 ) {
507 var outFolder = GetBuildOutput(build, buildOptions);
508 buildOptions ??= _buildOptionsCache;
509 uatConfig ??= _ => _;
510 ubtConfig ??= _ => _;
511
512 var sourceFolder = Folder;
513 if (buildOptions.UseDistributedPlugin || buildOptions.Method == PluginBuildMethod.UBT)
514 {
515 var (_, distFolder) = DistributeSource(build, distOptions);
516 sourceFolder = distFolder;
517 }
518
519 var hostProjectDir = build.GetOutput() / "Plugins" / Name / "HostProject";
520 outFolder.CreateOrCleanDirectory();
521 hostProjectDir.ExistingDirectory()?.DeleteDirectory();
522
523 var platforms = (buildOptions.Platforms ?? [])
524 .Union([Unreal.GetHostPlatform()])
525 .Union(Descriptor.SupportedTargetPlatforms ?? [])
526 .ToList()
527 ;
528
529 foreach (var platform in platforms)
530 {
531 var sdk = platform.GetSdk();
532 if (sdk != null)
533 {
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();
536 }
537 }
538
539 var dependencies = GetProjectPluginDependencies(build).ToList();
540 var thisArgs = new PluginBuildArguments(
541 uatConfig,
542 ubtConfig,
543 buildOptions,
544 distOptions,
545 dependencyHandler
546 );
547
548 foreach (var unbuiltDep in dependencies.Where(d => d._buildOutput == null))
549 {
550 Log.Information("Building dependency plugin {0}", unbuiltDep.Name);
551 var args = dependencyHandler?.Invoke(unbuiltDep, thisArgs) ?? thisArgs;
552 unbuiltDep.BuildPlugin(
553 build,
554 args.UatConfig,
555 args.UbtConfig,
556 args.BuildOptions,
557 args.DistOptions,
558 args.DependencyHandler
559 );
560 }
561 var enginePluginsDir = build.UnrealEnginePath / "Engine" / "Plugins" / "Marketplace";
562 foreach (var dep in dependencies)
563 {
564 Log.Information("Copying dependency plugin {0} to {1}", dep.Name, enginePluginsDir);
565 dep._buildOutput.Copy(enginePluginsDir / dep.Name);
566 }
567
568 switch (buildOptions.Method)
569 {
570
571 case PluginBuildMethod.UAT:
572 var shortSource = sourceFolder.Shorten();
573 var shortOut = outFolder.Shorten();
574 try
575 {
576 Unreal.AutomationTool(build, _ => _
577 .BuildPlugin(_ => _
578 .Plugin(shortSource / PluginPath.Name)
579 .Package(shortOut)
580 .TargetPlatforms(string.Join('+', platforms))
581 .If(Unreal.Is4(build), _ => _
582 .VS2019()
583 )
584 .Unversioned()
585 )
586 .Apply(build.UatGlobal)
587 .Apply(uatConfig)
588 )();
589 _buildOutput = outFolder;
590 }
591 catch (Exception) { throw; }
592 finally
593 {
594 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
595 {
596 shortSource.AsLinkInfo()?.Delete();
597 shortOut.AsLinkInfo()?.Delete();
598 }
599 }
600
601 break;
602
603 case PluginBuildMethod.UBT:
604
605 hostProjectDir.CreateDirectory();
606 var hostPluginDir = hostProjectDir / "Plugins" / Name;
607 var hostProject = new ProjectDescriptor(
608 EngineAssociation: build.ProjectDescriptor.EngineAssociation,
609 Plugins: [
610 new (Name: Name, Enabled: true),
611 ..
612 dependencies.Select(d => new PluginReferenceDescriptor(
613 Name: d.Name,
614 Enabled: true
615 ))
616 ]
617 );
618 Unreal.WriteJson(hostProject, hostProjectDir / "HostProject.uproject");
619 sourceFolder.Copy(hostPluginDir);
620
621 var shortHostProjectDir = hostProjectDir.Shorten();
622 var shortPluginDir = shortHostProjectDir / "Plugins" / Name;
623 try
624 {
625 UbtConfig Common(UbtConfig _) => _
626 .NoHotReload()
627 .Apply(build.UbtGlobal)
628 .Apply(ubtConfig)
629 ;
630 UbtConfig CommonProject(UbtConfig _) => _
631 .Project(shortHostProjectDir / "HostProject.uproject")
632 .Apply(Common)
633 ;
634 UbtConfig CommonPlugin(UbtConfig _) => _
635 .Plugin(shortPluginDir / PluginPath.Name)
636 .Apply(Common)
637 ;
638
639 foreach(var platform in platforms)
640 {
641 Log.Information("Building UnrealGame binaries from UProject for {0} @ {1}", Name, platform);
642 Unreal.BuildTool(build, _ => _
643 .Target("UnrealGame", platform,
644 [
645 UnrealConfig.Development,
646 UnrealConfig.Shipping
647 ])
648 .Apply(CommonProject)
649 )();
650 if (platform.IsDevelopment)
651 {
652 Log.Information("Building UnrealEditor binaries for UProject {0} @ {1}", Name, platform);
653 Unreal.BuildTool(build, _ => _
654 .Target("UnrealEditor", platform, [UnrealConfig.Development])
655 .Apply(CommonProject)
656 )();
657 }
658 Log.Information("Building UnrealGame binaries from UPlugin for {0} @ {1}", Name, platform);
659 Unreal.BuildTool(build, _ => _
660 .Target("UnrealGame", platform,
661 [
662 UnrealConfig.Development,
663 UnrealConfig.Shipping
664 ])
665 .Apply(CommonPlugin)
666 )();
667 if (platform.IsDevelopment)
668 {
669 Log.Information("Building UnrealEditor binaries for UPlugin {0} @ {1}", Name, platform);
670 Unreal.BuildTool(build, _ => _
671 .Target("UnrealEditor", platform, [UnrealConfig.Development])
672 .Apply(CommonPlugin)
673 )();
674 }
675 }
676 hostPluginDir.Copy(outFolder, ExistsPolicy.MergeAndOverwrite);
677 hostProjectDir.DeleteDirectory();
678 _buildOutput = outFolder;
679 }
680 finally
681 {
682 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
683 shortHostProjectDir.AsLinkInfo()?.Delete();
684
685 foreach (var dep in dependencies)
686 {
687 Log.Debug("Deleting temporary installed plugin {0}", enginePluginsDir / dep.Name);
688 (enginePluginsDir / dep.Name).ExistingDirectory()?.DeleteDirectory();
689 }
690 }
691 break;
692
693 }
694
695 return outFolder;
696 }
697}
698
699public static class UnrealPluginExtensions
700{
701 internal static IEnumerable<string> FilterBuildStepBlock(this IEnumerable<string> self, string name)
702 => self
703 .TakeUntil(a => a == "echo GENERATED BUILD STEPS".AppendNonEmpty(name))
704 .Concat(
705 self.SkipUntil(a => a == "echo GENERATED BUILD STEPS".AppendNonEmpty(name))
706 .Skip(1)
707 .SkipUntil(a => a.StartsWith("echo GENERATED BUILD STEPS"))
708 );
709
710 internal static IEnumerable<string> StartBuildStepBlock(this IEnumerable<string> self, string name)
711 => self.Append("echo GENERATED BUILD STEPS".AppendNonEmpty(name));
712
713 internal static void EnsureDevelopmentPlatforms(this Dictionary<UnrealPlatform, List<string>> self)
714 {
715 foreach (var platform in UnrealPlatform.DevelopmentPlatforms)
716 {
717 if (!self.ContainsKey(platform)) self.Add(platform, []);
718 }
719 }
720
721 public static UbtConfig StrictIncludes(this UbtConfig _) => _
722 .NoPCH()
723 .NoSharedPCH()
724 .DisableUnity();
725}
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...
Unreal Automation Tool is a vast collection of scripts solving all aspects of deploying a program mad...
Definition UatConfig.cs:13
virtual UbtConfig NoSharedPCH(bool present=true)
Enables "Shared PCHs", a feature which significantly speeds up compile times by attempting to share c...
virtual UbtConfig NoHotReload(HotReloadMode? val=null)
Whether to perform hot reload for this target.
virtual UbtConfig DisableUnity(bool present=true)
Whether to unify C++ code into larger files for faster compilation.
virtual UbtConfig Plugin(object val=null)
Foreign plugin to compile against this target.
virtual UbtConfig NoPCH(bool present=true)
Whether PCH files should be used.
Unreal Build Tool defines the Unreal project structure and provides unified source building utilities...
Definition UbtConfig.cs:13
UbtConfig Project(string project, bool withKey=false)
Specify Project file without -Project
Definition UbtConfig.cs:20
Build configurations UBT supports.
High level representation of common platforms supported by Unreal Engine (NDA ones excluded) and extr...
static readonly UnrealPlatform Win64
Any platform name containing 'Windows' is also mapped to this platform.
static readonly UnrealPlatform Mac
Any platform name containing 'Mac' is also mapped to this platform.
static readonly List< UnrealPlatform > DevelopmentPlatforms
List of platforms which can support the Unreal Editor.
A collection of utilities around basic functions regarding the environment of the Engine we're workin...
Definition Unreal.cs:24
static void WriteJson(object input, AbsolutePath path)
Write data in JSON with Unreal conventions of JSON format.
Definition Unreal.cs:82
static EngineVersion Version(IUnrealBuild build)
Get high-level version of currently used Engine.
static ToolEx AutomationTool(EngineVersion ofVersion)
Prepare invocation for UAT.
Definition Unreal.cs:281
static UnrealPlatform GetHostPlatform()
Get the current development platform Nuke.Unreal is ran on.
static ToolEx BuildTool(EngineVersion ofVersion)
Prepare invocation for UBT.
Definition Unreal.cs:231
static readonly JsonSerializerSettings JsonReadSettings
Common JsonSerializerSettings for Unreal conventions of JSON format.
Definition Unreal.cs:69
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.