88using Newtonsoft . Json . Linq ;
99using Nuke . Cola ;
1010using Nuke . Cola . FolderComposition ;
11+ using Nuke . Cola . Search ;
1112using Nuke . Common ;
1213using Nuke . Common . IO ;
1314using Nuke . Common . Utilities ;
@@ -49,21 +50,22 @@ public record class PluginDistributionOptions(
4950public 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