Files
UnrealEngineUWP/Engine/Source/Runtime/Projects/Private/PluginManager.cpp
Simon Tourangeau d88237d27d Copying //UE4/Dev-Enterprise to //UE4/Dev-Main (Source: //UE4/Dev-Enterprise @ 3490213)
#lockdown Nick.Penwarden
#rb JeanMichel.Dignard

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3471737 on 2017/06/02 by Martin.Sevigny

	Changed Statistics panel export to CSV to output unformatted numeric values (no separation, no units). This allows Excel and Sheets to recognize the data a numbers so it's possible to properly sort and do arithmetic. Previous format was being recognized as strings.

Change 3472839 on 2017/06/03 by JeanMichel.Dignard

	DatasmithImporter plugin
	- Used to import .mus files into UE

	DatasmithExporter
	- A DCC agnostic library to export to the .mus format

	DatasmithCore
	- A shared Datasmith library used by DatasmithImporter and DatasmithExporter

	ThirdParty
	- Added FreeImage and Itoo as Enterprise third party libraries

	Other
	- Removed an incorrect struct Rect forward declaration in IntRect.h
	- Added MinimalAPI to MaterialExpression Desaturation and Rotator

Change 3482313 on 2017/06/09 by JeanMichel.Dignard

	- Added Algo::IntroSort that performs an introspective sort. Starts with quick sort and switches to heap sort when the iteration depth is too big.
	- Added TReversePredicate to invoke Predicate(B, A) when calling algos with a predicate.
	- Moved the heapify and heap sort code that was inside Array to separate Algo files.
	- Used IntroSort in LayoutUV because Sort can be problematic when importing big meshes.

Change 3483881 on 2017/06/10 by JeanMichel.Dignard

	UBT
	- Added an Enterprise flag in the uproject file to specify that a project is using the enterprise plugins and modules.
	- A project will only depend on the enterprise assembly if it has the enterprise flag.
	- Enterprise plugins are only loaded for enterprise projects.
	- Added validation that Engine modules don't depend on Enterprise modules and that Enterprise modules don't depend on Game modules.
	- Fixed an issue where enterprise modules would end up in the game project binaries folder.

Change 3485448 on 2017/06/12 by JeanMichel.Dignard

	- Adds DatasmithMaxExporter
	- Adds the DatasmithExport sample
	- Removes dllmain from tbbmalloc to fix issues with Programs outputting dlls.

[CL 3490257 by Simon Tourangeau in Main branch]
2017-06-14 16:17:46 -04:00

