Files
UnrealEngineUWP/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenManifest.cpp
Ben Marsh 4ba423868f Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3209340)
#lockdown Nick.Penwarden
#rb none

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

Change 3209340 on 2016/11/23 by Ben.Marsh

	Convert UE4 codebase to an "include what you use" model - where every header just includes the dependencies it needs, rather than every source file including large monolithic headers like Engine.h and UnrealEd.h.

	Measured full rebuild times around 2x faster using XGE on Windows, and improvements of 25% or more for incremental builds and full rebuilds on most other platforms.

	  * Every header now includes everything it needs to compile.
	        * There's a CoreMinimal.h header that gets you a set of ubiquitous types from Core (eg. FString, FName, TArray, FVector, etc...). Most headers now include this first.
	        * There's a CoreTypes.h header that sets up primitive UE4 types and build macros (int32, PLATFORM_WIN64, etc...). All headers in Core include this first, as does CoreMinimal.h.
	  * Every .cpp file includes its matching .h file first.
	        * This helps validate that each header is including everything it needs to compile.
	  * No engine code includes a monolithic header such as Engine.h or UnrealEd.h any more.
	        * You will get a warning if you try to include one of these from the engine. They still exist for compatibility with game projects and do not produce warnings when included there.
	        * There have only been minor changes to our internal games down to accommodate these changes. The intent is for this to be as seamless as possible.
	  * No engine code explicitly includes a precompiled header any more.
	        * We still use PCHs, but they're force-included on the compiler command line by UnrealBuildTool instead. This lets us tune what they contain without breaking any existing include dependencies.
	        * PCHs are generated by a tool to get a statistical amount of coverage for the source files using it, and I've seeded the new shared PCHs to contain any header included by > 15% of source files.

	Tool used to generate this transform is at Engine\Source\Programs\IncludeTool.

[CL 3209342 by Ben Marsh in Main branch]
2016-11-23 15:48:37 -05:00

