Files
UnrealEngineUWP/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenManifest.cpp
Mike Beach b0599cdaa7 (WIP) Setting up the code generation backend so we can override the target PCH.
#codereview Maciej.Mroz

[CL 2714692 by Mike Beach in Main branch]
2015-10-02 12:42:26 -04:00

517 lines
19 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "BlueprintNativeCodeGenPCH.h"
#include "BlueprintNativeCodeGenManifest.h"
#include "NativeCodeGenCommandlineParams.h"
#include "App.h" // for GetGameName()
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "JsonObjectConverter.h"
#include "Serialization/JsonWriter.h"
#include "IBlueprintCompilerCppBackendModule.h" // for ConstructBaseFilename()
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 FallbackPluginName = TEXT("GeneratedBpCode");
static const FString PreviewFilePostfix = TEXT("-Preview");
static const FString PluginFileExt = TEXT(".uplugin");
static const FString SourceSubDir = TEXT("Source");
/**
* 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 the 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);
/**
* Constructs a directory path for the plugin's source files.
*
* @param PluginPath A path to the root directory for the plugin.
* @return A path to the plugin's source directory.
*/
static FString GetTargetSourceDir(const FString& PluginPath);
/**
* Constructs a path for the specified module, within the target plugin.
*
* @param PluginPath A path to the root directory for the plugin.
* @param ModuleName The name of the module that you're generating for the plugin.
* @return A path to the module's directory, within the plugin.
*/
static FString GetModuleDirectory(const FString& PluginPath, const FString& ModuleName);
/**
* Constructs a header path for the specified asset.
*
* @param PluginPath A path to the root directory for the plugin.
* @param Asset The asset you want a header file for.
* @param ModuleName The name of the module that you're generating for the plugin.
* @return A target file path for the specified asset to save a header to.
*/
static FString GenerateHeaderSavePath(const FString& PluginPath, const FAssetData& Asset, const FString& ModuleName);
/**
* Constructs a cpp path for the specified asset.
*
* @param PluginPath A path to the root directory for the plugin.
* @param Asset The asset you want a cpp file for.
* @param ModuleName The name of the module that you're generating for the plugin.
* @return A target file path for the specified asset to save a cpp file to.
*/
static FString GenerateCppSavePath(const FString& PluginPath, const FAssetData& Asset, const FString& ModuleName);
/**
* 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);
}
//------------------------------------------------------------------------------
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 FString BlueprintNativeCodeGenManifestImpl::GetTargetSourceDir(const FString& PluginPath)
{
return FPaths::Combine(*PluginPath, *SourceSubDir);
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GetModuleDirectory(const FString& PluginPath, const FString& ModuleName)
{
return FPaths::Combine(*GetTargetSourceDir(PluginPath), *ModuleName);
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GenerateHeaderSavePath(const FString& PluginPath, const FAssetData& Asset, const FString& ModuleName)
{
return FPaths::Combine( *FPaths::Combine(*GetModuleDirectory(PluginPath, ModuleName), *HeaderSubDir), *GetBaseFilename(Asset) ) + HeaderFileExt;
}
//------------------------------------------------------------------------------
static FString BlueprintNativeCodeGenManifestImpl::GenerateCppSavePath(const FString& PluginPath, const FAssetData& Asset, const FString& ModuleName)
{
return FPaths::Combine( *FPaths::Combine(*GetModuleDirectory(PluginPath, ModuleName), *CppSubDir), *GetBaseFilename(Asset) ) + CppFileExt;
}
//------------------------------------------------------------------------------
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 (ensure(bFoundLinker))
{
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->PackageFlags & (PKG_CompiledIn | PKG_EditorOnly)) == PKG_CompiledIn)
{
DependenciesOut.AddUnique(DependentPackage);// PkgImport.ObjectName.ToString());
}
}
}
return bFoundLinker;
}
/*******************************************************************************
* FConvertedAssetRecord
******************************************************************************/
//------------------------------------------------------------------------------
FConvertedAssetRecord::FConvertedAssetRecord(const FAssetData& AssetInfo, const FString& TargetDir, const FString& ModuleName)
: AssetPtr(AssetInfo.GetAsset())
, AssetType(AssetInfo.GetClass())
, AssetPath(AssetInfo.PackageName.ToString())
, GeneratedHeaderPath(BlueprintNativeCodeGenManifestImpl::GenerateHeaderSavePath(TargetDir, AssetInfo, ModuleName))
, GeneratedCppPath(BlueprintNativeCodeGenManifestImpl::GenerateCppSavePath(TargetDir, AssetInfo, ModuleName))
{
}
//------------------------------------------------------------------------------
bool FConvertedAssetRecord::IsValid()
{
// every conversion will have a header file (interfaces don't have implementation files)
return AssetPtr.IsValid() && !GeneratedHeaderPath.IsEmpty() && (AssetType != nullptr) && !AssetPath.IsEmpty();
}
/*******************************************************************************
* FBlueprintNativeCodeGenManifest
******************************************************************************/
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenManifest::GetDefaultFilename()
{
return FApp::GetGameName() + BlueprintNativeCodeGenManifestImpl::ManifestFileExt;
}
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenManifest::FBlueprintNativeCodeGenManifest(const FString TargetPath)
: OutputDir(TargetPath)
{
if (OutputDir.IsEmpty())
{
OutputDir = FPaths::GameIntermediateDir();
}
// do NOT load from an existing interface, as this is the default
// constructor used by the USTRUCT() system
}
//------------------------------------------------------------------------------
FBlueprintNativeCodeGenManifest::FBlueprintNativeCodeGenManifest(const FNativeCodeGenCommandlineParams& CommandlineParams)
{
using namespace BlueprintNativeCodeGenManifestImpl;
bool const bLoadExistingManifest = !CommandlineParams.bWipeRequested || CommandlineParams.OutputDir.IsEmpty();
PluginName = CommandlineParams.PluginName;
OutputDir = CommandlineParams.OutputDir;
if (FPaths::IsRelative(OutputDir))
{
FPaths::MakePathRelativeTo(OutputDir, *FPaths::GameDir());
}
if (!CommandlineParams.ManifestFilePath.IsEmpty())
{
ManifestFilePath = CommandlineParams.ManifestFilePath;
}
else
{
if (!ensure(!PluginName.IsEmpty()))
{
PluginName = FallbackPluginName;
}
if (!ensure(!OutputDir.IsEmpty()))
{
OutputDir = FPaths::Combine(*FPaths::GameIntermediateDir(), *PluginName);
}
FString ManifestFilename = GetDefaultFilename();
if (CommandlineParams.bPreviewRequested)
{
ManifestFilename.InsertAt(ManifestFilename.Find(ManifestFileExt, ESearchCase::CaseSensitive, ESearchDir::FromEnd), PreviewFilePostfix);
}
ManifestFilePath = FPaths::Combine(*GetTargetDir(), *ManifestFilename);
}
// incorporate an existing manifest (in case we're only re-converting a
// handful of assets and adding them into an existing module)
if (bLoadExistingManifest && LoadManifest(ManifestFilePath, this))
{
// if they specified a separate plugin path, lets make sure we use that
// over what was found in the existing manifest
if (!CommandlineParams.OutputDir.IsEmpty())
{
if (CommandlineParams.bWipeRequested)
{
OutputDir = CommandlineParams.OutputDir;
}
else
{
const FString ExpectedPath = GetComparibleDirPath(CommandlineParams.OutputDir);
FString TargetPath = GetTargetDir();
TargetPath = GetComparibleDirPath(TargetPath);
if ( !FPaths::IsSamePath(ExpectedPath, TargetPath) )
{
UE_LOG(LogNativeCodeGenManifest, Error,
TEXT("The existing manifest's plugin path does not match what was specified via the commandline."));
}
}
}
const bool bNewPluginNameRequested = !CommandlineParams.PluginName.IsEmpty() && (CommandlineParams.PluginName != PluginName);
if (bNewPluginNameRequested && !CommandlineParams.bPreviewRequested)
{
// delete the old plugin file (if one exists)
IFileManager::Get().Delete(*GetPluginFilePath());
}
// if we were only interested in obtaining the plugin path
if (CommandlineParams.bWipeRequested)
{
PluginName = CommandlineParams.PluginName;
Clear();
}
else
{
if (bNewPluginNameRequested)
{
UE_LOG(LogNativeCodeGenManifest, Warning, TEXT("The specified plugin name (%s) doesn't match the existing one (%s). Overridding with the new name."),
*CommandlineParams.PluginName, *PluginName);
PluginName = CommandlineParams.PluginName;
}
ConstructRecordLookupTable();
}
}
}
//------------------------------------------------------------------------------
TArray<FString> FBlueprintNativeCodeGenManifest::GetDestinationPaths() const
{
const FString TargetPath = GetTargetDir();
TArray<FString> DestPaths;
DestPaths.Add(ManifestFilePath);
DestPaths.Add(BlueprintNativeCodeGenManifestImpl::GetTargetSourceDir(TargetPath));
DestPaths.Add(GetPluginFilePath());
return DestPaths;
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenManifest::GetPluginFilePath() const
{
return FPaths::Combine(*GetTargetDir(), *PluginName) + BlueprintNativeCodeGenManifestImpl::PluginFileExt;
}
//------------------------------------------------------------------------------
FString FBlueprintNativeCodeGenManifest::GetModuleFilePath() const
{
return FPaths::Combine( *BlueprintNativeCodeGenManifestImpl::GetModuleDirectory(GetTargetDir(), GetTargetModuleName()), *GetTargetModuleName() ) +
BlueprintNativeCodeGenManifestImpl::ModuleBuildFileExt;
}
//------------------------------------------------------------------------------
FConvertedAssetRecord& FBlueprintNativeCodeGenManifest::CreateConversionRecord(const FAssetData& AssetInfo)
{
const FString AssetPath = AssetInfo.PackageName.ToString();
const FString TargetPath = GetTargetDir();
// load the asset (if it isn't already)
const UObject* AssetObj = AssetInfo.GetAsset();
const FString& ModuleName = GetTargetModuleName();
FConvertedAssetRecord* ConversionRecordPtr = FindConversionRecord(AssetPath);
if (ConversionRecordPtr == nullptr)
{
RecordLookupTable.Add(AssetPath, ConvertedAssets.Num());
ConversionRecordPtr = &ConvertedAssets[ ConvertedAssets.Add(FConvertedAssetRecord(AssetInfo, TargetPath, ModuleName)) ];
}
else if ( !ensure((ConversionRecordPtr->AssetPath == AssetPath) && (ConversionRecordPtr->AssetType == AssetInfo.GetClass())) )
{
UE_LOG(LogNativeCodeGenManifest, Warning,
TEXT("The existing manifest entery for '%s' doesn't match what was expected (asset path and/or type). Updating it to match the asset."),
*AssetPath);
ConversionRecordPtr->AssetPath = AssetPath;
ConversionRecordPtr->AssetType = AssetInfo.GetClass();
}
FConvertedAssetRecord& ConversionRecord = *ConversionRecordPtr;
if (!ConversionRecord.IsValid())
{
// if this was a manifest entry that was loaded from an existing file,
// then it wouldn't have the asset pointer (which is transient data)
if (!ConversionRecord.AssetPtr.IsValid())
{
ConversionRecord.AssetPtr = AssetObj;
}
if (ConversionRecord.GeneratedHeaderPath.IsEmpty())
{
ConversionRecord.GeneratedHeaderPath = BlueprintNativeCodeGenManifestImpl::GenerateHeaderSavePath(TargetPath, AssetInfo, ModuleName);
ConversionRecord.GeneratedCppPath = BlueprintNativeCodeGenManifestImpl::GenerateCppSavePath(TargetPath, AssetInfo, ModuleName);
}
ensure(ConversionRecord.IsValid());
}
return ConversionRecord;
}
//------------------------------------------------------------------------------
FConvertedAssetRecord* FBlueprintNativeCodeGenManifest::FindConversionRecord(const FString& AssetPath, bool bSlowSearch)
{
FConvertedAssetRecord* FoundRecord = nullptr;
if (int32* IndexPtr = RecordLookupTable.Find(AssetPath))
{
check(*IndexPtr < ConvertedAssets.Num());
FoundRecord = &ConvertedAssets[*IndexPtr];
ensure(FoundRecord->AssetPath == AssetPath);
}
else if (bSlowSearch)
{
for (FConvertedAssetRecord& ConversionRecord : ConvertedAssets)
{
if (ConversionRecord.AssetPath == AssetPath)
{
FoundRecord = &ConversionRecord;
}
}
}
else
{
check(RecordLookupTable.Num() == ConvertedAssets.Num());
}
return FoundRecord;
}
//------------------------------------------------------------------------------
bool FBlueprintNativeCodeGenManifest::LogDependencies(const FAssetData& AssetInfo)
{
// load the asset (if it isn't already)
const UObject* AssetObj = AssetInfo.GetAsset();
return BlueprintNativeCodeGenManifestImpl::GatherModuleDependencies(AssetObj, ModuleDependencies);
}
//------------------------------------------------------------------------------
bool FBlueprintNativeCodeGenManifest::Save(bool bSort) const
{
if (bSort)
{
ConvertedAssets.Sort([](const FConvertedAssetRecord& Lhs, const FConvertedAssetRecord& Rhs)->bool
{
if (Lhs.AssetType == Rhs.AssetType)
{
return Lhs.AssetPath < Rhs.AssetPath;
}
return Lhs.AssetType->GetName() < Rhs.AssetType->GetName();
});
RecordLookupTable.Empty();
}
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, *ManifestFilePath);
}
}
return false;
}
//------------------------------------------------------------------------------
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();
RecordLookupTable.Empty();
}
//------------------------------------------------------------------------------
void FBlueprintNativeCodeGenManifest::ConstructRecordLookupTable()
{
RecordLookupTable.Empty(ConvertedAssets.Num());
for (int32 RecordIndex = 0; RecordIndex < ConvertedAssets.Num(); ++RecordIndex)
{
const FConvertedAssetRecord& AssetRecord = ConvertedAssets[RecordIndex];
RecordLookupTable.Add(AssetRecord.AssetPath, RecordIndex);
}
}