517 lines
20 KiB
C++
517 lines
20 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "EditorValidator.h"
|
|
|
|
#include "AssetRegistry/ARFilter.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "AssetRegistry/IAssetRegistry.h"
|
|
#include "Blueprint/BlueprintSupport.h"
|
|
#include "Containers/Map.h"
|
|
#include "Containers/Set.h"
|
|
#include "DataValidationModule.h"
|
|
#include "Editor.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "EditorValidatorSubsystem.h"
|
|
#include "Engine/BlueprintCore.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "HAL/PlatformMisc.h"
|
|
#include "ISourceControlModule.h"
|
|
#include "ISourceControlOperation.h"
|
|
#include "ISourceControlProvider.h"
|
|
#include "ISourceControlState.h"
|
|
#include "Internationalization/Text.h"
|
|
#include "Logging/LogCategory.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "LyraEditor.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Misc/AssetRegistryInterface.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Settings/ProjectPackagingSettings.h"
|
|
#include "ShaderCompiler.h"
|
|
#include "SourceCodeNavigation.h"
|
|
#include "SourceControlOperations.h"
|
|
#include "Stats/StatsMisc.h"
|
|
#include "StudioAnalytics.h"
|
|
#include "Templates/SharedPointer.h"
|
|
#include "Trace/Detail/Channel.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/SoftObjectPath.h"
|
|
#include "UObject/TopLevelAssetPath.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/WeakObjectPtr.h"
|
|
#include "UObject/WeakObjectPtrTemplates.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "EditorValidator"
|
|
|
|
int32 GMaxAssetsChangedByAHeader = 200;
|
|
static FAutoConsoleVariableRef CVarMaxAssetsChangedByAHeader(TEXT("EditorValidator.MaxAssetsChangedByAHeader"), GMaxAssetsChangedByAHeader, TEXT("The maximum number of assets to check for content validation based on a single header change."), ECVF_Default);
|
|
|
|
bool UEditorValidator::bAllowFullValidationInEditor = false;
|
|
TArray<FString> FLyraValidationMessageGatherer::IgnorePatterns;
|
|
|
|
UEditorValidator::UEditorValidator()
|
|
: Super()
|
|
{
|
|
}
|
|
|
|
void UEditorValidator::ValidateCheckedOutContent(bool bInteractive, const EDataValidationUsecase InValidationUsecase)
|
|
{
|
|
FStudioAnalytics::RecordEvent(TEXT("ValidateContent"));
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
if (AssetRegistryModule.Get().IsLoadingAssets())
|
|
{
|
|
if (bInteractive)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("DiscoveringAssets", "Still discovering assets. Try again once it is complete."));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogLyraEditor, Display, TEXT("Could not run ValidateCheckedOutContent because asset discovery was still being done."));
|
|
}
|
|
return;
|
|
}
|
|
|
|
TArray<FString> ChangedPackageNames;
|
|
TArray<FString> DeletedPackageNames;
|
|
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
if (ISourceControlModule::Get().IsEnabled())
|
|
{
|
|
// Request the opened files at filter construction time to make sure checked out files have the correct state for the filter
|
|
TSharedRef<FUpdateStatus, ESPMode::ThreadSafe> UpdateStatusOperation = ISourceControlOperation::Create<FUpdateStatus>();
|
|
UpdateStatusOperation->SetGetOpenedOnly(true);
|
|
SourceControlProvider.Execute(UpdateStatusOperation, EConcurrency::Synchronous);
|
|
|
|
TArray<FSourceControlStateRef> CheckedOutFiles = SourceControlProvider.GetCachedStateByPredicate(
|
|
[](const FSourceControlStateRef& State) { return State->IsCheckedOut() || State->IsAdded() || State->IsDeleted(); }
|
|
);
|
|
|
|
for (const FSourceControlStateRef& FileState : CheckedOutFiles)
|
|
{
|
|
FString Filename = FileState->GetFilename();
|
|
if (FPackageName::IsPackageFilename(Filename))
|
|
{
|
|
// Assets
|
|
FString PackageName;
|
|
if (FPackageName::TryConvertFilenameToLongPackageName(Filename, PackageName))
|
|
{
|
|
if (FileState->IsDeleted())
|
|
{
|
|
DeletedPackageNames.Add(PackageName);
|
|
}
|
|
else
|
|
{
|
|
ChangedPackageNames.Add(PackageName);
|
|
}
|
|
}
|
|
}
|
|
else if (Filename.EndsWith(TEXT(".h")))
|
|
{
|
|
// Source code header changes for classes may cause issues in assets based on those classes
|
|
UEditorValidator::GetChangedAssetsForCode(AssetRegistryModule.Get(), Filename, ChangedPackageNames);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bAnyIssuesFound = false;
|
|
TArray<FString> AllWarningsAndErrors;
|
|
{
|
|
if (bInteractive)
|
|
{
|
|
bAllowFullValidationInEditor = true;
|
|
|
|
// We will be flushing shader compile as we load materials, so dont let other shader warnings be attributed incorrectly to the package that is loading.
|
|
if (GShaderCompilingManager)
|
|
{
|
|
FScopedSlowTask SlowTask(0.f, LOCTEXT("CompilingShadersBeforeCheckingContentTask", "Finishing shader compiles before checking content..."));
|
|
SlowTask.MakeDialog();
|
|
GShaderCompilingManager->FinishAllCompilation();
|
|
}
|
|
}
|
|
{
|
|
FScopedSlowTask SlowTask(0.f, LOCTEXT("CheckingContentTask", "Checking content..."));
|
|
SlowTask.MakeDialog();
|
|
if (!ValidatePackages(ChangedPackageNames, DeletedPackageNames, 2000, AllWarningsAndErrors, InValidationUsecase))
|
|
{
|
|
bAnyIssuesFound = true;
|
|
}
|
|
}
|
|
if (bInteractive)
|
|
{
|
|
bAllowFullValidationInEditor = false;
|
|
}
|
|
}
|
|
|
|
{
|
|
FLyraValidationMessageGatherer ScopedMessageGatherer;
|
|
if (!ValidateProjectSettings())
|
|
{
|
|
bAnyIssuesFound = true;
|
|
}
|
|
AllWarningsAndErrors.Append(ScopedMessageGatherer.GetAllWarningsAndErrors());
|
|
}
|
|
|
|
if (bInteractive)
|
|
{
|
|
const bool bAtLeastOneMessage = (AllWarningsAndErrors.Num() != 0);
|
|
if (bAtLeastOneMessage)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ContentValidationFailed", "!!!!!!! Your checked out content has issues. Don't submit until they are fixed !!!!!!!\r\n\r\nSee the MessageLog and OutputLog for details"));
|
|
}
|
|
else if (bAnyIssuesFound)
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ContentValidationFailedWithNoMessages", "No errors or warnings were found, but there was an error return code. Look in the OutputLog and log file for details. You may need engineering help."));
|
|
}
|
|
else
|
|
{
|
|
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ContentValidationPassed", "All checked out content passed. Nice job."));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UEditorValidator::ValidatePackages(const TArray<FString>& ExistingPackageNames, const TArray<FString>& DeletedPackageNames, int32 MaxPackagesToLoad, TArray<FString>& OutAllWarningsAndErrors, const EDataValidationUsecase InValidationUsecase)
|
|
{
|
|
bool bAnyIssuesFound = false;
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
|
|
|
TArray<FString> AllPackagesToValidate = ExistingPackageNames;
|
|
for (const FString& DeletedPackageName : DeletedPackageNames)
|
|
{
|
|
UE_LOG(LogLyraEditor, Display, TEXT("Adding referencers for deleted package %s to be verified"), *DeletedPackageName);
|
|
TArray<FName> PackageReferencers;
|
|
AssetRegistry.GetReferencers(FName(*DeletedPackageName), PackageReferencers, UE::AssetRegistry::EDependencyCategory::Package);
|
|
for (const FName& Referencer : PackageReferencers)
|
|
{
|
|
const FString ReferencerString = Referencer.ToString();
|
|
if (!DeletedPackageNames.Contains(ReferencerString) && !IsInUncookedFolder(ReferencerString))
|
|
{
|
|
UE_LOG(LogLyraEditor, Display, TEXT(" Deleted package referencer %s was added to the queue to be verified"), *ReferencerString);
|
|
AllPackagesToValidate.Add(ReferencerString);
|
|
}
|
|
}
|
|
}
|
|
|
|
FMessageLog DataValidationLog("AssetCheck");
|
|
DataValidationLog.NewPage(LOCTEXT("ValidatePackages", "Validate Packages"));
|
|
|
|
if (AllPackagesToValidate.Num() > MaxPackagesToLoad)
|
|
{
|
|
// Too much changed to verify, just pass it.
|
|
FString WarningMessage = FString::Printf(TEXT("Assets to validate (%d) exceeded -MaxPackagesToLoad=(%d). Skipping existing package validation."), AllPackagesToValidate.Num(), MaxPackagesToLoad);
|
|
UE_LOG(LogLyraEditor, Warning, TEXT("%s"), *WarningMessage);
|
|
OutAllWarningsAndErrors.Add(WarningMessage);
|
|
DataValidationLog.Warning(FText::FromString(WarningMessage));
|
|
}
|
|
else
|
|
{
|
|
// Load all packages that match the file filter string
|
|
TArray<FAssetData> AssetsToCheck;
|
|
for (const FString& PackageName : AllPackagesToValidate)
|
|
{
|
|
if (FPackageName::IsValidLongPackageName(PackageName) && !IsInUncookedFolder(PackageName))
|
|
{
|
|
int32 OldNumAssets = AssetsToCheck.Num();
|
|
AssetRegistry.GetAssetsByPackageName(FName(*PackageName), AssetsToCheck, true);
|
|
if (AssetsToCheck.Num() == OldNumAssets)
|
|
{
|
|
FString WarningMessage;
|
|
// See if the file exists at all. Otherwise, the package contains no assets.
|
|
if (FPackageName::DoesPackageExist(PackageName))
|
|
{
|
|
WarningMessage = FString::Printf(TEXT("Found no assets in package '%s'"), *PackageName);
|
|
}
|
|
else
|
|
{
|
|
if (ISourceControlModule::Get().IsEnabled())
|
|
{
|
|
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
|
|
FString PackageFilename = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
|
|
TSharedPtr<ISourceControlState, ESPMode::ThreadSafe> FileState = SourceControlProvider.GetState(PackageFilename, EStateCacheUsage::ForceUpdate);
|
|
if (FileState->IsAdded())
|
|
{
|
|
WarningMessage = FString::Printf(TEXT("Package '%s' is missing from disk. It is marked for add in perforce but missing from your hard drive."), *PackageName);
|
|
}
|
|
|
|
if (FileState->IsCheckedOut())
|
|
{
|
|
WarningMessage = FString::Printf(TEXT("Package '%s' is missing from disk. It is checked out in perforce but missing from your hard drive."), *PackageName);
|
|
}
|
|
}
|
|
|
|
if (WarningMessage.IsEmpty())
|
|
{
|
|
WarningMessage = FString::Printf(TEXT("Package '%s' is missing from disk."), *PackageName);
|
|
}
|
|
}
|
|
ensure(!WarningMessage.IsEmpty());
|
|
UE_LOG(LogLyraEditor, Warning, TEXT("%s"), *WarningMessage);
|
|
OutAllWarningsAndErrors.Add(WarningMessage);
|
|
DataValidationLog.Warning(FText::FromString(WarningMessage));
|
|
bAnyIssuesFound = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (AssetsToCheck.Num() > 0)
|
|
{
|
|
// Preload all assets to check, so load warnings can be handled separately from validation warnings
|
|
{
|
|
for (const FAssetData& AssetToCheck : AssetsToCheck)
|
|
{
|
|
if (!AssetToCheck.IsAssetLoaded())
|
|
{
|
|
UE_LOG(LogLyraEditor, Display, TEXT("Preloading %s..."), *AssetToCheck.GetObjectPathString());
|
|
|
|
// Start listening for load warnings
|
|
FLyraValidationMessageGatherer ScopedPreloadMessageGatherer;
|
|
|
|
// Load the asset
|
|
AssetToCheck.GetAsset();
|
|
|
|
if (ScopedPreloadMessageGatherer.GetAllWarningsAndErrors().Num() > 0)
|
|
{
|
|
// Repeat all errant load warnings as errors, so other CIS systems can treat them more severely (i.e. Build health will create an issue and assign it to a developer)
|
|
for (const FString& LoadWarning : ScopedPreloadMessageGatherer.GetAllWarnings())
|
|
{
|
|
UE_LOG(LogLyraEditor, Error, TEXT("%s"), *LoadWarning);
|
|
}
|
|
|
|
OutAllWarningsAndErrors.Append(ScopedPreloadMessageGatherer.GetAllWarningsAndErrors());
|
|
bAnyIssuesFound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run all validators now.
|
|
FLyraValidationMessageGatherer ScopedMessageGatherer;
|
|
FValidateAssetsSettings Settings;
|
|
FValidateAssetsResults Results;
|
|
|
|
Settings.bSkipExcludedDirectories = true;
|
|
Settings.bShowIfNoFailures = true;
|
|
Settings.ValidationUsecase = InValidationUsecase;
|
|
|
|
const bool bHasInvalidFiles = GEditor->GetEditorSubsystem<UEditorValidatorSubsystem>()->ValidateAssetsWithSettings(AssetsToCheck, Settings, Results) > 0;
|
|
|
|
if (bHasInvalidFiles || ScopedMessageGatherer.GetAllWarningsAndErrors().Num() > 0)
|
|
{
|
|
OutAllWarningsAndErrors.Append(ScopedMessageGatherer.GetAllWarningsAndErrors());
|
|
bAnyIssuesFound = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return !bAnyIssuesFound;
|
|
}
|
|
|
|
bool UEditorValidator::ValidateProjectSettings()
|
|
{
|
|
bool bSuccess = true;
|
|
|
|
FMessageLog ValidationLog("AssetCheck");
|
|
|
|
{
|
|
bool bDeveloperMode = false;
|
|
GConfig->GetBool(TEXT("/Script/PythonScriptPlugin.PythonScriptPluginSettings"), TEXT("bDeveloperMode"), /*out*/ bDeveloperMode, GEngineIni);
|
|
|
|
if (bDeveloperMode)
|
|
{
|
|
const FString ErrorMessage(TEXT("The project setting version of Python's bDeveloperMode should not be checked in. Use the editor preference version instead!"));
|
|
UE_LOG(LogLyraEditor, Error, TEXT("%s"), *ErrorMessage);
|
|
ValidationLog.Error(FText::AsCultureInvariant(ErrorMessage));
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
bool UEditorValidator::IsInUncookedFolder(const FString& PackageName, FString* OutUncookedFolderName)
|
|
{
|
|
const UProjectPackagingSettings* const PackagingSettings = GetDefault<UProjectPackagingSettings>();
|
|
check(PackagingSettings);
|
|
for (const FDirectoryPath& DirectoryToNeverCook : PackagingSettings->DirectoriesToNeverCook)
|
|
{
|
|
const FString& UncookedFolder = DirectoryToNeverCook.Path;
|
|
if (PackageName.StartsWith(UncookedFolder))
|
|
{
|
|
if (OutUncookedFolderName)
|
|
{
|
|
FString FolderToReport = UncookedFolder.StartsWith(TEXT("/Game/")) ? UncookedFolder.RightChop(6) : UncookedFolder;
|
|
if (FolderToReport.EndsWith(TEXT("/")))
|
|
{
|
|
*OutUncookedFolderName = FolderToReport.LeftChop(1);
|
|
}
|
|
else
|
|
{
|
|
*OutUncookedFolderName = FolderToReport;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UEditorValidator::ShouldAllowFullValidation()
|
|
{
|
|
return IsRunningCommandlet() || bAllowFullValidationInEditor;
|
|
}
|
|
|
|
bool UEditorValidator::CanValidateAsset_Implementation(UObject* InAsset) const
|
|
{
|
|
if (InAsset)
|
|
{
|
|
FString PackageName = InAsset->GetOutermost()->GetName();
|
|
if (!IsInUncookedFolder(PackageName))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UEditorValidator::GetChangedAssetsForCode(IAssetRegistry& AssetRegistry, const FString& ChangedHeaderLocalFilename, TArray<FString>& OutChangedPackageNames)
|
|
{
|
|
static struct FCachedNativeClasses
|
|
{
|
|
public:
|
|
FCachedNativeClasses()
|
|
{
|
|
static const FName ModuleNameFName = "ModuleName";
|
|
static const FName ModuleRelativePathFName = "ModuleRelativePath";
|
|
|
|
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
|
|
{
|
|
UClass* TestClass = *ClassIt;
|
|
if (TestClass->HasAnyClassFlags(CLASS_Native))
|
|
{
|
|
FAssetData ClassAssetData(TestClass);
|
|
|
|
FString ModuleName, ModuleRelativePath;
|
|
ClassAssetData.GetTagValue(ModuleNameFName, ModuleName);
|
|
ClassAssetData.GetTagValue(ModuleRelativePathFName, ModuleRelativePath);
|
|
|
|
Classes.Add(ModuleName + TEXT("+") + ModuleRelativePath, TestClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<TWeakObjectPtr<UClass>> GetClassesInHeader(const FString& ModuleName, const FString& ModuleRelativePath)
|
|
{
|
|
TArray<TWeakObjectPtr<UClass>> ClassesInHeader;
|
|
Classes.MultiFind(ModuleName + TEXT("+") + ModuleRelativePath, ClassesInHeader);
|
|
|
|
return ClassesInHeader;
|
|
}
|
|
|
|
private:
|
|
TMultiMap<FString, TWeakObjectPtr<UClass>> Classes;
|
|
} NativeClassCache;
|
|
|
|
const TArray<FString>& ModuleNames = FSourceCodeNavigation::GetSourceFileDatabase().GetModuleNames();
|
|
const FString* Module = ModuleNames.FindByPredicate([ChangedHeaderLocalFilename](const FString& ModuleBuildPath) {
|
|
const FString ModuleFullPath = FPaths::ConvertRelativePathToFull(FPaths::GetPath(ModuleBuildPath));
|
|
if (ChangedHeaderLocalFilename.StartsWith(ModuleFullPath))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (Module)
|
|
{
|
|
SCOPE_LOG_TIME_IN_SECONDS(TEXT("Looking for blueprints affected by code changes"), nullptr);
|
|
|
|
const FString FoundModulePath = FPaths::ConvertRelativePathToFull(FPaths::GetPath(*Module));
|
|
const FString FoundModulePathWithSlash = FoundModulePath / TEXT("");
|
|
FString ChangedHeaderReleativeToModule = ChangedHeaderLocalFilename;
|
|
FPaths::MakePathRelativeTo(ChangedHeaderReleativeToModule, *FoundModulePathWithSlash);
|
|
FString ChangedHeaderModule = FPaths::GetBaseFilename(FoundModulePath);
|
|
|
|
// STEP 1 - Find all the native classes inside the header that changed.
|
|
TArray<TWeakObjectPtr<UClass>> ClassList = NativeClassCache.GetClassesInHeader(ChangedHeaderModule, ChangedHeaderReleativeToModule);
|
|
|
|
// STEP 2 - We now need to convert the set of native classes into actual derived blueprints.
|
|
bool bTooManyFiles = false;
|
|
TArray<FAssetData> BlueprintsDerivedFromNativeModifiedClasses;
|
|
for (TWeakObjectPtr<UClass> ModifiedClassPtr : ClassList)
|
|
{
|
|
// If we capped out on maximum number of modified files for a single header change, don't try to keep looking for more stuff.
|
|
if (bTooManyFiles)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (UClass* ModifiedClass = ModifiedClassPtr.Get())
|
|
{
|
|
// This finds all native derived blueprints, both direct subclasses, or subclasses of subclasses.
|
|
TSet<FTopLevelAssetPath> DerivedClassNames;
|
|
TArray<FTopLevelAssetPath> ClassNames;
|
|
ClassNames.Add(ModifiedClass->GetClassPathName());
|
|
AssetRegistry.GetDerivedClassNames(ClassNames, TSet<FTopLevelAssetPath>(), DerivedClassNames);
|
|
|
|
UE_LOG(LogLyraEditor, Display, TEXT("Validating Subclasses of %s in %s + %s"), *ModifiedClass->GetName(), *ChangedHeaderModule, *ChangedHeaderReleativeToModule);
|
|
|
|
FARFilter Filter;
|
|
Filter.bRecursiveClasses = true;
|
|
Filter.ClassPaths.Add(UBlueprintCore::StaticClass()->GetClassPathName());
|
|
|
|
// We enumerate all assets to find any blueprints who inherit from native classes directly - or
|
|
// from other blueprints.
|
|
AssetRegistry.EnumerateAssets(Filter, [&BlueprintsDerivedFromNativeModifiedClasses, &bTooManyFiles, &DerivedClassNames, ChangedHeaderModule, ChangedHeaderReleativeToModule](const FAssetData& AssetData)
|
|
{
|
|
FString PackageName = AssetData.PackageName.ToString();
|
|
// Don't check data-only blueprints, we'll be here all day.
|
|
if (!AssetData.GetTagValueRef<bool>(FBlueprintTags::IsDataOnly) && !UEditorValidator::IsInUncookedFolder(PackageName))
|
|
{
|
|
// Need to get the generated class here to see if it's one in the derived set we care about.
|
|
const FString ClassFromData = AssetData.GetTagValueRef<FString>(FBlueprintTags::GeneratedClassPath);
|
|
if (!ClassFromData.IsEmpty())
|
|
{
|
|
const FTopLevelAssetPath ClassObjectPath(FPackageName::ExportTextPathToObjectPath(ClassFromData));
|
|
if (DerivedClassNames.Contains(ClassObjectPath))
|
|
{
|
|
UE_LOG(LogLyraEditor, Display, TEXT("\tAdding %s To Validate"), *PackageName);
|
|
|
|
BlueprintsDerivedFromNativeModifiedClasses.Emplace(AssetData);
|
|
|
|
if (BlueprintsDerivedFromNativeModifiedClasses.Num() >= GMaxAssetsChangedByAHeader)
|
|
{
|
|
bTooManyFiles = true;
|
|
UE_LOG(LogLyraEditor, Display, TEXT("Too many assets invalidated (Max %d) by change to, %s + %s"), GMaxAssetsChangedByAHeader, *ChangedHeaderModule, *ChangedHeaderReleativeToModule);
|
|
return false; // Stop enumerating.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
// STEP 3 - Report the possibly changed blueprints as affected modified packages that need
|
|
// to be proved out.
|
|
for (const FAssetData& BlueprintsDerivedFromNativeModifiedClass : BlueprintsDerivedFromNativeModifiedClasses)
|
|
{
|
|
OutChangedPackageNames.Add(BlueprintsDerivedFromNativeModifiedClass.PackageName.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |