Creating Plugin with Optional Dependencies

Initial Question:
Is there a method to produce a plugin which compiles correctly in an empty project but can also contain additional logic which is compiled and activated when built in a project with another plugin enabled?

Use Case:
My plugin should have behaviour that only applies if it’s being used in a project built for the SteamVR plugin, and have different behaviour that applies if it’s being used in a project built for the “Oculus VR” plugin. My plugin’s C++ code must be able to call functions that are declared/defined in the SteamVR/Oculus VR plugins.

A few ideas:
Plugin Descriptor File - I’m aware that you can flag an entire plugin as having a required dependency on another plugin, but what we’re looking for is more an ability to flag a specific module inside that plugin as depending on another plugin, and if that dependency isn’t found, skip that module.
Precompile Defines - This is likely a required part of the solution to enable some plugin-specific code to become inactive when its dependency isn’t found. Not sure where to start with manipulating these though.
Reflection - Should work for this type of thing but not sure of the intricacies with Unreal’s build process. Also a bummer due to the lack of compile-time safety and potential performance hit(?).

Let me know if you’ve run into this problem before and how you went about solving it.

Thanks!
Ben

We have an open feature request for being able to check whether plugins are enabled from .build.cs files. It’s a little tricky though; we don’t want to allow you to modify build settings in response to it, because that would break the installed engine build and plugin marketplace, where everything is already precompiled. So it would probably have to be a separate module that’s conditionally enabled and dynamically loaded at runtime via settings in the plugin file.

Just wanted to second this request. I just finished writing a plugin (B) that I’d like to make one of my other plugins (A) use if it is present. Unfortunately I haven’t been able to find a way to get it to work.

I was hoping that if I included both plugins in the project I could use an #ifdef to conditionally compile the dependent code in the first plugin (A) based on a PublicDefinition in the other plugin (B), but it doesn’t seem to be possible for whatever reason (I’m guessing because they are different modules). I even tried setting the LoadingPhase for plugin (B) to PreDefault to make sure it is loaded first but no dice.

I can definitely see how this would be next to impossible for engine plugins since they are compiled separately and not recompiled when compiling the project, but it seems like it would be fairly straight-forward for project plugins.

I think for now I will just add a line to the README for plugin (A) to tell the user to add the PublicDefinition manually to plugin (A)'s Build.cs, but that is obviously less than ideal.

I would be pretty happy just to be able to do this with project plugins, but it would really be ideal if it was somehow possible for engine plugins as well.

By the way…this is trivial to do in Unity. Obviously it’s totally different worlds since there is no such thing as an engine plugin in Unity and everything in a project is one big compilation unit, but it’s a shame that Unity can do something that Unreal can’t ;p

Hi [USER=“1345”]Ben Marsh[/USER], thanks for the info, it helped me along significantly.

I was able to explore this route but did get some mixed signals so I wanted to check in:

The plugin we want optional module activation with (OculusVR) can’t be declared in the “Plugins” field since we don’t want it forced on. This leads to warnings on compile/packaging:

Triggering this warning is definitely sub-optimal for our users but doesn’t seem to actually indicate any underlying problems as I’m able to call LoadModule at runtime as needed and have the additional functionality kick in from there.

So my question is: will there be/have there been any changes to the system to better handle this scenario or at least some confirmation that the current approach will continue to be supported? Can work with the current situation (especially with a way to remove/suppress the warning) but would like an understanding of where it’s headed.

Cheers

So this still seems to be an issue? There’s the “Optional” field for plugin dependencies, but that seems to be ignored for the purposes of the warning?

For a concrete example, we have a plugin with optional features which use the editor’s Python scripting plugin. This is all editor-only and completely optional, but obviously that warning gets spat out regardless.

I have the exact same use case as OP where I have 2 different projects for SteamVR and Oculus, then I have preprocessor guards in C++ to enable the plugin-specific code only when that plugin is enabled.

I haven’t found a good way to conditionally pass those pre-processor defines though.

Just to add some code snippets here, so this is at least how I got the preprocessor macro in, the warning mentioned above still persists

        string PluginsPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "../../.."));
        bool bSRanipalPlugin = Directory.Exists(Path.Combine(PluginsPath, "SRanipal"));
        if(bSRanipalPlugin)
        {
            PrivateDefinitions.Add("WITH_SRANIPAL");
            PrivateDependencyModuleNames.AddRange(new string[] {"SRanipal", "SRanipalEye" });
        }

And this also needs a

using System.IO;

at the top of the SomePlugin.Build.cs file

That approach didn’t work for me:

UPROPERTY must not be inside preprocessor blocks, except for WITH_EDITORONLY_DATA

if (!Target.EnablePlugins.Contains(“WidgetStudio”)) return;
PrivateDefinitions.Add(“WITH_WIDGET_STUDIO”);
PublicDependencyModuleNames.AddRange(new {“WidgetStudioRuntime”});

I’m not sure if the condition will work, but the compiler stopped me before i could find out

If anyone is still trying to get this to work, here’s how I’ve done it.

In my uplugin file I’ve added:

	{
		"Name": "ExternalPlugin",
		"Enabled": false,
		"Optional": true
	}

to “Plugins”. This gets rid of the warning that your module depends on a module from a plugin not on your “Plugins” list.

Then, also in my uplugin file, I’ve added:

	{
	  "Name": "MyDependentModule",
	  "Type": "Runtime",
	  "LoadingPhase": "None"
	}

to “Modules”. This prevents the module from being automatically loaded, preventing the Editor from starting.

Finally, in my main Runtime module’s StartupModule function I’ve added:

		auto& ModuleManager = FModuleManager::Get();
		if (ModuleManager.IsModuleLoaded("ExternalModule"))
		{
			ModuleManager.LoadModule("MyDependentModule");
		}
		else
		{
			ModuleManager.OnModulesChanged().AddLambda([&ModuleManager](FName Name, EModuleChangeReason Reason)
			{
				if (Name == "ExternalModule" && Reason == EModuleChangeReason::ModuleLoaded)
				{
					ModuleManager.LoadModule("MyDependentModule");
				}
			});
		}

to load my dependent module only when it’s safe to do so.

I just implemented this, so I may still run into problems, but so far it seems to work ok for both Modular builds and, surprisingly, Monolithic builds too.

I’ll report back if I find any problems during testing.

Hope this helps!

EDIT: I spoke too soon. The warning message remains, and in Monolithic builds you’re still dragging in your module’s code, along with whatever dependencies from the external module it’s got, which is not optimal.

I, too, was intrigued by that Optional flag in the Plugin settings. I wasn’t able to locate any documentation for it, but I did track it down in the source code and it was, luckily, documented there:

/** Whether this plugin is optional, and the game should silently ignore it not being present */
bool bOptional;

I haven’t looked deeper (to actual usage) but my understanding from that comment is that it doesn’t have anything to do with build-time handling and is, rather, all about what the game should do if it isn’t found at runtime.

It seems that the only way to “cleanly” create optional dependencies is to break out the dependent code into another Plugin that is itself optional and has dependencies on the original Plugin from which it came and the other Plugin that may or may not be loaded.


[EDIT] It appears that the field also had a helpful tooltip (though I find the comment to be more clear):