// 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& DependenciesOut); } //------------------------------------------------------------------------------ static bool BlueprintNativeCodeGenManifestImpl::LoadManifest(const FString& FilePath, FBlueprintNativeCodeGenManifest* Manifest) { FString ManifestStr; if (FFileHelper::LoadFileToString(ManifestStr, *FilePath)) { TSharedRef< TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(ManifestStr); TSharedPtr JsonObject; if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) { return FJsonObjectConverter::JsonObjectToUStruct(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& 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(/*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 FBlueprintNativeCodeGenManifest::GetDestinationPaths() const { const FString TargetPath = GetTargetDir(); TArray 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 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); } }