You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb devin.doucette #preflight 6231e536e2541b4ff3af6cd2 #ROBOMERGE-AUTHOR: steve.robb #ROBOMERGE-SOURCE: CL 19405353 via CL 19408955 via CL 19419621 via CL 19419706 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v930-19419903) [CL 19420584 by steve robb in ue5-main branch]
275 lines
12 KiB
C++
275 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Manifest.h"
|
|
#include "Exceptions.h"
|
|
#include "IScriptGeneratorPluginInterface.h"
|
|
#include "UnrealHeaderTool.h"
|
|
#include "Misc/DateTime.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "UnrealHeaderToolGlobals.h"
|
|
#include "Serialization/JsonTypes.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
|
|
FString GManifestFilename;
|
|
|
|
namespace
|
|
{
|
|
template <typename T> struct TJsonFieldType;
|
|
template <> struct TJsonFieldType<double> { static const EJson Value = EJson::Number; };
|
|
template <> struct TJsonFieldType<FString> { static const EJson Value = EJson::String; };
|
|
template <> struct TJsonFieldType<bool> { static const EJson Value = EJson::Boolean; };
|
|
template <> struct TJsonFieldType<TArray<TSharedPtr<FJsonValue>>> { static const EJson Value = EJson::Array; };
|
|
template <> struct TJsonFieldType<TSharedPtr<FJsonObject>> { static const EJson Value = EJson::Object; };
|
|
|
|
template <typename T>
|
|
void GetJsonValue(T& OutVal, const TSharedPtr<FJsonValue>& JsonValue, const TCHAR* Outer)
|
|
{
|
|
if (JsonValue->Type != TJsonFieldType<T>::Value)
|
|
{
|
|
FUHTMessage(GManifestFilename).Throwf(TEXT("'%s' is the wrong type"), Outer);
|
|
}
|
|
|
|
JsonValue->AsArgumentType(OutVal);
|
|
}
|
|
|
|
template <typename T>
|
|
void GetJsonFieldValue(T& OutVal, const TSharedPtr<FJsonObject>& JsonObject, const TCHAR* FieldName, const TCHAR* Outer)
|
|
{
|
|
TSharedPtr<FJsonValue>* JsonValue = JsonObject->Values.Find(FieldName);
|
|
|
|
if (!JsonValue)
|
|
{
|
|
FUHTMessage(GManifestFilename).Throwf(TEXT("Unable to find field '%s' in '%s'"), FieldName, Outer);
|
|
}
|
|
|
|
if ((*JsonValue)->Type != TJsonFieldType<T>::Value)
|
|
{
|
|
FUHTMessage(GManifestFilename).Throwf(TEXT("Field '%s' in '%s' is the wrong type"), Outer);
|
|
}
|
|
|
|
(*JsonValue)->AsArgumentType(OutVal);
|
|
}
|
|
|
|
void ProcessHeaderArray(FString* OutStringArray, const TArray<TSharedPtr<FJsonValue>>& InJsonArray, const TCHAR* Outer)
|
|
{
|
|
for (int32 Index = 0, Count = InJsonArray.Num(); Index != Count; ++Index)
|
|
{
|
|
GetJsonValue(*OutStringArray++, InJsonArray[Index], *FString::Printf(TEXT("%s[%d]"), Outer, Index));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FManifest FManifest::LoadFromFile(const FString& Filename)
|
|
{
|
|
GManifestFilename = Filename;
|
|
FManifest Result;
|
|
FString FilenamePath = FPaths::GetPath(Filename);
|
|
FString Json;
|
|
|
|
if (!FFileHelper::LoadFileToString(Json, *Filename))
|
|
{
|
|
FUHTMessage(Filename).Throwf(TEXT("Unable to load manifest: %s"), *Filename);
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> RootObject = TSharedPtr<FJsonObject>();
|
|
TSharedRef<TJsonReader<TCHAR>> Reader = TJsonReaderFactory<TCHAR>::Create(Json);
|
|
|
|
if (!FJsonSerializer::Deserialize(Reader, RootObject))
|
|
{
|
|
FUHTMessage(Filename).Throwf(TEXT("Manifest is malformed: %s"), *Filename);
|
|
}
|
|
|
|
TArray<TSharedPtr<FJsonValue>> ModulesArray;
|
|
|
|
GetJsonFieldValue(Result.IsGameTarget, RootObject, TEXT("IsGameTarget"), TEXT("{manifest root}"));
|
|
GetJsonFieldValue(Result.RootLocalPath, RootObject, TEXT("RootLocalPath"), TEXT("{manifest root}"));
|
|
Result.RootBuildPath = Result.RootLocalPath + FPlatformMisc::GetDefaultPathSeparator();
|
|
GetJsonFieldValue(Result.TargetName, RootObject, TEXT("TargetName"), TEXT("{manifest root}"));
|
|
GetJsonFieldValue(Result.ExternalDependenciesFile, RootObject, TEXT("ExternalDependenciesFile"), TEXT("{manifest root}"));
|
|
GetJsonFieldValue(ModulesArray, RootObject, TEXT("Modules"), TEXT("{manifest root}"));
|
|
|
|
UE_LOG(LogCompile, Log, TEXT("Loaded manifest: %s"), *Filename);
|
|
UE_LOG(LogCompile, Log, TEXT("Manifest.IsGameTarget=%s"), Result.IsGameTarget ? TEXT("True") : TEXT("False"));
|
|
UE_LOG(LogCompile, Log, TEXT("Manifest.RootLocalPath=%s"), *Result.RootLocalPath);
|
|
UE_LOG(LogCompile, Log, TEXT("Manifest.RootBuildPath=%s"), *Result.RootBuildPath);
|
|
UE_LOG(LogCompile, Log, TEXT("Manifest.TargetName=%s"), *Result.TargetName);
|
|
UE_LOG(LogCompile, Log, TEXT("Manifest.Modules=%d"), ModulesArray.Num());
|
|
|
|
Result.RootLocalPath = FPaths::ConvertRelativePathToFull(FilenamePath, Result.RootLocalPath);
|
|
Result.RootBuildPath = FPaths::ConvertRelativePathToFull(FilenamePath, Result.RootBuildPath);
|
|
|
|
// Ensure directories end with a slash, because this aids their use with FPaths::MakePathRelativeTo.
|
|
if (!Result.RootLocalPath.EndsWith(TEXT("/")))
|
|
{
|
|
Result.RootLocalPath += TEXT("/");
|
|
}
|
|
|
|
if (!Result.RootBuildPath.EndsWith(TEXT("/")))
|
|
{
|
|
Result.RootBuildPath += TEXT("/");
|
|
}
|
|
|
|
int32 ModuleIndex = 0;
|
|
|
|
for (const TSharedPtr<FJsonValue>& Module : ModulesArray)
|
|
{
|
|
const TSharedPtr<FJsonObject>& ModuleObj = Module->AsObject();
|
|
|
|
TArray<TSharedPtr<FJsonValue>> ClassesHeaders;
|
|
TArray<TSharedPtr<FJsonValue>> PublicHeaders;
|
|
TArray<TSharedPtr<FJsonValue>> InternalHeaders;
|
|
TArray<TSharedPtr<FJsonValue>> PrivateHeaders;
|
|
|
|
Result.Modules.AddZeroed();
|
|
FManifestModule& KnownModule = Result.Modules.Last();
|
|
|
|
FString Outer = FString::Printf(TEXT("Modules[%d]"), ModuleIndex);
|
|
FString GeneratedCodeVersionString;
|
|
GetJsonFieldValue(KnownModule.Name, ModuleObj, TEXT("Name"), *Outer);
|
|
GetJsonFieldValue(KnownModule.BaseDirectory, ModuleObj, TEXT("BaseDirectory"), *Outer);
|
|
GetJsonFieldValue(KnownModule.IncludeBase, ModuleObj, TEXT("IncludeBase"), *Outer);
|
|
GetJsonFieldValue(KnownModule.GeneratedIncludeDirectory, ModuleObj, TEXT("OutputDirectory"), *Outer);
|
|
GetJsonFieldValue(KnownModule.SaveExportedHeaders, ModuleObj, TEXT("SaveExportedHeaders"), *Outer);
|
|
GetJsonFieldValue(ClassesHeaders, ModuleObj, TEXT("ClassesHeaders"), *Outer);
|
|
GetJsonFieldValue(PublicHeaders, ModuleObj, TEXT("PublicHeaders"), *Outer);
|
|
GetJsonFieldValue(InternalHeaders, ModuleObj, TEXT("InternalHeaders"), *Outer);
|
|
GetJsonFieldValue(PrivateHeaders, ModuleObj, TEXT("PrivateHeaders"), *Outer);
|
|
GetJsonFieldValue(KnownModule.GeneratedCPPFilenameBase, ModuleObj, TEXT("GeneratedCPPFilenameBase"), *Outer);
|
|
GetJsonFieldValue(GeneratedCodeVersionString, ModuleObj, TEXT("UHTGeneratedCodeVersion"), *Outer);
|
|
KnownModule.GeneratedCodeVersion = ToGeneratedCodeVersion(GeneratedCodeVersionString);
|
|
|
|
FString ModuleTypeText;
|
|
GetJsonFieldValue(ModuleTypeText, ModuleObj, TEXT("ModuleType"), *Outer);
|
|
KnownModule.ModuleType = EBuildModuleType::Parse(*ModuleTypeText);
|
|
|
|
FString OverrideModuleTypeText;
|
|
GetJsonFieldValue(OverrideModuleTypeText, ModuleObj, TEXT("OverrideModuleType"), *Outer);
|
|
KnownModule.OverrideModuleType = EPackageOverrideType::Parse(*OverrideModuleTypeText);
|
|
|
|
KnownModule.LongPackageName = FPackageName::ConvertToLongScriptPackageName(*KnownModule.Name);
|
|
|
|
// Convert relative paths
|
|
KnownModule.BaseDirectory = FPaths::ConvertRelativePathToFull(FilenamePath, KnownModule.BaseDirectory);
|
|
KnownModule.IncludeBase = FPaths::ConvertRelativePathToFull(FilenamePath, KnownModule.IncludeBase);
|
|
KnownModule.GeneratedIncludeDirectory = FPaths::ConvertRelativePathToFull(FilenamePath, KnownModule.GeneratedIncludeDirectory);
|
|
KnownModule.GeneratedCPPFilenameBase = FPaths::ConvertRelativePathToFull(FilenamePath, KnownModule.GeneratedCPPFilenameBase);
|
|
|
|
// Ensure directories end with a slash, because this aids their use with FPaths::MakePathRelativeTo.
|
|
if (!KnownModule.BaseDirectory .EndsWith(TEXT("/"))) { KnownModule.BaseDirectory .AppendChar(TEXT('/')); }
|
|
if (!KnownModule.IncludeBase .EndsWith(TEXT("/"))) { KnownModule.IncludeBase .AppendChar(TEXT('/')); }
|
|
if (!KnownModule.GeneratedIncludeDirectory.EndsWith(TEXT("/"))) { KnownModule.GeneratedIncludeDirectory.AppendChar(TEXT('/')); }
|
|
|
|
KnownModule.PublicUObjectClassesHeaders.AddZeroed(ClassesHeaders .Num());
|
|
KnownModule.PublicUObjectHeaders .AddZeroed(PublicHeaders .Num());
|
|
KnownModule.InternalUObjectHeaders .AddZeroed(InternalHeaders.Num());
|
|
KnownModule.PrivateUObjectHeaders .AddZeroed(PrivateHeaders .Num());
|
|
|
|
ProcessHeaderArray(KnownModule.PublicUObjectClassesHeaders.GetData(), ClassesHeaders , *(Outer + TEXT(".ClassHeaders")));
|
|
ProcessHeaderArray(KnownModule.PublicUObjectHeaders .GetData(), PublicHeaders , *(Outer + TEXT(".PublicHeaders" )));
|
|
ProcessHeaderArray(KnownModule.InternalUObjectHeaders .GetData(), InternalHeaders, *(Outer + TEXT(".InternalHeaders")));
|
|
ProcessHeaderArray(KnownModule.PrivateUObjectHeaders .GetData(), PrivateHeaders , *(Outer + TEXT(".PrivateHeaders")));
|
|
|
|
// Sort the headers alphabetically. This is just to add determinism to the compilation dependency order, since we currently
|
|
// don't rely on explicit includes (but we do support 'dependson')
|
|
// @todo uht: Ideally, we should sort these by sensical order before passing them in -- or better yet, follow include statements ourselves in here.
|
|
KnownModule.PublicUObjectClassesHeaders.Sort();
|
|
KnownModule.PublicUObjectHeaders .Sort();
|
|
KnownModule.InternalUObjectHeaders .Sort();
|
|
KnownModule.PrivateUObjectHeaders .Sort();
|
|
|
|
UE_LOG(LogCompile, Log, TEXT(" %s"), *KnownModule.Name);
|
|
UE_LOG(LogCompile, Log, TEXT(" .BaseDirectory=%s"), *KnownModule.BaseDirectory);
|
|
UE_LOG(LogCompile, Log, TEXT(" .IncludeBase=%s"), *KnownModule.IncludeBase);
|
|
UE_LOG(LogCompile, Log, TEXT(" .GeneratedIncludeDirectory=%s"), *KnownModule.GeneratedIncludeDirectory);
|
|
UE_LOG(LogCompile, Log, TEXT(" .SaveExportedHeaders=%s"), KnownModule.SaveExportedHeaders ? TEXT("True") : TEXT("False"));
|
|
UE_LOG(LogCompile, Log, TEXT(" .GeneratedCPPFilenameBase=%s"), *KnownModule.GeneratedCPPFilenameBase);
|
|
UE_LOG(LogCompile, Log, TEXT(" .ModuleType=%s"), *ModuleTypeText);
|
|
|
|
++ModuleIndex;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FManifestModule::NeedsRegeneration() const
|
|
{
|
|
if (ShouldForceRegeneration())
|
|
{
|
|
return true;
|
|
}
|
|
FString Timestamp;
|
|
const auto& TimestampText = TEXT("Timestamp");
|
|
Timestamp.Empty(GeneratedIncludeDirectory.Len() + UE_ARRAY_COUNT(TimestampText));
|
|
Timestamp += GeneratedIncludeDirectory;
|
|
Timestamp += TEXT("Timestamp");
|
|
|
|
if (!FPaths::FileExists(Timestamp))
|
|
{
|
|
// No timestamp, must regenerate.
|
|
return true;
|
|
}
|
|
|
|
FDateTime TimestampFileLastModify = IFileManager::Get().GetTimeStamp(*Timestamp);
|
|
|
|
for (const FString& Header : PublicUObjectClassesHeaders)
|
|
{
|
|
if (IFileManager::Get().GetTimeStamp(*Header) > TimestampFileLastModify)
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("File %s is newer than last timestamp. Regenerating reflection data for module %s."), *Header, *Name);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (const FString& Header : PublicUObjectHeaders)
|
|
{
|
|
if (IFileManager::Get().GetTimeStamp(*Header) > TimestampFileLastModify)
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("File %s is newer than last timestamp. Regenerating reflection data for module %s."), *Header, *Name);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (const FString& Header : InternalUObjectHeaders)
|
|
{
|
|
if (IFileManager::Get().GetTimeStamp(*Header) > TimestampFileLastModify)
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("File %s is newer than last timestamp. Regenerating reflection data for module %s."), *Header, *Name);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (const FString& Header : PrivateUObjectHeaders)
|
|
{
|
|
if (IFileManager::Get().GetTimeStamp(*Header) > TimestampFileLastModify)
|
|
{
|
|
UE_LOG(LogCompile, Log, TEXT("File %s is newer than last timestamp. Regenerating reflection data for module %s."), *Header, *Name);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// No header is newer than timestamp, no need to regenerate.
|
|
return false;
|
|
}
|
|
|
|
bool FManifestModule::IsCompatibleWith(const FManifestModule& ManifestModule)
|
|
{
|
|
return ModuleType == ManifestModule.ModuleType
|
|
&& SaveExportedHeaders == ManifestModule.SaveExportedHeaders
|
|
&& GeneratedCodeVersion == ManifestModule.GeneratedCodeVersion
|
|
&& Name == ManifestModule.Name
|
|
&& LongPackageName == ManifestModule.LongPackageName
|
|
&& BaseDirectory == ManifestModule.BaseDirectory
|
|
&& IncludeBase == ManifestModule.IncludeBase
|
|
&& GeneratedIncludeDirectory == ManifestModule.GeneratedIncludeDirectory
|
|
&& PublicUObjectClassesHeaders == ManifestModule.PublicUObjectClassesHeaders
|
|
&& PublicUObjectHeaders == ManifestModule.PublicUObjectHeaders
|
|
&& InternalUObjectHeaders == ManifestModule.InternalUObjectHeaders
|
|
&& PrivateUObjectHeaders == ManifestModule.PrivateUObjectHeaders
|
|
&& GeneratedCPPFilenameBase == ManifestModule.GeneratedCPPFilenameBase;
|
|
}
|