Keywords: UE4, Building Multiple Plugins, Dependencies, Without Source, Source Removed, Prebuilding, Precompile, Distributing

If want to distribute plugins with source removed using installed engine (in EpicGames Launcher), do steps as shown in following content.

[UE4]Precompile Plugins - Build from Engine Source

GUI

1, New two plugins (Edit -> Plugins -> New Plugin) that named as P1 and P2.

2, Add source for P1 and P2.

We have assumed that Actor1 was in plugin P1, and Actor2 was in plugin P2, P2 has dependencies on P1, but P1 has no dependencies on P2.

Actor1.h

#include "Actor1.h"
#include "Engine.h"

// Sets default values
AActor1::AActor1()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AActor1::BeginPlay()
{
    Super::BeginPlay();
    
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString("Actor1 BeginPlay"));
}

Actor2.h

#include "Actor2.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "Actor1.h"

// Sets default values
AActor2::AActor2()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AActor2::BeginPlay()
{
    Super::BeginPlay();
    
    GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, FString("Actor2 BeginPlay"));

    FTransform Trans;
    AActor1 A1 = GetWorld()->SpawnActor<AActor1>(AActor1::StaticClass(), Trans);
}

3, List P1 in PrivateDependencyModuleNames of Build.cs of P2:

PrivateDependencyModuleNames.AddRange(
    new string[]
    {
        "CoreUObject",
        "Engine",
        "Slate",
        "SlateCore",
        "P1"
        // ... add private dependencies that you statically link with here ...    
    }
    );

4, List plugin P1 in P2.uplugin, then add WhitelistPlatforms in P1.uplugin and P2.uplugin:

P1.uplugin

{
    "FileVersion": 3,
    "Version": 1,
    "VersionName": "1.0",
    "FriendlyName": "P1",
    "Description": "",
    "Category": "Other",
    "CreatedBy": "",
    "CreatedByURL": "",
    "DocsURL": "",
    "MarketplaceURL": "",
    "SupportURL": "",
    "CanContainContent": true,
    "IsBetaVersion": false,
    "IsExperimentalVersion": false,
    "Installed": false,
    "Modules": [
        {
            "Name": "P1",
            "Type": "Runtime",
            "LoadingPhase": "Default",
            "WhitelistPlatforms": [
                "Win64"
            ]
        }
    ]
}

P2.uplugin

{
    "FileVersion": 3,
    "Version": 1,
    "VersionName": "1.0",
    "FriendlyName": "P2",
    "Description": "",
    "Category": "Other",
    "CreatedBy": "",
    "CreatedByURL": "",
    "DocsURL": "",
    "MarketplaceURL": "",
    "SupportURL": "",
    "CanContainContent": true,
    "IsBetaVersion": false,
    "IsExperimentalVersion": false,
    "Installed": false,
    "Modules": [
        {
            "Name": "P2",
            "Type": "Runtime",
            "LoadingPhase": "Default",
            "WhitelistPlatforms": [
                "Win64"
            ]
        }
    ],
    "Plugins": [
        {
            "Name": "P1",
            "Enabled": true
        }
    ]
}

5, Close editor, rebuild project source and start editor.

6, Package plugin P1: Edit -> Plugins -> Project -> P1 -> Package

then close editor and remove TestTD\Plugins\P1, then copy packaged version of P1 into engine plugins directory.

then you can remove private source files:

When restart editor, you will see the packaged plugin P1 in Installed tab of plugins.

Attention:
Packaging project would fail if haven’t packaged plugin.

Following message is the error if haven’t packaged plugin:

UATHelper: Packaging (Windows (64-bit)):   ERROR: Missing precompiled manifest for 'TestPlugin'. This module was most likely not flagged for being included in a precompiled build - set 'PrecompileForTargets = PrecompileTargetsType.Any;' in TestPlugin.build.cs to override.
PackagingResults: Error: Missing precompiled manifest for 'TestPlugin'. This module was most likely not flagged for being included in a precompiled build - set 'PrecompileForTargets = PrecompileTargetsType.Any;' in TestPlugin.build.cs to override.

7, Package plugin P2: Edit -> Plugins -> Project -> P2 -> Package:

then close editor and remove TestTD\Plugins\P2, then copy packaged version of P2 into engine plugins directory.

then you can remove private source files:

8, List P2 in PublicDependencyModuleNames of TestTD.Build.cs:

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class TestTD : ModuleRules
{
    public TestTD(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule" });

        PublicDependencyModuleNames.AddRange(new string[] { "P2" });
    }
}

9, List P1 and P2 in TestTD.uproject

{
    "FileVersion": 3,
    "EngineAssociation": "4.24",
    "Category": "",
    "Description": "",
    "Modules": [
        {
            "Name": "TestTD",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        }
    ],
    "Plugins": [
        {
            "Name": "P1",
            "Enabled": true
        },
        {
            "Name": "P2",
            "Enabled": true
        }
    ]
}

Otherwise you would get error at building:

The game module `TestTD` could not be loaded. There may be an operating system error or the module may not be properly set up.

9, Now you can inlcude plugin P2 headers in your game project:

#include "TestTDGameMode.h"
#include "TestTDPlayerController.h"
#include "TestTDCharacter.h"
#include "UObject/ConstructorHelpers.h"
#include "Actor2.h"

ATestTDGameMode::ATestTDGameMode()
{
    // use our custom PlayerController class
    PlayerControllerClass = ATestTDPlayerController::StaticClass();

    // set default pawn class to our Blueprinted character
    static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/TopDownCPP/Blueprints/TopDownCharacter"));
    if (PlayerPawnBPClass.Class != NULL)
    {
        DefaultPawnClass = PlayerPawnBPClass.Class;
    }
}

void ATestTDGameMode::StartPlay()
{
    Super::StartPlay();

    FTransform Trans;
    AActor2* Pawn = GetWorld()->SpawnActor<AActor2>(AActor2::StaticClass(), Trans);
}

then rebuild project source and start editor, now you can package project:
File -> Package Project -> Windows -> Win64.

CLI

Command:

"D:\Epic Games\UE_4.24\Engine\Binaries\DotNET\AutomationTool.exe" BuildPlugin -Plugin=E:/TestTD/Plugins/MyPlugin/MyPlugin.uplugin -Package=F:/Build/MyPlugin -CreateSubFolder

Source file of BuildPlugin command :

Engine\Source\Programs\AutomationTool\Scripts\BuildPluginCommand.Automation.cs

References

How to package C++ plugin without private source code (only binaries + headers) in 4.20?
https://forums.unrealengine.com/development-discussion/engine-source-github/1521453-how-to-package-c-plugin-without-private-source-code-only-binaries-headers-in-4-20


If a man is to shed the light of the sun upon other men, he must first of all have it within himself. ― Romain Rolland