// 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 struct TJsonFieldType; template <> struct TJsonFieldType { static const EJson Value = EJson::Number; }; template <> struct TJsonFieldType { static const EJson Value = EJson::String; }; template <> struct TJsonFieldType { static const EJson Value = EJson::Boolean; }; template <> struct TJsonFieldType>> { static const EJson Value = EJson::Array; }; template <> struct TJsonFieldType> { static const EJson Value = EJson::Object; }; template void GetJsonValue(T& OutVal, const TSharedPtr& JsonValue, const TCHAR* Outer) { if (JsonValue->Type != TJsonFieldType::Value) { FUHTMessage(GManifestFilename).Throwf(TEXT("'%s' is the wrong type"), Outer); } JsonValue->AsArgumentType(OutVal); } template void GetJsonFieldValue(T& OutVal, const TSharedPtr& JsonObject, const TCHAR* FieldName, const TCHAR* Outer) { TSharedPtr* JsonValue = JsonObject->Values.Find(FieldName); if (!JsonValue) { FUHTMessage(GManifestFilename).Throwf(TEXT("Unable to find field '%s' in '%s'"), FieldName, Outer); } if ((*JsonValue)->Type != TJsonFieldType::Value) { FUHTMessage(GManifestFilename).Throwf(TEXT("Field '%s' in '%s' is the wrong type"), Outer); } (*JsonValue)->AsArgumentType(OutVal); } void ProcessHeaderArray(FString* OutStringArray, const TArray>& 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 RootObject = TSharedPtr(); TSharedRef> Reader = TJsonReaderFactory::Create(Json); if (!FJsonSerializer::Deserialize(Reader, RootObject)) { FUHTMessage(Filename).Throwf(TEXT("Manifest is malformed: %s"), *Filename); } TArray> 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& Module : ModulesArray) { const TSharedPtr& ModuleObj = Module->AsObject(); TArray> ClassesHeaders; TArray> PublicHeaders; TArray> InternalHeaders; TArray> 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; }