Files
UnrealEngineUWP/Engine/Source/Runtime/Projects/Private/PluginDescriptor.cpp
dave belanger e59338fcbb Add PluginUtils::FindLoadedPlugin
PluginUtils::LoadPlugin improvements and API cleanup:
- Add SynchronousAssetsScan and OutAlreadyLoaded params
- Make all loading options false by default (caller opts-in on whatever it wants instead of opting out on some)
- Put OutFailReason in the loading param struct
- Deprecate MountPlugin API and rename it LoadPlugin to mirror UnloadPlugin terminology
#rb Rex.Hill
#preflight 61a8fc58e8314ee7b598f55d

#ROBOMERGE-AUTHOR: dave.belanger
#ROBOMERGE-SOURCE: CL 18358835 via CL 18364441 via CL 18364493 via CL 18364530 via CL 18434167 via CL 18435484
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v897-18405271)

[CL 18436249 by dave belanger in ue5-release-engine-test branch]
2021-12-10 18:33:00 -05:00

487 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PluginDescriptor.h"
#include "Misc/FileHelper.h"
#include "HAL/FileManager.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "ProjectDescriptor.h"
#define LOCTEXT_NAMESPACE "PluginDescriptor"
namespace PluginDescriptor
{
bool ReadFile(const FString& FileName, FString& Text, FText* OutFailReason = nullptr)
{
if (!FFileHelper::LoadFileToString(Text, *FileName))
{
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailedToLoadDescriptorFile", "Failed to open descriptor file '{0}'"), FText::FromString(FileName));
}
return false;
}
return true;
}
bool WriteFile(const FString& FileName, const FString& Text, FText* OutFailReason = nullptr)
{
if (!FFileHelper::SaveStringToFile(Text, *FileName))
{
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailedToWriteDescriptorFile", "Failed to write plugin descriptor file '{0}'. Perhaps the file is Read-Only?"), FText::FromString(FileName));
}
return false;
}
return true;
}
TSharedPtr<FJsonObject> DeserializeJson(const FString& Text, FText* OutFailReason = nullptr)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Text);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailedToReadDescriptorFile", "Failed to read file. {0}"), FText::FromString(Reader->GetErrorMessage()));
}
return TSharedPtr<FJsonObject>();
}
return JsonObject;
}
}
/**
* Version numbers for plugin descriptors. These version numbers are not generally needed; serialization from JSON attempts to be tolerant of missing/added fields.
*/
enum class EPluginDescriptorVersion : uint8
{
Invalid = 0,
Initial = 1,
NameHash = 2,
ProjectPluginUnification = 3,
// !!!!!!!!!! IMPORTANT: Remember to also update LatestPluginDescriptorFileVersion in Plugins.cs (and Plugin system documentation) when this changes!!!!!!!!!!!
// -----<new versions can be added before this line>-------------------------------------------------
// - this needs to be the last line (see note below)
LatestPlusOne,
Latest = LatestPlusOne - 1
};
const FString& FPluginDescriptor::GetFileExtension()
{
static const FString Extension(TEXT(".uplugin"));
return Extension;
}
FPluginDescriptor::FPluginDescriptor()
: Version(0)
, EnabledByDefault(EPluginEnabledByDefault::Unspecified)
, bCanContainContent(false)
, bIsBetaVersion(false)
, bIsExperimentalVersion(false)
, bInstalled(false)
, bRequiresBuildPlatform(false)
, bIsHidden(false)
, bExplicitlyLoaded(false)
, bHasExplicitPlatforms(false)
, bIsPluginExtension(false)
{
}
bool FPluginDescriptor::Load(const FString& FileName, FText* OutFailReason /*= nullptr*/)
{
#if WITH_EDITOR
CachedJson.Reset();
AdditionalFieldsToWrite.Reset();
#endif // WITH_EDITOR
FString Text;
if (PluginDescriptor::ReadFile(FileName, Text, OutFailReason))
{
return Read(Text, OutFailReason);
}
return false;
}
bool FPluginDescriptor::Load(const FString& FileName, FText& OutFailReason)
{
return Load(FileName, &OutFailReason);
}
bool FPluginDescriptor::Read(const FString& Text, FText* OutFailReason /*= nullptr*/)
{
#if WITH_EDITOR
CachedJson.Reset();
AdditionalFieldsToWrite.Reset();
#endif // WITH_EDITOR
// Deserialize a JSON object from the string
TSharedPtr<FJsonObject> JsonObject = PluginDescriptor::DeserializeJson(Text, OutFailReason);
if (JsonObject.IsValid())
{
// Parse it as a plug-in descriptor
if (Read(*JsonObject.Get(), OutFailReason))
{
#if WITH_EDITOR
CachedJson = JsonObject;
AdditionalFieldsToWrite.Reset();
#endif // WITH_EDITOR
return true;
}
}
return false;
}
bool FPluginDescriptor::Read(const FString& Text, FText& OutFailReason)
{
return Read(Text, &OutFailReason);
}
bool FPluginDescriptor::Read(const FJsonObject& Object, FText* OutFailReason /*= nullptr*/)
{
// Read the file version
int32 FileVersionInt32;
if(!Object.TryGetNumberField(TEXT("FileVersion"), FileVersionInt32))
{
if(!Object.TryGetNumberField(TEXT("PluginFileVersion"), FileVersionInt32))
{
if (OutFailReason)
{
*OutFailReason = LOCTEXT("InvalidProjectFileVersion", "File does not have a valid 'FileVersion' number.");
}
return false;
}
}
// Check that it's within range
EPluginDescriptorVersion PluginFileVersion = (EPluginDescriptorVersion)FileVersionInt32;
if ((PluginFileVersion <= EPluginDescriptorVersion::Invalid) || (PluginFileVersion > EPluginDescriptorVersion::Latest))
{
if (OutFailReason)
{
const FText ReadVersionText = FText::FromString(FString::Printf(TEXT("%d"), (int32)PluginFileVersion));
const FText LatestVersionText = FText::FromString(FString::Printf(TEXT("%d"), (int32)EPluginDescriptorVersion::Latest));
*OutFailReason = FText::Format(LOCTEXT("ProjectFileVersionTooLarge", "File appears to be in a newer version ({0}) of the file format that we can load (max version: {1})."), ReadVersionText, LatestVersionText);
}
return false;
}
// Read the other fields
Object.TryGetNumberField(TEXT("Version"), Version);
Object.TryGetStringField(TEXT("VersionName"), VersionName);
Object.TryGetStringField(TEXT("FriendlyName"), FriendlyName);
Object.TryGetStringField(TEXT("Description"), Description);
if (!Object.TryGetStringField(TEXT("Category"), Category))
{
// Category used to be called CategoryPath in .uplugin files
Object.TryGetStringField(TEXT("CategoryPath"), Category);
}
// Due to a difference in command line parsing between Windows and Mac, we shipped a few Mac samples containing
// a category name with escaped quotes. Remove them here to make sure we can list them in the right category.
if (Category.Len() >= 2 && Category.StartsWith(TEXT("\""), ESearchCase::CaseSensitive) && Category.EndsWith(TEXT("\""), ESearchCase::CaseSensitive))
{
Category.MidInline(1, Category.Len() - 2, false);
}
Object.TryGetStringField(TEXT("CreatedBy"), CreatedBy);
Object.TryGetStringField(TEXT("CreatedByURL"), CreatedByURL);
Object.TryGetStringField(TEXT("DocsURL"), DocsURL);
Object.TryGetStringField(TEXT("MarketplaceURL"), MarketplaceURL);
Object.TryGetStringField(TEXT("SupportURL"), SupportURL);
Object.TryGetStringField(TEXT("EngineVersion"), EngineVersion);
Object.TryGetStringField(TEXT("EditorCustomVirtualPath"), EditorCustomVirtualPath);
Object.TryGetStringArrayField(TEXT("SupportedTargetPlatforms"), SupportedTargetPlatforms);
Object.TryGetStringArrayField(TEXT("SupportedPrograms"), SupportedPrograms);
Object.TryGetBoolField(TEXT("bIsPluginExtension"), bIsPluginExtension);
if (!FModuleDescriptor::ReadArray(Object, TEXT("Modules"), Modules, OutFailReason))
{
return false;
}
if (!FLocalizationTargetDescriptor::ReadArray(Object, TEXT("LocalizationTargets"), LocalizationTargets, OutFailReason))
{
return false;
}
bool bEnabledByDefault;
if(Object.TryGetBoolField(TEXT("EnabledByDefault"), bEnabledByDefault))
{
EnabledByDefault = bEnabledByDefault? EPluginEnabledByDefault::Enabled : EPluginEnabledByDefault::Disabled;
}
Object.TryGetBoolField(TEXT("CanContainContent"), bCanContainContent);
Object.TryGetBoolField(TEXT("IsBetaVersion"), bIsBetaVersion);
Object.TryGetBoolField(TEXT("IsExperimentalVersion"), bIsExperimentalVersion);
Object.TryGetBoolField(TEXT("Installed"), bInstalled);
Object.TryGetBoolField(TEXT("RequiresBuildPlatform"), bRequiresBuildPlatform);
Object.TryGetBoolField(TEXT("Hidden"), bIsHidden);
Object.TryGetBoolField(TEXT("ExplicitlyLoaded"), bExplicitlyLoaded);
Object.TryGetBoolField(TEXT("HasExplicitPlatforms"), bHasExplicitPlatforms);
bool bCanBeUsedWithUnrealHeaderTool;
if(Object.TryGetBoolField("CanBeUsedWithUnrealHeaderTool", bCanBeUsedWithUnrealHeaderTool) && bCanBeUsedWithUnrealHeaderTool)
{
SupportedPrograms.Add(TEXT("UnrealHeaderTool"));
}
PreBuildSteps.Read(Object, TEXT("PreBuildSteps"));
PostBuildSteps.Read(Object, TEXT("PostBuildSteps"));
if (!FPluginReferenceDescriptor::ReadArray(Object, TEXT("Plugins"), Plugins, OutFailReason))
{
return false;
}
return true;
}
bool FPluginDescriptor::Read(const FJsonObject& Object, FText& OutFailReason)
{
return Read(Object, &OutFailReason);
}
bool FPluginDescriptor::Save(const FString& FileName, FText* OutFailReason /*= nullptr*/) const
{
// Write the descriptor to text
FString Text;
Write(Text);
// Save it to a file
return PluginDescriptor::WriteFile(FileName, Text, OutFailReason);
}
bool FPluginDescriptor::Save(const FString& FileName, FText& OutFailReason) const
{
return Save(FileName, &OutFailReason);
}
void FPluginDescriptor::Write(FString& Text) const
{
// Write the contents of the descriptor to a string. Make sure the writer is destroyed so that the contents are flushed to the string.
TSharedRef< TJsonWriter<> > WriterRef = TJsonWriterFactory<>::Create(&Text);
TJsonWriter<>& Writer = WriterRef.Get();
Write(Writer);
Writer.Close();
}
void FPluginDescriptor::Write(TJsonWriter<>& Writer) const
{
TSharedPtr<FJsonObject> PluginJsonObject = MakeShared<FJsonObject>();
#if WITH_EDITOR
if (CachedJson.IsValid())
{
FJsonObject::Duplicate(/*Source=*/ CachedJson, /*Dest=*/ PluginJsonObject);
}
#endif
UpdateJson(*PluginJsonObject);
FJsonSerializer::Serialize(PluginJsonObject.ToSharedRef(), Writer);
}
void FPluginDescriptor::UpdateJson(FJsonObject& JsonObject) const
{
JsonObject.SetNumberField(TEXT("FileVersion"), EProjectDescriptorVersion::Latest);
JsonObject.SetNumberField(TEXT("Version"), Version);
JsonObject.SetStringField(TEXT("VersionName"), VersionName);
JsonObject.SetStringField(TEXT("FriendlyName"), FriendlyName);
JsonObject.SetStringField(TEXT("Description"), Description);
JsonObject.SetStringField(TEXT("Category"), Category);
JsonObject.SetStringField(TEXT("CreatedBy"), CreatedBy);
JsonObject.SetStringField(TEXT("CreatedByURL"), CreatedByURL);
JsonObject.SetStringField(TEXT("DocsURL"), DocsURL);
JsonObject.SetStringField(TEXT("MarketplaceURL"), MarketplaceURL);
JsonObject.SetStringField(TEXT("SupportURL"), SupportURL);
if (EngineVersion.Len() > 0)
{
JsonObject.SetStringField(TEXT("EngineVersion"), EngineVersion);
}
else
{
JsonObject.RemoveField(TEXT("EngineVersion"));
}
if (EditorCustomVirtualPath.Len() > 0)
{
JsonObject.SetStringField(TEXT("EditorCustomVirtualPath"), EditorCustomVirtualPath);
}
else
{
JsonObject.RemoveField(TEXT("EditorCustomVirtualPath"));
}
if (EnabledByDefault != EPluginEnabledByDefault::Unspecified)
{
JsonObject.SetBoolField(TEXT("EnabledByDefault"), (EnabledByDefault == EPluginEnabledByDefault::Enabled));
}
else
{
JsonObject.RemoveField(TEXT("EnabledByDefault"));
}
JsonObject.SetBoolField(TEXT("CanContainContent"), bCanContainContent);
JsonObject.SetBoolField(TEXT("IsBetaVersion"), bIsBetaVersion);
JsonObject.SetBoolField(TEXT("IsExperimentalVersion"), bIsExperimentalVersion);
JsonObject.SetBoolField(TEXT("Installed"), bInstalled);
if (SupportedTargetPlatforms.Num() > 0)
{
TArray<TSharedPtr<FJsonValue>> SupportedTargetPlatformValues;
for (const FString& SupportedTargetPlatform : SupportedTargetPlatforms)
{
SupportedTargetPlatformValues.Add(MakeShareable(new FJsonValueString(SupportedTargetPlatform)));
}
JsonObject.SetArrayField(TEXT("SupportedTargetPlatforms"), SupportedTargetPlatformValues);
}
else
{
JsonObject.RemoveField(TEXT("SupportedTargetPlatforms"));
}
if (SupportedPrograms.Num() > 0)
{
TArray<TSharedPtr<FJsonValue>> SupportedProgramValues;
for (const FString& SupportedProgram : SupportedPrograms)
{
SupportedProgramValues.Add(MakeShareable(new FJsonValueString(SupportedProgram)));
}
JsonObject.SetArrayField(TEXT("SupportedPrograms"), SupportedProgramValues);
}
else
{
JsonObject.RemoveField(TEXT("SupportedPrograms"));
}
if (bIsPluginExtension)
{
JsonObject.SetBoolField(TEXT("bIsPluginExtension"), bIsPluginExtension);
}
else
{
JsonObject.RemoveField(TEXT("bIsPluginExtension"));
}
FModuleDescriptor::UpdateArray(JsonObject, TEXT("Modules"), Modules);
FLocalizationTargetDescriptor::UpdateArray(JsonObject, TEXT("LocalizationTargets"), LocalizationTargets);
if (bRequiresBuildPlatform)
{
JsonObject.SetBoolField(TEXT("RequiresBuildPlatform"), bRequiresBuildPlatform);
}
else
{
JsonObject.RemoveField(TEXT("RequiresBuildPlatform"));
}
if (bIsHidden)
{
JsonObject.SetBoolField(TEXT("Hidden"), bIsHidden);
}
else
{
JsonObject.RemoveField(TEXT("Hidden"));
}
if (bExplicitlyLoaded)
{
JsonObject.SetBoolField(TEXT("ExplicitlyLoaded"), bExplicitlyLoaded);
}
else
{
JsonObject.RemoveField(TEXT("ExplicitlyLoaded"));
}
if (bHasExplicitPlatforms)
{
JsonObject.SetBoolField(TEXT("HasExplicitPlatforms"), bHasExplicitPlatforms);
}
else
{
JsonObject.RemoveField("HasExplicitPlatforms");
}
PreBuildSteps.UpdateJson(JsonObject, TEXT("PreBuildSteps"));
PostBuildSteps.UpdateJson(JsonObject, TEXT("PostBuildSteps"));
FPluginReferenceDescriptor::UpdateArray(JsonObject, TEXT("Plugins"), Plugins);
#if WITH_EDITOR
for (const auto& KVP : AdditionalFieldsToWrite)
{
JsonObject.SetField(KVP.Key, FJsonValue::Duplicate(KVP.Value));
}
#endif
}
bool FPluginDescriptor::UpdatePluginFile(const FString& FileName, FText* OutFailReason /*= nullptr*/) const
{
if (IFileManager::Get().FileExists(*FileName))
{
// Plugin file exists so we need to read it and update it.
FString JsonText;
if (!PluginDescriptor::ReadFile(FileName, JsonText, OutFailReason))
{
return false;
}
TSharedPtr<FJsonObject> JsonObject = PluginDescriptor::DeserializeJson(JsonText, OutFailReason);
if (!JsonObject.IsValid())
{
return false;
}
UpdateJson(*JsonObject.Get());
{
TSharedRef<TJsonWriter<>> JsonWriter = TJsonWriterFactory<>::Create(&JsonText);
if (!ensure(FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter)))
{
if (OutFailReason)
{
*OutFailReason = LOCTEXT("FailedToWriteDescriptor", "Failed to write plugin descriptor content");
}
return false;
}
}
#if WITH_EDITOR
CachedJson = JsonObject;
#endif
return PluginDescriptor::WriteFile(FileName, JsonText, OutFailReason);
}
else
{
// Plugin file doesn't exist so just write it.
return Save(FileName, OutFailReason);
}
}
bool FPluginDescriptor::UpdatePluginFile(const FString& FileName, FText& OutFailReason) const
{
return UpdatePluginFile(FileName, &OutFailReason);
}
bool FPluginDescriptor::SupportsTargetPlatform(const FString& Platform) const
{
if (bHasExplicitPlatforms)
{
return SupportedTargetPlatforms.Contains(Platform);
}
else
{
return SupportedTargetPlatforms.Num() == 0 || SupportedTargetPlatforms.Contains(Platform);
}
}
#undef LOCTEXT_NAMESPACE