EM_Task/UnrealEd/Private/Commandlets/CompileAllBlueprintsCommandlet.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

358 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Commandlets/CompileAllBlueprintsCommandlet.h"
#include "AssetRegistryModule.h"
#include "Kismet2/CompilerResultsLog.h"
#include "EngineUtils.h"
#include "ISourceControlModule.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Engine/Engine.h"
#include "Kismet2/KismetEditorUtilities.h"
DEFINE_LOG_CATEGORY_STATIC(LogCompileAllBlueprintsCommandlet, Log, All);
#define KISMET_COMPILER_MODULENAME "KismetCompiler"
UCompileAllBlueprintsCommandlet::UCompileAllBlueprintsCommandlet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bResultsOnly = false;
bCookedOnly = false;
bDirtyOnly = false;
bSimpleAssetList = false;
BlueprintBaseClassName = UBlueprint::StaticClass()->GetFName();
TotalNumFailedLoads = 0;
TotalNumFatalIssues = 0;
TotalNumWarnings = 0;
}
int32 UCompileAllBlueprintsCommandlet::Main(const FString& Params)
{
InitCommandLine(Params);
InitKismetBlueprintCompiler();
BuildBlueprintAssetList();
BuildBlueprints();
LogResults();
return (TotalNumFatalIssues + TotalNumFailedLoads);
}
void UCompileAllBlueprintsCommandlet::InitCommandLine(const FString& Params)
{
TArray<FString> Tokens;
TArray<FString> Switches;
TMap<FString, FString> SwitchParams;
ParseCommandLine(*Params, Tokens, Switches, SwitchParams);
bResultsOnly = Switches.Contains(TEXT("ShowResultsOnly"));
bDirtyOnly = Switches.Contains(TEXT("DirtyOnly"));
bCookedOnly = Switches.Contains(TEXT("CookedOnly"));
bSimpleAssetList = Switches.Contains(TEXT("SimpleAssetList"));
RequireAssetTags.Empty();
if (SwitchParams.Contains(TEXT("RequireTags")))
{
const FString& FullTagInfo = SwitchParams[TEXT("RequireTags")];
ParseTagPairs(FullTagInfo, RequireAssetTags);
}
ExcludeAssetTags.Empty();
if (SwitchParams.Contains(TEXT("ExcludeTags")))
{
const FString& FullTagInfo = SwitchParams[TEXT("ExcludeTags")];
ParseTagPairs(FullTagInfo, ExcludeAssetTags);
}
IgnoreFolders.Empty();
if (SwitchParams.Contains(TEXT("IgnoreFolder")))
{
const FString& AllIgnoreFolders = SwitchParams[TEXT("IgnoreFolder")];
ParseIgnoreFolders(AllIgnoreFolders);
}
WhitelistFiles.Empty();
if (SwitchParams.Contains(TEXT("WhitelistFile")))
{
const FString& WhitelistFullPath = SwitchParams[TEXT("WhitelistFile")];
ParseWhitelist(WhitelistFullPath);
}
if (SwitchParams.Contains(TEXT("BlueprintBaseClass")))
{
BlueprintBaseClassName = *SwitchParams[TEXT("BlueprintBaseClass")];
}
}
void UCompileAllBlueprintsCommandlet::ParseTagPairs(const FString& FullTagString, TArray<TPair<FString, TArray<FString>>>& OutputAssetTags)
{
TArray<FString> AllTagPairs;
FullTagString.ParseIntoArray(AllTagPairs, TEXT(";"));
// Break All Tag Pairs into individual tags and values
for (const FString& StringTagPair: AllTagPairs)
{
TArray<FString> ParsedTagPairs;
StringTagPair.ParseIntoArray(ParsedTagPairs, TEXT(","));
if (ParsedTagPairs.Num() > 0)
{
TArray<FString> TagValues;
// Start AssetTagIndex at 1, as the first one is the key. We will be using index 0 in all adds as the key
for (int AssetTagIndex = 1; AssetTagIndex < ParsedTagPairs.Num(); ++AssetTagIndex)
{
TagValues.Add(ParsedTagPairs[AssetTagIndex]);
}
TPair<FString, TArray<FString>> NewPair(ParsedTagPairs[0], TagValues);
OutputAssetTags.Add(NewPair);
}
}
}
void UCompileAllBlueprintsCommandlet::ParseIgnoreFolders(const FString& FullIgnoreFolderString)
{
TArray<FString> ParsedIgnoreFolders;
FullIgnoreFolderString.ParseIntoArray(ParsedIgnoreFolders, TEXT(","));
for (const FString& IgnoreFolder: ParsedIgnoreFolders)
{
IgnoreFolders.Add(IgnoreFolder.TrimQuotes());
}
}
void UCompileAllBlueprintsCommandlet::ParseWhitelist(const FString& WhitelistFilePath)
{
const FString FilePath = FPaths::ProjectDir() + WhitelistFilePath;
if (!FFileHelper::LoadANSITextFileToStrings(*FilePath, &IFileManager::Get(), WhitelistFiles))
{
UE_LOG(LogCompileAllBlueprintsCommandlet, Error, TEXT("Failed to Load Whitelist File! : %s"), *FilePath);
}
}
void UCompileAllBlueprintsCommandlet::BuildBlueprints()
{
double LastGCTime = FPlatformTime::Seconds();
for (FAssetData const& Asset: BlueprintAssetList)
{
if (ShouldBuildAsset(Asset))
{
FString const AssetPath = Asset.ObjectPath.ToString();
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Loading and Compiling: '%s'..."), *AssetPath);
// Load with LOAD_NoWarn and LOAD_DisableCompileOnLoad as we are covering those explicitly with CompileBlueprint errors.
UBlueprint* LoadedBlueprint = Cast<UBlueprint>(StaticLoadObject(Asset.GetClass(), /*Outer =*/nullptr, *AssetPath, nullptr, LOAD_NoWarn | LOAD_DisableCompileOnLoad));
if (LoadedBlueprint == nullptr)
{
++TotalNumFailedLoads;
UE_LOG(LogCompileAllBlueprintsCommandlet, Error, TEXT("Failed to Load : '%s'."), *AssetPath);
continue;
}
else
{
CompileBlueprint(LoadedBlueprint);
}
const double TimeNow = FPlatformTime::Seconds();
if (TimeNow - LastGCTime >= 10.0)
{
GEngine->TrimMemory();
LastGCTime = TimeNow;
}
}
}
}
void UCompileAllBlueprintsCommandlet::BuildBlueprintAssetList()
{
BlueprintAssetList.Empty();
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Loading Asset Registry..."));
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName);
AssetRegistryModule.Get().SearchAllAssets(/*bSynchronousSearch =*/true);
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Finished Loading Asset Registry."));
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Gathering All Blueprints From Asset Registry..."));
AssetRegistryModule.Get().GetAssetsByClass(BlueprintBaseClassName, BlueprintAssetList, true);
}
bool UCompileAllBlueprintsCommandlet::ShouldBuildAsset(FAssetData const& Asset) const
{
bool bShouldBuild = true;
if (bCookedOnly && Asset.GetClass() && !Asset.GetClass()->bCooked)
{
FString const AssetPath = Asset.ObjectPath.ToString();
UE_LOG(LogCompileAllBlueprintsCommandlet, Verbose, TEXT("Skipping Building %s: As is not cooked"), *AssetPath);
bShouldBuild = false;
}
if (IgnoreFolders.Num() > 0)
{
for (const FString& IgnoreFolder: IgnoreFolders)
{
if (Asset.ObjectPath.ToString().StartsWith(IgnoreFolder))
{
FString const AssetPath = Asset.ObjectPath.ToString();
UE_LOG(LogCompileAllBlueprintsCommandlet, Verbose, TEXT("Skipping Building %s: As Object is in an Ignored Folder"), *AssetPath);
bShouldBuild = false;
}
}
}
if ((ExcludeAssetTags.Num() > 0) && (CheckHasTagInList(Asset, ExcludeAssetTags)))
{
FString const AssetPath = Asset.ObjectPath.ToString();
UE_LOG(LogCompileAllBlueprintsCommandlet, Verbose, TEXT("Skipping Building %s: As has an excluded tag"), *AssetPath);
bShouldBuild = false;
}
if ((RequireAssetTags.Num() > 0) && (!CheckHasTagInList(Asset, RequireAssetTags)))
{
FString const AssetPath = Asset.ObjectPath.ToString();
UE_LOG(LogCompileAllBlueprintsCommandlet, Verbose, TEXT("Skipping Building %s: As the asset is missing a required tag"), *AssetPath);
bShouldBuild = false;
}
if ((WhitelistFiles.Num() > 0) && (!CheckInWhitelist(Asset)))
{
FString const AssetPath = Asset.ObjectPath.ToString();
UE_LOG(LogCompileAllBlueprintsCommandlet, Verbose, TEXT("Skipping Building %s: As the asset is not part of the whitelist"), *AssetPath);
bShouldBuild = false;
}
if (bDirtyOnly)
{
const UPackage* AssetPackage = Asset.GetPackage();
if ((AssetPackage == nullptr) || !AssetPackage->IsDirty())
{
FString const AssetPath = Asset.ObjectPath.ToString();
UE_LOG(LogCompileAllBlueprintsCommandlet, Verbose, TEXT("Skipping Building %s: As Package is not dirty"), *AssetPath);
bShouldBuild = false;
}
}
return bShouldBuild;
}
bool UCompileAllBlueprintsCommandlet::CheckHasTagInList(FAssetData const& Asset, const TArray<TPair<FString, TArray<FString>>>& TagCollectionToCheck) const
{
bool bContainedTag = false;
for (const TPair<FString, TArray<FString>>& SingleTagAndValues: TagCollectionToCheck)
{
if (Asset.TagsAndValues.Contains(FName(*SingleTagAndValues.Key)))
{
const TArray<FString>& TagValuesToCheck = SingleTagAndValues.Value;
if (TagValuesToCheck.Num() > 0)
{
for (const FString& IndividualValueToCheck: TagValuesToCheck)
{
if (Asset.TagsAndValues.ContainsKeyValue(FName(*SingleTagAndValues.Key), IndividualValueToCheck))
{
bContainedTag = true;
break;
}
}
}
// if we don't have any values to check, just return true as the tag was included
else
{
bContainedTag = true;
break;
}
}
}
return bContainedTag;
}
bool UCompileAllBlueprintsCommandlet::CheckInWhitelist(FAssetData const& Asset) const
{
bool bIsInWhitelist = false;
const FString& AssetFilePath = Asset.ObjectPath.ToString();
for (const FString& WhiteList: WhitelistFiles)
{
if (AssetFilePath == WhiteList)
{
bIsInWhitelist = true;
break;
}
}
return bIsInWhitelist;
}
void UCompileAllBlueprintsCommandlet::CompileBlueprint(UBlueprint* Blueprint)
{
if (KismetBlueprintCompilerModule && Blueprint)
{
// Have to create a new MessageLog for each asset as the warning / error counts are cumulative
FCompilerResultsLog MessageLog;
// Need to prevent the Compiler Results Log from automatically outputting results if verbosity is too low
if (bResultsOnly)
{
MessageLog.bSilentMode = true;
}
else
{
MessageLog.bAnnotateMentionedNodes = true;
}
MessageLog.SetSourcePath(Blueprint->GetPathName());
MessageLog.BeginEvent(TEXT("Compile"));
FKismetEditorUtilities::CompileBlueprint(Blueprint, EBlueprintCompileOptions::SkipGarbageCollection, &MessageLog);
MessageLog.EndEvent();
if ((MessageLog.NumErrors + MessageLog.NumWarnings) > 0)
{
AssetsWithErrorsOrWarnings.Add(Blueprint->GetPathName());
TotalNumFatalIssues += MessageLog.NumErrors;
TotalNumWarnings += MessageLog.NumWarnings;
}
for (TSharedRef<class FTokenizedMessage>& Message: MessageLog.Messages)
{
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("%s"), *Message->ToText().ToString());
}
}
}
void UCompileAllBlueprintsCommandlet::InitKismetBlueprintCompiler()
{
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Loading Kismit Blueprint Compiler..."));
// Get Kismet Compiler Setup. Static so that the expensive stuff only happens once per run.
KismetBlueprintCompilerModule = &FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(TEXT(KISMET_COMPILER_MODULENAME));
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Finished Loading Kismit Blueprint Compiler..."));
}
void UCompileAllBlueprintsCommandlet::LogResults()
{
// results output
UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("\n\n\n===================================================================================\nCompiling Completed with %d errors and %d warnings and %d blueprints that failed to load.\n===================================================================================\n\n\n"), TotalNumFatalIssues, TotalNumWarnings, TotalNumFailedLoads);
// Assets with problems listing
if (bSimpleAssetList && (AssetsWithErrorsOrWarnings.Num() > 0))
{
UE_LOG(LogCompileAllBlueprintsCommandlet, Warning, TEXT("\n===================================================================================\nAssets With Errors or Warnings:\n===================================================================================\n"));
for (const FString& Asset: AssetsWithErrorsOrWarnings)
{
UE_LOG(LogCompileAllBlueprintsCommandlet, Warning, TEXT("%s"), *Asset);
}
UE_LOG(LogCompileAllBlueprintsCommandlet, Warning, TEXT("\n===================================================================================\nEnd of Asset List\n===================================================================================\n"));
}
}