1064 lines
36 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "PluginManager.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "HAL/PlatformFilemanager.h"
#include "HAL/FileManager.h"
#include "Misc/MessageDialog.h"
#include "Misc/CommandLine.h"
#include "Misc/Paths.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/ScopedSlowTask.h"
#include "Misc/CoreDelegates.h"
#include "Misc/App.h"
#include "Misc/EngineVersion.h"
#include "Misc/FileHelper.h"
#include "ProjectDescriptor.h"
#include "Interfaces/IProjectManager.h"
#include "Modules/ModuleManager.h"
#include "ProjectManager.h"
DEFINE_LOG_CATEGORY_STATIC( LogPluginManager, Log, All );
#define LOCTEXT_NAMESPACE "PluginManager"
namespace PluginSystemDefs
{
/** File extension of plugin descriptor files.
NOTE: This constant exists in UnrealBuildTool code as well. */
static const TCHAR PluginDescriptorFileExtension[] = TEXT( ".uplugin" );
/**
* Parsing the command line and loads any foreign plugins that were
* specified using the -PLUGIN= command.
*
* @param CommandLine The commandline used to launch the editor.
* @param SearchPathsOut
* @return The number of plugins that were specified using the -PLUGIN param.
*/
static int32 GetAdditionalPluginPaths(TSet<FString>& PluginPathsOut)
{
const TCHAR* SwitchStr = TEXT("PLUGIN=");
const int32 SwitchLen = FCString::Strlen(SwitchStr);
int32 PluginCount = 0;
const TCHAR* SearchStr = FCommandLine::Get();
do
{
FString PluginPath;
SearchStr = FCString::Strifind(SearchStr, SwitchStr);
if (FParse::Value(SearchStr, SwitchStr, PluginPath))
{
FString PluginDir = FPaths::GetPath(PluginPath);
PluginPathsOut.Add(PluginDir);
++PluginCount;
SearchStr += SwitchLen + PluginPath.Len();
}
else
{
break;
}
} while (SearchStr != nullptr);
#if IS_PROGRAM
// For programs that have the project dir set, look for plugins under the project directory
const FProjectDescriptor *Project = IProjectManager::Get().GetCurrentProject();
if (Project != nullptr)
{
PluginPathsOut.Add(FPaths::GetPath(FPaths::GetProjectFilePath()) / TEXT("Plugins"));
}
#endif
return PluginCount;
}
}
FPlugin::FPlugin(const FString& InFileName, const FPluginDescriptor& InDescriptor, EPluginLoadedFrom InLoadedFrom)
: Name(FPaths::GetBaseFilename(InFileName))
, FileName(InFileName)
, Descriptor(InDescriptor)
, LoadedFrom(InLoadedFrom)
, bEnabled(false)
{
}
FPlugin::~FPlugin()
{
}
FString FPlugin::GetName() const
{
return Name;
}
FString FPlugin::GetDescriptorFileName() const
{
return FileName;
}
FString FPlugin::GetBaseDir() const
{
return FPaths::GetPath(FileName);
}
FString FPlugin::GetContentDir() const
{
return FPaths::GetPath(FileName) / TEXT("Content");
}
FString FPlugin::GetMountedAssetPath() const
{
return FString::Printf(TEXT("/%s/"), *Name);
}
bool FPlugin::IsEnabled() const
{
return bEnabled;
}
bool FPlugin::IsHidden() const
{
return Descriptor.bIsHidden;
}
bool FPlugin::CanContainContent() const
{
return Descriptor.bCanContainContent;
}
EPluginLoadedFrom FPlugin::GetLoadedFrom() const
{
return LoadedFrom;
}
const FPluginDescriptor& FPlugin::GetDescriptor() const
{
return Descriptor;
}
bool FPlugin::UpdateDescriptor(const FPluginDescriptor& NewDescriptor, FText& OutFailReason)
{
if(!NewDescriptor.Save(FileName, LoadedFrom == EPluginLoadedFrom::GameProject, OutFailReason))
{
return false;
}
Descriptor = NewDescriptor;
return true;
}
FPluginManager::FPluginManager()
: bHaveConfiguredEnabledPlugins(false)
, bHaveAllRequiredPlugins(false)
{
DiscoverAllPlugins();
}
FPluginManager::~FPluginManager()
{
// NOTE: All plugins and modules should be cleaned up or abandoned by this point
// @todo plugin: Really, we should "reboot" module manager's unloading code so that it remembers at which startup phase
// modules were loaded in, so that we can shut groups of modules down (in reverse-load order) at the various counterpart
// shutdown phases. This will fix issues where modules that are loaded after game modules are shutdown AFTER many engine
// systems are already killed (like GConfig.) Currently the only workaround is to listen to global exit events, or to
// explicitly unload your module somewhere. We should be able to handle most cases automatically though!
}
void FPluginManager::RefreshPluginsList()
{
// Read a new list of all plugins
TMap<FString, TSharedRef<FPlugin>> NewPlugins;
ReadAllPlugins(NewPlugins, PluginDiscoveryPaths);
// Build a list of filenames for plugins which are enabled, and remove the rest
TArray<FString> EnabledPluginFileNames;
for(TMap<FString, TSharedRef<FPlugin>>::TIterator Iter(AllPlugins); Iter; ++Iter)
{
const TSharedRef<FPlugin>& Plugin = Iter.Value();
if(Plugin->bEnabled)
{
EnabledPluginFileNames.Add(Plugin->FileName);
}
else
{
Iter.RemoveCurrent();
}
}
// Add all the plugins which aren't already enabled
for(const TPair<FString, TSharedRef<FPlugin>>& NewPluginPair: NewPlugins)
{
const TSharedRef<FPlugin>& NewPlugin = NewPluginPair.Value;
if(!EnabledPluginFileNames.Contains(NewPlugin->FileName))
{
AllPlugins.Add(NewPlugin->GetName(), NewPlugin);
}
}
}
void FPluginManager::DiscoverAllPlugins()
{
ensure( AllPlugins.Num() == 0 ); // Should not have already been initialized!
PluginSystemDefs::GetAdditionalPluginPaths(PluginDiscoveryPaths);
ReadAllPlugins(AllPlugins, PluginDiscoveryPaths);
}
void FPluginManager::ReadAllPlugins(TMap<FString, TSharedRef<FPlugin>>& Plugins, const TSet<FString>& ExtraSearchPaths)
{
#if (WITH_ENGINE && !IS_PROGRAM) || WITH_PLUGIN_SUPPORT
// Find "built-in" plugins. That is, plugins situated right within the Engine directory.
ReadPluginsInDirectory(FPaths::EnginePluginsDir(), EPluginLoadedFrom::Engine, Plugins);
// Find plugins in the game project directory (<MyGameProject>/Plugins). If there are any engine plugins matching the name of a game plugin,
// assume that the game plugin version is preferred.
if( FApp::HasGameName() )
{
ReadPluginsInDirectory(FPaths::GamePluginsDir(), EPluginLoadedFrom::GameProject, Plugins);
}
const FProjectDescriptor* Project = IProjectManager::Get().GetCurrentProject();
if (Project != nullptr)
{
// If they have a list of additional directories to check, add those plugins too
for (const FString& Dir : Project->GetAdditionalPluginDirectories())
{
ReadPluginsInDirectory(Dir, EPluginLoadedFrom::Engine, Plugins);
}
// For enterprise projects, add plugins in EnterprisePluginsDir
if (Project->bIsEnterpriseProject)
{
ReadPluginsInDirectory(FPaths::EnterprisePluginsDir(), EPluginLoadedFrom::Engine, Plugins);
}
}
for (const FString& ExtraSearchPath : ExtraSearchPaths)
{
ReadPluginsInDirectory(ExtraSearchPath, EPluginLoadedFrom::GameProject, Plugins);
}
#endif
}
void FPluginManager::ReadPluginsInDirectory(const FString& PluginsDirectory, const EPluginLoadedFrom LoadedFrom, TMap<FString, TSharedRef<FPlugin>>& Plugins)
{
// Make sure the directory even exists
if(FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*PluginsDirectory))
{
TArray<FString> FileNames;
FindPluginsInDirectory(PluginsDirectory, FileNames);
for(const FString& FileName: FileNames)
{
FPluginDescriptor Descriptor;
FText FailureReason;
if ( Descriptor.Load(FileName, LoadedFrom == EPluginLoadedFrom::GameProject, FailureReason) )
{
TSharedRef<FPlugin> Plugin = MakeShareable(new FPlugin(FileName, Descriptor, LoadedFrom));
FString FullPath = FPaths::ConvertRelativePathToFull(FileName);
UE_LOG(LogPluginManager, Verbose, TEXT("Read plugin descriptor for %s, from %s"), *Plugin->GetName(), *FullPath);
const TSharedRef<FPlugin>* ExistingPlugin = Plugins.Find(Plugin->GetName());
if (ExistingPlugin == nullptr)
{
Plugins.Add(Plugin->GetName(), Plugin);
}
else if ((*ExistingPlugin)->LoadedFrom == EPluginLoadedFrom::Engine && LoadedFrom == EPluginLoadedFrom::GameProject)
{
Plugins[Plugin->GetName()] = Plugin;
UE_LOG(LogPluginManager, Verbose, TEXT("Replacing engine version of '%s' plugin with game version"), *Plugin->GetName());
}
else if( (*ExistingPlugin)->LoadedFrom != EPluginLoadedFrom::GameProject || LoadedFrom != EPluginLoadedFrom::Engine)
{
UE_LOG(LogPluginManager, Warning, TEXT("Plugin '%s' exists at '%s' and '%s' - second location will be ignored"), *Plugin->GetName(), *(*ExistingPlugin)->FileName, *Plugin->FileName);
}
}
else
{
// NOTE: Even though loading of this plugin failed, we'll keep processing other plugins
FString FullPath = FPaths::ConvertRelativePathToFull(FileName);
FText FailureMessage = FText::Format(LOCTEXT("FailureFormat", "{0} ({1})"), FailureReason, FText::FromString(FullPath));
FText DialogTitle = LOCTEXT("PluginFailureTitle", "Failed to load Plugin");
UE_LOG(LogPluginManager, Error, TEXT("%s"), *FailureMessage.ToString());
FMessageDialog::Open(EAppMsgType::Ok, FailureMessage, &DialogTitle);
}
}
}
}
void FPluginManager::FindPluginsInDirectory(const FString& PluginsDirectory, TArray<FString>& FileNames)
{
// Class to enumerate the contents of a directory, and find all sub-directories and plugin descriptors within it
struct FPluginDirectoryVisitor : public IPlatformFile::FDirectoryVisitor
{
TArray<FString> SubDirectories;
TArray<FString> PluginDescriptors;
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
{
FString FilenameOrDirectoryStr = FilenameOrDirectory;
if(bIsDirectory)
{
SubDirectories.Add(FilenameOrDirectoryStr);
}
else if(FilenameOrDirectoryStr.EndsWith(TEXT(".uplugin")))
{
PluginDescriptors.Add(FilenameOrDirectoryStr);
}
return true;
}
};
// Enumerate the contents of the current directory
FPluginDirectoryVisitor Visitor;
FPlatformFileManager::Get().GetPlatformFile().IterateDirectory(*PluginsDirectory, Visitor);
// If there's no plugins in this directory, recurse through all the child directories
if(Visitor.PluginDescriptors.Num() == 0)
{
for(const FString& SubDirectory: Visitor.SubDirectories)
{
FindPluginsInDirectory(SubDirectory, FileNames);
}
}
else
{
for(const FString& PluginDescriptor: Visitor.PluginDescriptors)
{
if (!FileNames.Contains(PluginDescriptor))
{
FileNames.Add(PluginDescriptor);
}
}
}
}
// Helper class to find all pak files.
class FPakFileSearchVisitor : public IPlatformFile::FDirectoryVisitor
{
TArray<FString>& FoundFiles;
public:
FPakFileSearchVisitor(TArray<FString>& InFoundFiles)
: FoundFiles(InFoundFiles)
{}
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory)
{
if (bIsDirectory == false)
{
FString Filename(FilenameOrDirectory);
if (Filename.MatchesWildcard(TEXT("*.pak")) && !FoundFiles.Contains(Filename))
{
FoundFiles.Add(Filename);
}
}
return true;
}
};
bool FPluginManager::ConfigureEnabledPlugins()
{
if(!bHaveConfiguredEnabledPlugins)
{
// Don't need to run this again
bHaveConfiguredEnabledPlugins = true;
// Get all the enabled plugin names
TArray<FString> InitialPluginNames;
TMap<FString, const FPluginReferenceDescriptor*> NameToPluginReference;
#if IS_PROGRAM
// Programs can also define the list of enabled plugins in ini
GConfig->GetArray(TEXT("Plugins"), TEXT("ProgramEnabledPlugins"), InitialPluginNames, GEngineIni);
#endif
#if !IS_PROGRAM || HACK_HEADER_GENERATOR
if (!FParse::Param(FCommandLine::Get(), TEXT("NoEnginePlugins")))
{
// Find all the plugin references in the project file
const FProjectDescriptor* ProjectDescriptor = IProjectManager::Get().GetCurrentProject();
if (ProjectDescriptor != nullptr)
{
for (const FPluginReferenceDescriptor& PluginReference : ProjectDescriptor->Plugins)
{
NameToPluginReference.FindOrAdd(PluginReference.Name) = &PluginReference;
}
}
// Get the default list of plugin names
for (const TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
{
const FPluginReferenceDescriptor* PluginReference = NameToPluginReference.FindRef(PluginPair.Key);
if ((PluginReference == nullptr && PluginPair.Value->Descriptor.bEnabledByDefault) || (PluginReference != nullptr && PluginReference->bEnabled))
{
InitialPluginNames.Add(PluginPair.Key);
}
}
}
#endif
// Enable all the plugins
TSet<FString> EnabledPluginNames;
for (const FString& PluginName : InitialPluginNames)
{
const FPluginReferenceDescriptor* EnabledPluginReference = NameToPluginReference.FindRef(PluginName);
if (EnabledPluginReference == nullptr)
{
if (!ConfigureEnabledPlugin(FPluginReferenceDescriptor(PluginName, true), EnabledPluginNames))
{
return false;
}
}
else
{
if (!ConfigureEnabledPlugin(*EnabledPluginReference, EnabledPluginNames))
{
return false;
}
}
}
// If we made it here, we have all the required plugins
bHaveAllRequiredPlugins = true;
// Mount all the enabled plugins
for(const TPair<FString, TSharedRef<FPlugin>>& PluginPair: AllPlugins)
{
const FPlugin& Plugin = *PluginPair.Value;
if (Plugin.bEnabled)
{
// Plugins can have their own shaders
// Add potential plugin shader directory only if the plugin is loaded in PostConfigInit. Not supported otherwise
for (const FModuleDescriptor& Module : Plugin.GetDescriptor().Modules)
{
if (Module.LoadingPhase == ELoadingPhase::PostConfigInit)
{
FGenericPlatformProcess::AddShaderDir(FPaths::Combine(*Plugin.GetBaseDir(), TEXT("Shaders")));
break;
}
}
// Build the list of content folders
if (Plugin.Descriptor.bCanContainContent)
{
if (FConfigFile* EngineConfigFile = GConfig->Find(GEngineIni, false))
{
if (FConfigSection* CoreSystemSection = EngineConfigFile->Find(TEXT("Core.System")))
{
CoreSystemSection->AddUnique("Paths", Plugin.GetContentDir());
}
}
}
// Load <PluginName>.ini config file if it exists
FString PluginConfigDir = FPaths::GetPath(Plugin.FileName) / TEXT("Config/");
FString EngineConfigDir = FPaths::EngineConfigDir();
FString SourceConfigDir = FPaths::SourceConfigDir();
// Load Engine plugins out of BasePluginName.ini and the engine directory, game plugins out of DefaultPluginName.ini
if (Plugin.LoadedFrom == EPluginLoadedFrom::Engine)
{
EngineConfigDir = PluginConfigDir;
}
else
{
SourceConfigDir = PluginConfigDir;
}
FString PluginConfigFilename = FString::Printf(TEXT("%s%s/%s.ini"), *FPaths::GeneratedConfigDir(), ANSI_TO_TCHAR(FPlatformProperties::PlatformName()), *Plugin.Name);
FConfigFile& PluginConfig = GConfig->Add(PluginConfigFilename, FConfigFile());
// This will write out an ini to PluginConfigFilename
if (!FConfigCacheIni::LoadExternalIniFile(PluginConfig, *Plugin.Name, *EngineConfigDir, *SourceConfigDir, true, nullptr, false, true))
{
// Nothing to add, remove from map
GConfig->Remove(PluginConfigFilename);
}
if (!GIsEditor)
{
// override config cache entries with plugin configs (Engine.ini, Game.ini, etc in <PluginDir>\Config\)
TArray<FString> PluginConfigs;
IFileManager::Get().FindFiles(PluginConfigs, *PluginConfigDir, TEXT("ini"));
for (const FString& ConfigFile : PluginConfigs)
{
FString PlaformName = FPlatformProperties::PlatformName();
PluginConfigFilename = FString::Printf(TEXT("%s%s/%s.ini"), *FPaths::GeneratedConfigDir(), *PlaformName, *FPaths::GetBaseFilename(ConfigFile));
FConfigFile* FoundConfig = GConfig->Find(PluginConfigFilename, false);
if (FoundConfig != nullptr)
{
FString PluginConfigContent;
if (FFileHelper::LoadFileToString(PluginConfigContent, *FPaths::Combine(PluginConfigDir, ConfigFile)))
{
FoundConfig->CombineFromBuffer(PluginConfigContent);
// if plugin config overrides are applied then don't save
FoundConfig->NoSave = true;
}
}
}
}
}
}
// Mount all the plugin content folders and pak files
TArray<FString> FoundPaks;
FPakFileSearchVisitor PakVisitor(FoundPaks);
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
for(TSharedRef<IPlugin> Plugin: GetEnabledPlugins())
{
if (Plugin->CanContainContent() && ensure(RegisterMountPointDelegate.IsBound()))
{
FString ContentDir = Plugin->GetContentDir();
RegisterMountPointDelegate.Execute(Plugin->GetMountedAssetPath(), ContentDir);
// Pak files are loaded from <PluginName>/Content/Paks/<PlatformName>
if (FPlatformProperties::RequiresCookedData())
{
FoundPaks.Reset();
PlatformFile.IterateDirectoryRecursively(*(ContentDir / TEXT("Paks") / FPlatformProperties::PlatformName()), PakVisitor);
for (const FString& PakPath : FoundPaks)
{
if (FCoreDelegates::OnMountPak.IsBound())
{
FCoreDelegates::OnMountPak.Execute(PakPath, 0, nullptr);
PluginsWithPakFile.AddUnique(Plugin);
}
else
{
UE_LOG(LogPluginManager, Warning, TEXT("PAK file (%s) could not be mounted because OnMountPak is not bound"), *PakPath)
}
}
}
}
}
}
return bHaveAllRequiredPlugins;
}
bool FPluginManager::ConfigureEnabledPlugin(const FPluginReferenceDescriptor& FirstReference, TSet<FString>& EnabledPluginNames)
{
if (!EnabledPluginNames.Contains(FirstReference.Name))
{
// Set of plugin names we've added to the queue for processing
TSet<FString> NewPluginNames;
NewPluginNames.Add(FirstReference.Name);
// Queue of plugin references to consider
TArray<const FPluginReferenceDescriptor*> NewPluginReferences;
NewPluginReferences.Add(&FirstReference);
// Loop through the queue of plugin references that need to be enabled, queuing more items as we go
TArray<TSharedRef<FPlugin>> NewPlugins;
for (int32 Idx = 0; Idx < NewPluginReferences.Num(); Idx++)
{
const FPluginReferenceDescriptor& Reference = *NewPluginReferences[Idx];
// Check if the plugin is required for this platform
if(!Reference.IsEnabledForPlatform(FPlatformMisc::GetUBTPlatform()) || !Reference.IsEnabledForTarget(FPlatformMisc::GetUBTTarget()))
{
UE_LOG(LogPluginManager, Verbose, TEXT("Ignoring plugin '%s' for platform/configuration"), *Reference.Name);
continue;
}
// Find the plugin being enabled
TSharedRef<FPlugin>* PluginPtr = AllPlugins.Find(Reference.Name);
if (PluginPtr == nullptr)
{
// Ignore any optional plugins
if (Reference.bOptional)
{
UE_LOG(LogPluginManager, Verbose, TEXT("Ignored optional reference to '%s' plugin; plugin was not found."), *Reference.Name);
continue;
}
// If we're in unattended mode, don't open any windows
if (FApp::IsUnattended())
{
UE_LOG(LogPluginManager, Error, TEXT("This project requires the '%s' plugin. Install it and try again, or remove it from the project's required plugin list."), *Reference.Name);
return false;
}
#if !IS_MONOLITHIC
// Try to download it from the marketplace
if (Reference.MarketplaceURL.Len() > 0 && PromptToDownloadPlugin(Reference.Name, Reference.MarketplaceURL))
{
UE_LOG(LogPluginManager, Display, TEXT("Downloading '%s' plugin from marketplace (%s)."), *Reference.Name, *Reference.MarketplaceURL);
return false;
}
// Prompt to disable it in the project file, if possible
if (PromptToDisableMissingPlugin(FirstReference.Name, Reference.Name))
{
UE_LOG(LogPluginManager, Display, TEXT("Disabled plugin '%s', continuing."), *FirstReference.Name);
return true;
}
#endif
// Unable to continue
UE_LOG(LogPluginManager, Error, TEXT("Unable to load plugin '%s'. Aborting."), *Reference.Name);
return false;
}
// Check the plugin is not disabled by the platform
FPlugin& Plugin = PluginPtr->Get();
// Allow the platform to disable it
if (FPlatformMisc::ShouldDisablePluginAtRuntime(Plugin.Name))
{
UE_LOG(LogPluginManager, Verbose, TEXT("Plugin '%s' was disabled by platform."), *Reference.Name);
continue;
}
#if !IS_MONOLITHIC
// Mount the binaries directory, and check the modules are valid
if (Plugin.Descriptor.Modules.Num() > 0)
{
// Mount the binaries directory
const FString PluginBinariesPath = FPaths::Combine(*FPaths::GetPath(Plugin.FileName), TEXT("Binaries"), FPlatformProcess::GetBinariesSubdirectory());
FModuleManager::Get().AddBinariesDirectory(*PluginBinariesPath, Plugin.LoadedFrom == EPluginLoadedFrom::GameProject);
// Check if the modules are valid
TArray<FString> IncompatibleFiles;
if (!FModuleDescriptor::CheckModuleCompatibility(Plugin.Descriptor.Modules, Plugin.LoadedFrom == EPluginLoadedFrom::GameProject, IncompatibleFiles))
{
if (PromptToDisableIncompatiblePlugin(FirstReference.Name, Reference.Name))
{
UE_LOG(LogPluginManager, Display, TEXT("Disabled plugin '%s', continuing."), *FirstReference.Name);
return true;
}
}
}
// Check the declared engine version. This is a soft requirement, so allow the user to skip over it.
if (!IsPluginCompatible(Plugin) && !PromptToLoadIncompatiblePlugin(Plugin, FirstReference.Name))
{
UE_LOG(LogPluginManager, Display, TEXT("Skipping load of '%s'."), *Plugin.Name);
return true;
}
#endif
// Add references to all its dependencies
for (const FPluginReferenceDescriptor& NextReference : Plugin.Descriptor.Plugins)
{
if (!EnabledPluginNames.Contains(NextReference.Name) && !NewPluginNames.Contains(NextReference.Name))
{
NewPluginNames.Add(NextReference.Name);
NewPluginReferences.Add(&NextReference);
}
}
// Add the plugin
NewPlugins.Add(*PluginPtr);
}
// Mark all the plugins as enabled
for (TSharedRef<FPlugin>& NewPlugin : NewPlugins)
{
NewPlugin->bEnabled = true;
EnabledPluginNames.Add(NewPlugin->Name);
}
}
return true;
}
bool FPluginManager::PromptToDownloadPlugin(const FString& PluginName, const FString& MarketplaceURL)
{
FText Caption = FText::Format(LOCTEXT("DownloadPluginCaption", "Missing {0} Plugin"), FText::FromString(PluginName));
FText Message = FText::Format(LOCTEXT("DownloadPluginMessage", "This project requires the {0} plugin.\n\nWould you like to download it from the Unreal Engine Marketplace?"), FText::FromString(PluginName));
if(FMessageDialog::Open(EAppMsgType::YesNo, Message, &Caption) == EAppReturnType::Yes)
{
FString Error;
FPlatformProcess::LaunchURL(*MarketplaceURL, nullptr, &Error);
if (Error.Len() == 0)
{
return true;
}
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Error));
}
return false;
}
bool FPluginManager::PromptToDisableMissingPlugin(const FString& PluginName, const FString& MissingPluginName)
{
FText Message;
if (PluginName == MissingPluginName)
{
Message = FText::Format(LOCTEXT("DisablePluginMessage_NotFound", "This project requires the '{0}' plugin, which could not be found.\n\nWould you like to disable it? You will no longer be able to open any assets created using it."), FText::FromString(PluginName));
}
else
{
Message = FText::Format(LOCTEXT("DisablePluginMessage_MissingDependency", "This project requires the '{0}' plugin, which has a missing dependency on the '{1}' plugin.\n\nWould you like to disable it? You will no longer be able to open any assets created using it."), FText::FromString(PluginName), FText::FromString(MissingPluginName));
}
FText Caption(LOCTEXT("DisablePluginCaption", "Missing Plugin"));
return PromptToDisablePlugin(Caption, Message, PluginName);
}
bool FPluginManager::PromptToDisableIncompatiblePlugin(const FString& PluginName, const FString& IncompatiblePluginName)
{
FText Message;
if (PluginName == IncompatiblePluginName)
{
Message = FText::Format(LOCTEXT("DisablePluginMessage_IncompatibleEngineVersion", "This project requires the '{0}' plugin, which is not compatible with the current engine version.\n\nWould you like to disable it? You will no longer be able to open any assets created using it."), FText::FromString(PluginName));
}
else
{
Message = FText::Format(LOCTEXT("DisablePluginMessage_IncompatibleDependency", "This project requires the '{0}' plugin, which has a dependency on the '{1}' plugin, which is not compatible with the current engine version.\n\nWould you like to disable it? You will no longer be able to open any assets created using it."), FText::FromString(PluginName), FText::FromString(IncompatiblePluginName));
}
FText Caption(LOCTEXT("DisablePluginCaption", "Missing Plugin"));
return PromptToDisablePlugin(Caption, Message, PluginName);
}
bool FPluginManager::PromptToDisablePlugin(const FText& Caption, const FText& Message, const FString& PluginName)
{
// Check we have a project file. If this is a missing engine/program plugin referenced by something, we can't disable it through this method.
if (IProjectManager::Get().GetCurrentProject() != nullptr)
{
if (FMessageDialog::Open(EAppMsgType::YesNo, Message, &Caption) == EAppReturnType::Yes)
{
FText FailReason;
if (IProjectManager::Get().SetPluginEnabled(*PluginName, false, FailReason))
{
return true;
}
FMessageDialog::Open(EAppMsgType::Ok, FailReason);
}
}
return false;
}
bool FPluginManager::IsPluginCompatible(const FPlugin& Plugin)
{
if (Plugin.Descriptor.EngineVersion.Len() > 0)
{
FEngineVersion Version;
if (!FEngineVersion::Parse(Plugin.Descriptor.EngineVersion, Version))
{
UE_LOG(LogPluginManager, Warning, TEXT("Engine version string in %s could not be parsed (\"%s\")"), *Plugin.FileName, *Plugin.Descriptor.EngineVersion);
return true;
}
EVersionComparison Comparison = FEngineVersion::GetNewest(FEngineVersion::CompatibleWith(), Version, nullptr);
if (Comparison != EVersionComparison::Neither)
{
UE_LOG(LogPluginManager, Warning, TEXT("Plugin '%s' is not compatible with the current engine version (%s)"), *Plugin.Name, *Plugin.Descriptor.EngineVersion);
return false;
}
}
return true;
}
bool FPluginManager::PromptToLoadIncompatiblePlugin(const FPlugin& Plugin, const FString& ReferencingPluginName)
{
// Format the message dependning on whether the plugin is referenced directly, or as a dependency
FText Message;
if (Plugin.Name == ReferencingPluginName)
{
Message = FText::Format(LOCTEXT("LoadIncompatiblePlugin", "The '{0}' plugin was designed for build {1}. Attempt to load it anyway?"), FText::FromString(Plugin.Name), FText::FromString(Plugin.Descriptor.EngineVersion));
}
else
{
Message = FText::Format(LOCTEXT("LoadIncompatibleDependencyPlugin", "The '{0}' plugin is required by the '{1}' plugin, but was designed for build {2}. Attempt to load it anyway?"), FText::FromString(Plugin.Name), FText::FromString(ReferencingPluginName), FText::FromString(Plugin.Descriptor.EngineVersion));
}
FText Caption = FText::Format(LOCTEXT("IncompatiblePluginCaption", "'{0}' is Incompatible"), FText::FromString(Plugin.Name));
return FMessageDialog::Open(EAppMsgType::YesNo, Message, &Caption) == EAppReturnType::Yes;
}
TSharedPtr<FPlugin> FPluginManager::FindPluginInstance(const FString& Name)
{
const TSharedRef<FPlugin>* Instance = AllPlugins.Find(Name);
if (Instance == nullptr)
{
return TSharedPtr<FPlugin>();
}
else
{
return TSharedPtr<FPlugin>(*Instance);
}
}
static bool TryLoadModulesForPlugin( const FPlugin& Plugin, const ELoadingPhase::Type LoadingPhase )
{
TMap<FName, EModuleLoadResult> ModuleLoadFailures;
FModuleDescriptor::LoadModulesForPhase(LoadingPhase, Plugin.Descriptor.Modules, ModuleLoadFailures);
FText FailureMessage;
for( auto FailureIt( ModuleLoadFailures.CreateConstIterator() ); FailureIt; ++FailureIt )
{
const FName ModuleNameThatFailedToLoad = FailureIt.Key();
const EModuleLoadResult FailureReason = FailureIt.Value();
if( FailureReason != EModuleLoadResult::Success )
{
const FText PluginNameText = FText::FromString(Plugin.Name);
const FText TextModuleName = FText::FromName(FailureIt.Key());
if ( FailureReason == EModuleLoadResult::FileNotFound )
{
FailureMessage = FText::Format( LOCTEXT("PluginModuleNotFound", "Plugin '{0}' failed to load because module '{1}' could not be found. Please ensure the plugin is properly installed, otherwise consider disabling the plugin for this project."), PluginNameText, TextModuleName );
}
else if ( FailureReason == EModuleLoadResult::FileIncompatible )
{
FailureMessage = FText::Format( LOCTEXT("PluginModuleIncompatible", "Plugin '{0}' failed to load because module '{1}' does not appear to be compatible with the current version of the engine. The plugin may need to be recompiled."), PluginNameText, TextModuleName );
}
else if ( FailureReason == EModuleLoadResult::CouldNotBeLoadedByOS )
{
FailureMessage = FText::Format( LOCTEXT("PluginModuleCouldntBeLoaded", "Plugin '{0}' failed to load because module '{1}' could not be loaded. There may be an operating system error or the module may not be properly set up."), PluginNameText, TextModuleName );
}
else if ( FailureReason == EModuleLoadResult::FailedToInitialize )
{
FailureMessage = FText::Format( LOCTEXT("PluginModuleFailedToInitialize", "Plugin '{0}' failed to load because module '{1}' could not be initialized successfully after it was loaded."), PluginNameText, TextModuleName );
}
else
{
ensure(0); // If this goes off, the error handling code should be updated for the new enum values!
FailureMessage = FText::Format( LOCTEXT("PluginGenericLoadFailure", "Plugin '{0}' failed to load because module '{1}' could not be loaded for an unspecified reason. This plugin's functionality will not be available. Please report this error."), PluginNameText, TextModuleName );
}
// Don't need to display more than one module load error per plugin that failed to load
break;
}
}
if( !FailureMessage.IsEmpty() )
{
FMessageDialog::Open(EAppMsgType::Ok, FailureMessage);
return false;
}
return true;
}
bool FPluginManager::LoadModulesForEnabledPlugins( const ELoadingPhase::Type LoadingPhase )
{
// Figure out which plugins are enabled
if(!ConfigureEnabledPlugins())
{
return false;
}
FScopedSlowTask SlowTask(AllPlugins.Num());
// Load plugins!
for( const TPair<FString, TSharedRef< FPlugin >> PluginPair : AllPlugins )
{
const TSharedRef<FPlugin> &Plugin = PluginPair.Value;
SlowTask.EnterProgressFrame(1);
if ( Plugin->bEnabled )
{
if (!TryLoadModulesForPlugin(Plugin.Get(), LoadingPhase))
{
return false;
}
}
}
return true;
}
void FPluginManager::GetLocalizationPathsForEnabledPlugins( TArray<FString>& OutLocResPaths )
{
// Figure out which plugins are enabled
if (!ConfigureEnabledPlugins())
{
return;
}
// Gather the paths from all plugins that have localization targets that are loaded based on the current runtime environment
for (const TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
{
const TSharedRef<FPlugin>& Plugin = PluginPair.Value;
if (!Plugin->bEnabled || Plugin->GetDescriptor().LocalizationTargets.Num() == 0)
{
continue;
}
const FString PluginLocDir = Plugin->GetContentDir() / TEXT("Localization");
for (const FLocalizationTargetDescriptor& LocTargetDesc : Plugin->GetDescriptor().LocalizationTargets)
{
if (LocTargetDesc.ShouldLoadLocalizationTarget())
{
OutLocResPaths.Add(PluginLocDir / LocTargetDesc.Name);
}
}
}
}
void FPluginManager::SetRegisterMountPointDelegate( const FRegisterMountPointDelegate& Delegate )
{
RegisterMountPointDelegate = Delegate;
}
bool FPluginManager::AreRequiredPluginsAvailable()
{
return ConfigureEnabledPlugins();
}
bool FPluginManager::CheckModuleCompatibility(TArray<FString>& OutIncompatibleModules)
{
if(!ConfigureEnabledPlugins())
{
return false;
}
bool bResult = true;
for(const TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
{
const TSharedRef< FPlugin > &Plugin = PluginPair.Value;
if (Plugin->bEnabled && !FModuleDescriptor::CheckModuleCompatibility(Plugin->Descriptor.Modules, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject, OutIncompatibleModules))
{
bResult = false;
}
}
return bResult;
}
IPluginManager& IPluginManager::Get()
{
// Single instance of manager, allocated on demand and destroyed on program exit.
static FPluginManager* PluginManager = NULL;
if( PluginManager == NULL )
{
PluginManager = new FPluginManager();
}
return *PluginManager;
}
TSharedPtr<IPlugin> FPluginManager::FindPlugin(const FString& Name)
{
const TSharedRef<FPlugin>* Instance = AllPlugins.Find(Name);
if (Instance == nullptr)
{
return TSharedPtr<IPlugin>();
}
else
{
return TSharedPtr<IPlugin>(*Instance);
}
}
TArray<TSharedRef<IPlugin>> FPluginManager::GetEnabledPlugins()
{
TArray<TSharedRef<IPlugin>> Plugins;
for(TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
{
TSharedRef<FPlugin>& PossiblePlugin = PluginPair.Value;
if(PossiblePlugin->bEnabled)
{
Plugins.Add(PossiblePlugin);
}
}
return Plugins;
}
TArray<TSharedRef<IPlugin>> FPluginManager::GetDiscoveredPlugins()
{
TArray<TSharedRef<IPlugin>> Plugins;
for (TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins)
{
Plugins.Add(PluginPair.Value);
}
return Plugins;
}
TArray< FPluginStatus > FPluginManager::QueryStatusForAllPlugins() const
{
TArray< FPluginStatus > PluginStatuses;
for( const TPair<FString, TSharedRef<FPlugin>>& PluginPair : AllPlugins )
{
const TSharedRef< FPlugin >& Plugin = PluginPair.Value;
FPluginStatus PluginStatus;
PluginStatus.Name = Plugin->Name;
PluginStatus.PluginDirectory = FPaths::GetPath(Plugin->FileName);
PluginStatus.bIsEnabled = Plugin->bEnabled;
PluginStatus.Descriptor = Plugin->Descriptor;
PluginStatus.LoadedFrom = Plugin->LoadedFrom;
PluginStatuses.Add( PluginStatus );
}
return PluginStatuses;
}
void FPluginManager::AddPluginSearchPath(const FString& ExtraDiscoveryPath, bool bRefresh)
{
PluginDiscoveryPaths.Add(ExtraDiscoveryPath);
if (bRefresh)
{
RefreshPluginsList();
}
}
TArray<TSharedRef<IPlugin>> FPluginManager::GetPluginsWithPakFile() const
{
return PluginsWithPakFile;
}
IPluginManager::FNewPluginMountedEvent& FPluginManager::OnNewPluginMounted()
{
return NewPluginMountedEvent;
}
void FPluginManager::MountNewlyCreatedPlugin(const FString& PluginName)
{
for(TMap<FString, TSharedRef<FPlugin>>::TIterator Iter(AllPlugins); Iter; ++Iter)
{
const TSharedRef<FPlugin>& Plugin = Iter.Value();
if (Plugin->Name == PluginName)
{
// Mark the plugin as enabled
Plugin->bEnabled = true;
// Mount the plugin content directory
if (Plugin->CanContainContent() && ensure(RegisterMountPointDelegate.IsBound()))
{
FString ContentDir = Plugin->GetContentDir();
RegisterMountPointDelegate.Execute(Plugin->GetMountedAssetPath(), ContentDir);
// Register this plugin's path with the list of content directories that the editor will search
if (FConfigFile* EngineConfigFile = GConfig->Find(GEngineIni, false))
{
if (FConfigSection* CoreSystemSection = EngineConfigFile->Find(TEXT("Core.System")))
{
CoreSystemSection->AddUnique("Paths", Plugin->GetContentDir());
}
}
}
// If it's a code module, also load the modules for it
if (Plugin->Descriptor.Modules.Num() > 0)
{
// Add the plugin binaries directory
const FString PluginBinariesPath = FPaths::Combine(*FPaths::GetPath(Plugin->FileName), TEXT("Binaries"), FPlatformProcess::GetBinariesSubdirectory());
FModuleManager::Get().AddBinariesDirectory(*PluginBinariesPath, Plugin->LoadedFrom == EPluginLoadedFrom::GameProject);
// Load all the plugin modules
for (ELoadingPhase::Type LoadingPhase = (ELoadingPhase::Type)0; LoadingPhase < ELoadingPhase::Max; LoadingPhase = (ELoadingPhase::Type)(LoadingPhase + 1))
{
if (LoadingPhase != ELoadingPhase::None)
{
TryLoadModulesForPlugin(Plugin.Get(), LoadingPhase);
}
}
}
// Notify any listeners that a new plugin has been mounted
if (NewPluginMountedEvent.IsBound())
{
NewPluginMountedEvent.Broadcast(*Plugin);
}
break;
}
}
}
#undef LOCTEXT_NAMESPACE