545 lines
21 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "BlueprintNativeCodeGenManifest.h"
#include "UObject/Package.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/App.h"
#include "Engine/Blueprint.h"
#include "Dom/JsonObject.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "IBlueprintCompilerCppBackendModule.h"
#include "JsonObjectConverter.h"
DEFINE_LOG_CATEGORY_STATIC(LogNativeCodeGenManifest, Log, All);
/*******************************************************************************
* BlueprintNativeCodeGenManifestImpl
******************************************************************************/
namespace BlueprintNativeCodeGenManifestImpl
{
static const int64 CPF_NoFlags = 0x00;
static const FString ManifestFileExt = TEXT(".BpCodeGenManifest.json");
static const FString CppFileExt = TEXT(".cpp");
static const FString HeaderFileExt = TEXT(".h");
static const FString HeaderSubDir = TEXT("Public");
static const FString CppSubDir = TEXT("Private");
static const FString ModuleBuildFileExt = TEXT(".Build.cs");
static const FString PreviewFilePostfix = TEXT("-Preview");
static const FString PluginFileExt = TEXT(".uplugin");
static const FString SourceSubDir = TEXT("Source");
static const FString EditorModulePostfix = TEXT("Editor");
/**
* Populates the provided manifest object with data from the specified file.
*
* @param FilePath A json file path, denoting the file you want loaded and serialized in.
* @param Manifest The target object that you want filled out with data from the file.
* @return True if the manifest was successfully loaded, otherwise false.
*/
static bool LoadManifest(const FString& FilePath, FBlueprintNativeCodeGenManifest* Manifest);
/**
* Helper function that homogenizes file/directory paths so that they can be
* compared for equivalence against others.
*
* @param DirectoryPath The path that you want sanitized.
* @return A equivalent file/directory path, standardized for comparisons.
*/
static FString GetComparibleDirPath(const FString& DirectoryPath);
/**
* Retrieves the sub-directory for either header or cpp source files
* (depending on which was requested).
*
* @param SourceType Defines the type of source file to return for (header or cpp).
* @return A directory name for the specified source file type.
*/
static const FString& GetSourceSubDir(const FBlueprintNativeCodeGenPaths::ESourceFileType SourceType);
/**
* Retrieves the extension for either header or cpp source files (depending
* on which was requested).
*
* @param SourceType Defines the type of source file to return for (header or cpp).
* @return A file extension (including the leading dot), for the specified source file type.
*/
static const FString& GetSourceFileExt(const FBlueprintNativeCodeGenPaths::ESourceFileType SourceType);
/**
* Constructs a source file path for the specified asset.
*
* @param TargetPaths Specified the destination directory for the file.
* @param Asset The asset you want a header file for.
* @param SourceType Defines the type of source file to generate for (header or cpp).
* @return A target file path for the specified asset to save a header to.
*/
static FString GenerateSourceFileSavePath(const FBlueprintNativeCodeGenPaths& TargetPaths, const FAssetData& Asset, const FBlueprintNativeCodeGenPaths::ESourceFileType SourceType);
/**
*
*
* @param TargetPaths
* @param Asset
* @return
*/
static FString GenerateUnconvertedWrapperPath(const FBlueprintNativeCodeGenPaths& TargetPaths, const FAssetData& Asset);
/**
* Coordinates with the code-gen backend, to produce a base filename (one
* without a file extension).
*
* @param Asset The asset you want a filename for.
* @return A filename (without extension) that matches the #include statements generated by the backend.
*/
static FString GetBaseFilename(const FAssetData& Asset);
/**
* Collects native packages (which reflect distinct modules) that the
* specified object relies upon.
*
* @param AssetObj The object you want dependencies for.
* @param DependenciesOut An output array, which will be filled out with module packages that the target object relies upon.
* @return False if the function failed to collect dependencies for the specified object.
*/
static bool GatherModuleDependencies(const UObject* AssetObj, TArray<UPackage*>& DependenciesOut);
/**
* Obtains the reflected name for the native field (class/enum/struct) that
* we'll generate to replace the specified asset.
*
* @param Asset The asset you want a name from.
* @return The name of the asset field (class/enum/struct).
*/
static FString GetFieldName(const FAssetData& Asset);
/**
* The object returned by FAssetData::GetAsset() doesn't always give us the
* target object that will be replaced (for Blueprint's, it would be the
* class instead). So this helper function will suss out the right object
* for you.
*
* @param Asset The asset you want an object for.
* @return A pointer to the targeted object from the asset's package.
*/
static UField* GetTargetAssetObject(const FAssetData& Asset);
/**
* Returns the object path for the field from the specified asset's package
* that is being replaced (Asset.ObjectPath will not suffice, as that
* does not always reflect the object that is being replaced).
*
* @param Asset The asset you want an object-path for.
* @return An object-path for the target field-object within the asset's package.
*/
static FString GetTargetObjectPath(const FAssetData& Asset);
}
//------------------------------------------------------------------------------
static bool BlueprintNativeCodeGenManifestImpl::LoadManifest(const FString& FilePath, FBlueprintNativeCodeGenManifest* Manifest)
{
FString ManifestStr;
if (FFileHelper::LoadFileToString(ManifestStr, *FilePath))
{
TSharedRef< TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(ManifestStr);
TSharedPtr<FJsonObject> JsonObject;
if (FJsonSerializer::Deserialize(JsonReader, JsonObject))
{
return FJsonObjectConverter::JsonObjectToUStruct<FBlueprintNativeCodeGenManifest>(JsonObject.ToSharedRef(), Manifest,
/*CheckFlags =*/CPF_NoFlags, /*SkipFlags =*/CPF_NoFlags);
}
}
return false;
}
//------------------------------------------------------------------------------
static const FString& BlueprintNativeCodeGenManifestImpl::GetSourceSubDir(const FBlueprintNativeCodeGenPaths::ESourceFileType SourceType)
{
return (SourceType == FBlueprintNativeCodeGenPaths::HFile) ? HeaderSubDir : CppSubDir;
}
//------------------------------------------------------------------------------
static const FString& BlueprintNativeCodeGenManifestImpl::GetSourceFileExt(const FBlueprintNativeCodeGenPaths::ESourceFileType SourceType)
{
return (SourceType == FBlueprintNativeCodeGenPaths::HFile) ? HeaderFileExt : CppFileExt;
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GenerateSourceFileSavePath(const FBlueprintNativeCodeGenPaths& TargetPaths, const FAssetData& Asset, const FBlueprintNativeCodeGenPaths::ESourceFileType SourceType)
{
return FPaths::Combine(*TargetPaths.RuntimeSourceDir(SourceType), *GetBaseFilename(Asset)) + GetSourceFileExt(SourceType);
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GenerateUnconvertedWrapperPath(const FBlueprintNativeCodeGenPaths& TargetPaths, const FAssetData& Asset)
{
const FBlueprintNativeCodeGenPaths::ESourceFileType WrapperFileType = FBlueprintNativeCodeGenPaths::HFile;
return FPaths::Combine(*TargetPaths.RuntimeSourceDir(WrapperFileType), *GetBaseFilename(Asset)) + GetSourceFileExt(WrapperFileType);
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GetBaseFilename(const FAssetData& Asset)
{
IBlueprintCompilerCppBackendModule& CodeGenBackend = (IBlueprintCompilerCppBackendModule&)IBlueprintCompilerCppBackendModule::Get();
return CodeGenBackend.ConstructBaseFilename(Asset.GetAsset());
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GetComparibleDirPath(const FString& DirectoryPath)
{
FString NormalizedPath = DirectoryPath;
const FString PathDelim = TEXT("/");
if (!NormalizedPath.EndsWith(PathDelim))
{
// to account for the case where the relative path would resolve to X:
// (when we want "X:/")... ConvertRelativePathToFull() leaves the
// trailing slash, and NormalizeDirectoryName() will remove it (if it is
// not a drive letter)
NormalizedPath += PathDelim;
}
if (FPaths::IsRelative(NormalizedPath))
{
NormalizedPath = FPaths::ConvertRelativePathToFull(NormalizedPath);
}
FPaths::NormalizeDirectoryName(NormalizedPath);
return NormalizedPath;
}
//------------------------------------------------------------------------------
static bool BlueprintNativeCodeGenManifestImpl::GatherModuleDependencies(const UObject* AssetObj, TArray<UPackage*>& DependenciesOut)
{
UPackage* AssetPackage = AssetObj->GetOutermost();
const FLinkerLoad* PkgLinker = FLinkerLoad::FindExistingLinkerForPackage(AssetPackage);
const bool bFoundLinker = (PkgLinker != nullptr);
if (ensureMsgf(bFoundLinker, TEXT("Failed to identify the asset package that '%s' belongs to."), *AssetObj->GetName()))
{
for (const FObjectImport& PkgImport : PkgLinker->ImportMap)
{
if (PkgImport.ClassName != NAME_Package)
{
continue;
}
UPackage* DependentPackage = FindObject<UPackage>(/*Outer =*/nullptr, *PkgImport.ObjectName.ToString(), /*ExactClass =*/true);
if (DependentPackage == nullptr)
{
continue;
}
// we want only native packages, ones that are not editor-only
if ((DependentPackage->GetPackageFlags() & (PKG_CompiledIn | PKG_EditorOnly | PKG_Developer)) == PKG_CompiledIn)
{
DependenciesOut.AddUnique(DependentPackage);// PkgImport.ObjectName.ToString());
}
}
}
return bFoundLinker;
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GetFieldName(const FAssetData& Asset)
{
UField* AssetField = GetTargetAssetObject(Asset);
return (AssetField != nullptr) ? AssetField->GetName() : TEXT("");
}
//------------------------------------------------------------------------------
static UField* BlueprintNativeCodeGenManifestImpl::GetTargetAssetObject(const FAssetData& Asset)
{
UObject* AssetObj = Asset.GetAsset();
UField* AssetField = nullptr;
if (UBlueprint* BlueprintAsset = Cast<UBlueprint>(AssetObj))
{
AssetField = BlueprintAsset->GeneratedClass;
if (!AssetField)
{
UE_LOG(LogNativeCodeGenManifest, Warning, TEXT("null BPGC in %s"), *BlueprintAsset->GetPathName());
}
}
else
{
// only other asset types that we should be converting are enums and
// structs (both UFields)
AssetField = CastChecked<UField>(AssetObj);
}
return AssetField;
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GetTargetObjectPath(const FAssetData& Asset)
{
UField* AssetField = GetTargetAssetObject(Asset);
return (AssetField != nullptr) ? AssetField->GetPathName() : TEXT("");
}
/*******************************************************************************
* FConvertedAssetRecord
******************************************************************************/
//------------------------------------------------------------------------------
FConvertedAssetRecord::FConvertedAssetRecord(const FAssetData& AssetInfo, const FBlueprintNativeCodeGenPaths& TargetPaths)
: AssetType(AssetInfo.GetClass())
, TargetObjPath(BlueprintNativeCodeGenManifestImpl::GetTargetObjectPath(AssetInfo))
{
GeneratedCppPath = BlueprintNativeCodeGenManifestImpl::GenerateSourceFileSavePath(TargetPaths, AssetInfo, FBlueprintNativeCodeGenPaths::CppFile);
GeneratedHeaderPath = BlueprintNativeCodeGenManifestImpl::GenerateSourceFileSavePath(TargetPaths, AssetInfo, FBlueprintNativeCodeGenPaths::HFile);
}
//------------------------------------------------------------------------------
FStringAssetReference FConvertedAssetRecord::ToAssetRef() const
{
return FStringAssetReference(TargetObjPath);
}
/*******************************************************************************
* FUnconvertedDependencyRecord
******************************************************************************/
//------------------------------------------------------------------------------
FUnconvertedDependencyRecord::FUnconvertedDependencyRecord(const FString& InGeneratedWrapperPath)
: GeneratedWrapperPath(InGeneratedWrapperPath)
{
}
/*******************************************************************************
* FBlueprintNativeCodeGenPaths
******************************************************************************/
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::GetDefaultManifestPath()
{
return FApp::GetGameName() + BlueprintNativeCodeGenManifestImpl::ManifestFileExt;
}
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenPaths::FBlueprintNativeCodeGenPaths(const FString& PluginNameIn, const FString& TargetDirIn, const FString& ManifestPathIn)
: TargetDir(TargetDirIn)
, PluginName(PluginNameIn)
, ManifestPath(ManifestPathIn)
{
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::ManifestFilePath() const
{
return ManifestPath;
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::PluginRootDir() const
{
return TargetDir;
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::PluginFilePath() const
{
return FPaths::Combine(*PluginRootDir(), *PluginName) + BlueprintNativeCodeGenManifestImpl::PluginFileExt;;
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::PluginSourceDir() const
{
return FPaths::Combine(*PluginRootDir(), *BlueprintNativeCodeGenManifestImpl::SourceSubDir);
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::RuntimeModuleDir() const
{
return FPaths::Combine(*PluginSourceDir(), *RuntimeModuleName());
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::RuntimeModuleName() const
{
return PluginName;
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::RuntimeBuildFile() const
{
return FPaths::Combine(*RuntimeModuleDir(), *RuntimeModuleName()) + BlueprintNativeCodeGenManifestImpl::ModuleBuildFileExt;
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::RuntimeSourceDir(ESourceFileType SourceType) const
{
return FPaths::Combine(*RuntimeModuleDir(), *BlueprintNativeCodeGenManifestImpl::GetSourceSubDir(SourceType));
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::RuntimeModuleFile(ESourceFileType SourceType) const
{
return FPaths::Combine(*RuntimeSourceDir(SourceType), *RuntimeModuleName()) + BlueprintNativeCodeGenManifestImpl::GetSourceFileExt(SourceType);
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenPaths::RuntimePCHFilename() const
{
return FPaths::GetCleanFilename(RuntimeModuleFile(HFile));
}
/*******************************************************************************
* FBlueprintNativeCodeGenManifest
******************************************************************************/
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenManifest::FBlueprintNativeCodeGenManifest()
{
}
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenManifest::FBlueprintNativeCodeGenManifest(const FString& InPluginName, const FString& InOutputDir)
{
using namespace BlueprintNativeCodeGenManifestImpl;
PluginName = InPluginName;
OutputDir = InOutputDir;
if (FPaths::IsRelative(OutputDir))
{
FPaths::MakePathRelativeTo(OutputDir, *FPaths::GameDir());
}
if (!ensure(!OutputDir.IsEmpty()))
{
OutputDir = FPaths::Combine(*FPaths::GameIntermediateDir(), *PluginName);
}
FString ManifestFilename = FBlueprintNativeCodeGenPaths::GetDefaultManifestPath();
ManifestFilePath = FPaths::Combine(*GetTargetDir(), *ManifestFilename);
}
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenManifest::FBlueprintNativeCodeGenManifest(const FString& ManifestFilePathIn)
: ManifestFilePath(ManifestFilePathIn)
{
ensureAlwaysMsgf(BlueprintNativeCodeGenManifestImpl::LoadManifest(ManifestFilePathIn, this), TEXT("Missing Manifest for Blueprint code generation: %s"), *ManifestFilePath );
}
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenPaths FBlueprintNativeCodeGenManifest::GetTargetPaths() const
{
return FBlueprintNativeCodeGenPaths(PluginName, GetTargetDir(), ManifestFilePath);
}
//------------------------------------------------------------------------------
FConvertedAssetRecord& FBlueprintNativeCodeGenManifest::CreateConversionRecord(const FAssetId Key, const FAssetData& AssetInfo)
{
// It is an error to consider a record converted and unconverted. This is probably not negotiable. In order to leave the door
// open for parallelism we need to be able to trivially and consistently determine whether an asset will be converted without
// discovering things as we go.
check(UnconvertedDependencies.Find(Key) == nullptr);
// Similarly we should not convert things over and over and over:
if (FConvertedAssetRecord* Existing = ConvertedAssets.Find(Key))
{
return *Existing;
}
const FBlueprintNativeCodeGenPaths TargetPaths = GetTargetPaths();
UClass* AssetType = AssetInfo.GetClass();
// load the asset (if it isn't already)
const UObject* AssetObj = AssetInfo.GetAsset();
FConvertedAssetRecord* ConversionRecord = &ConvertedAssets.Add(Key, FConvertedAssetRecord(AssetInfo, TargetPaths));
return *ConversionRecord;
}
//------------------------------------------------------------------------------
FUnconvertedDependencyRecord& FBlueprintNativeCodeGenManifest::CreateUnconvertedDependencyRecord(const FAssetId UnconvertedAssetKey, const FAssetData& AssetInfo)
{
// It is an error to create a record multiple times because it is not obvious what the client wants to happen.
check(ConvertedAssets.Find(UnconvertedAssetKey) == nullptr);
if (FUnconvertedDependencyRecord* Existing = UnconvertedDependencies.Find(UnconvertedAssetKey))
{
return *Existing;
}
const FBlueprintNativeCodeGenPaths TargetPaths = GetTargetPaths();
FUnconvertedDependencyRecord* RecordPtr = &UnconvertedDependencies.Add(UnconvertedAssetKey, FUnconvertedDependencyRecord(BlueprintNativeCodeGenManifestImpl::GenerateUnconvertedWrapperPath(TargetPaths, AssetInfo)));
return *RecordPtr;
}
//------------------------------------------------------------------------------
void FBlueprintNativeCodeGenManifest::GatherModuleDependencies(UPackage* Package)
{
BlueprintNativeCodeGenManifestImpl::GatherModuleDependencies(Package, ModuleDependencies);
}
//------------------------------------------------------------------------------
bool FBlueprintNativeCodeGenManifest::Save(int32 Id) const
{
FString FullFilename;
if (Id != -1)
{
FullFilename = *(ManifestFilePath + FString::FromInt(Id));
}
else
{
FullFilename = *ManifestFilePath;
}
const TCHAR* Filename = *FullFilename;
TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
if (FJsonObjectConverter::UStructToJsonObject(FBlueprintNativeCodeGenManifest::StaticStruct(), this, JsonObject,
/*CheckFlags =*/BlueprintNativeCodeGenManifestImpl::CPF_NoFlags, /*SkipFlags =*/BlueprintNativeCodeGenManifestImpl::CPF_NoFlags))
{
FString FileContents;
TSharedRef< TJsonWriter<> > JsonWriter = TJsonWriterFactory<>::Create(&FileContents);
if (FJsonSerializer::Serialize(JsonObject, JsonWriter))
{
JsonWriter->Close();
return FFileHelper::SaveStringToFile(FileContents, Filename);
}
}
return false;
}
void FBlueprintNativeCodeGenManifest::Merge(const FBlueprintNativeCodeGenManifest& OtherManifest)
{
for (auto& Entry : OtherManifest.ModuleDependencies)
{
ModuleDependencies.AddUnique(Entry);
}
for (auto& Entry : OtherManifest.ConvertedAssets)
{
ConvertedAssets.Add(Entry.Key, Entry.Value);
}
for (auto& Entry : OtherManifest.UnconvertedDependencies)
{
UnconvertedDependencies.Add(Entry.Key, Entry.Value);
}
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenManifest::GetTargetDir() const
{
FString TargetPath = OutputDir;
if (FPaths::IsRelative(TargetPath))
{
TargetPath = FPaths::ConvertRelativePathToFull(FPaths::GameDir(), TargetPath);
TargetPath = FPaths::ConvertRelativePathToFull(TargetPath);
}
return TargetPath;
}
//------------------------------------------------------------------------------
void FBlueprintNativeCodeGenManifest::Clear()
{
ConvertedAssets.Empty();
}