RealtimeStyleTransferRuntime/Source/LyraEditor/Commandlets/ContentValidationCommandlet...

447 lines
14 KiB
C++
Raw Normal View History

2022-05-23 18:41:30 +00:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ContentValidationCommandlet.h"
2022-09-13 07:18:28 +00:00
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Containers/Map.h"
#include "CoreGlobals.h"
#include "DataValidationModule.h"
2022-05-23 18:41:30 +00:00
#include "HAL/PlatformProcess.h"
#include "Interfaces/IPluginManager.h"
2022-09-13 07:18:28 +00:00
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Logging/LogVerbosity.h"
#include "Misc/AssertionMacros.h"
#include "Misc/CString.h"
#include "Misc/CommandLine.h"
2022-05-23 18:41:30 +00:00
#include "Misc/ConfigCacheIni.h"
2022-09-13 07:18:28 +00:00
#include "Misc/OutputDevice.h"
#include "Misc/OutputDeviceRedirector.h"
#include "Misc/Parse.h"
2022-05-23 18:41:30 +00:00
#include "Misc/Paths.h"
2022-09-13 07:18:28 +00:00
#include "Modules/ModuleManager.h"
#include "Serialization/Archive.h"
2022-05-23 18:41:30 +00:00
#include "ShaderCompiler.h"
2022-09-13 07:18:28 +00:00
#include "SourceControlHelpers.h"
#include "Templates/SharedPointer.h"
#include "Trace/Detail/Channel.h"
#include "UObject/Class.h"
#include "UObject/NameTypes.h"
#include "UObject/TopLevelAssetPath.h"
2022-05-23 18:41:30 +00:00
#include "Validation/EditorValidator.h"
DEFINE_LOG_CATEGORY_STATIC(LogLyraContentValidation, Log, Log);
class FScopedContentValidationMessageGatherer : public FOutputDevice
{
public:
FScopedContentValidationMessageGatherer()
: bAtLeastOneError(false)
{
GLog->AddOutputDevice(this);
}
~FScopedContentValidationMessageGatherer()
{
GLog->RemoveOutputDevice(this);
}
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
{
if (Verbosity <= ELogVerbosity::Error)
{
bAtLeastOneError = true;
}
}
bool bAtLeastOneError;
};
UContentValidationCommandlet::UContentValidationCommandlet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
int32 UContentValidationCommandlet::Main(const FString& FullCommandLine)
{
UE_LOG(LogLyraContentValidation, Display, TEXT("Running ContentValidationCommandlet commandlet..."));
TArray<FString> Tokens;
TArray<FString> Switches;
TMap<FString, FString> Params;
ParseCommandLine(*FullCommandLine, Tokens, Switches, Params);
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
AssetRegistry.SearchAllAssets(true);
int32 ReturnVal = 0;
TArray<FString> ChangedPackageNames;
TArray<FString> DeletedPackageNames;
TArray<FString> ChangedCode;
TArray<FString> ChangedOtherFiles;
FString* P4FilterString = Params.Find(TEXT("P4Filter"));
if (P4FilterString && !P4FilterString->IsEmpty())
{
FString P4CmdString = TEXT("files ") + *P4FilterString;
if (!GetAllChangedFiles(AssetRegistry, P4CmdString, ChangedPackageNames, DeletedPackageNames, ChangedCode, ChangedOtherFiles))
{
UE_LOG(LogLyraContentValidation, Display, TEXT("ContentValidation returning 1. Failed to get changed files."));
ReturnVal = 1;
}
}
FString* P4ChangelistString = Params.Find(TEXT("P4Changelist"));
if (P4ChangelistString && !P4ChangelistString->IsEmpty())
{
FString P4CmdString = TEXT("opened -c ") + *P4ChangelistString;
if (!GetAllChangedFiles(AssetRegistry, P4CmdString, ChangedPackageNames, DeletedPackageNames, ChangedCode, ChangedOtherFiles))
{
UE_LOG(LogLyraContentValidation, Display, TEXT("ContentValidation returning 1. Failed to get changed files."));
ReturnVal = 1;
}
}
bool bP4Opened = Switches.Contains(TEXT("P4Opened"));
if (bP4Opened)
{
check(GConfig);
FString Workspace;
FString* P4ClientString = Params.Find(TEXT("P4Client"));
if (P4ClientString && !P4ClientString->IsEmpty())
{
Workspace = *P4ClientString;
}
else
{
const FString& SSCIniFile = SourceControlHelpers::GetSettingsIni();
GConfig->GetString(TEXT("PerforceSourceControl.PerforceSourceControlSettings"), TEXT("Workspace"), Workspace, SSCIniFile);
}
if (!Workspace.IsEmpty())
{
FString P4CmdString = FString::Printf(TEXT("-c%s opened"), *Workspace);
if (!GetAllChangedFiles(AssetRegistry, P4CmdString, ChangedPackageNames, DeletedPackageNames, ChangedCode, ChangedOtherFiles))
{
UE_LOG(LogLyraContentValidation, Display, TEXT("ContentValidation returning 1. Failed to get changed files."));
ReturnVal = 1;
}
}
else
{
UE_LOG(LogLyraContentValidation, Error, TEXT("P4 workspace was not found when using P4Opened"));
UE_LOG(LogLyraContentValidation, Display, TEXT("ContentValidation returning 1. Workspace not found."));
ReturnVal = 1;
}
}
int32 MaxPackagesToLoad = 2000;
FString* InPathString = Params.Find(TEXT("InPath"));
if (InPathString && !InPathString->IsEmpty())
{
GetAllPackagesInPath(AssetRegistry, *InPathString, ChangedPackageNames);
}
FString* OfTypeString = Params.Find(TEXT("OfType"));
if (OfTypeString && !OfTypeString->IsEmpty())
{
const int32 InitialPackages = ChangedPackageNames.Num();
GetAllPackagesOfType(*OfTypeString, ChangedPackageNames);
MaxPackagesToLoad += ChangedPackageNames.Num() - InitialPackages;
}
FString* SpecificPackagesString = Params.Find(TEXT("Packages"));
if (SpecificPackagesString && !SpecificPackagesString->IsEmpty())
{
TArray<FString> PackagePaths;
SpecificPackagesString->ParseIntoArray(PackagePaths, TEXT("+"));
ChangedPackageNames.Append(PackagePaths);
}
// We will be flushing shader compile as we load materials, so don't let other shader warnings be attributed incorrectly to the package that is loading.
if (GShaderCompilingManager)
{
GShaderCompilingManager->FinishAllCompilation();
}
FString* InMaxPackagesToLoadString = Params.Find(TEXT("MaxPackagesToLoad"));
if (InMaxPackagesToLoadString)
{
MaxPackagesToLoad = FCString::Atoi(**InMaxPackagesToLoadString);
}
TArray<FString> AllWarningsAndErrors;
UEditorValidator::ValidatePackages(ChangedPackageNames, DeletedPackageNames, MaxPackagesToLoad, AllWarningsAndErrors, EDataValidationUsecase::Commandlet);
if (!UEditorValidator::ValidateProjectSettings())
{
ReturnVal = 1;
}
return ReturnVal;
}
bool UContentValidationCommandlet::GetAllChangedFiles(IAssetRegistry& AssetRegistry, const FString& P4CmdString, TArray<FString>& OutChangedPackageNames, TArray<FString>& DeletedPackageNames, TArray<FString>& OutChangedCode, TArray<FString>& OutChangedOtherFiles) const
{
TArray<FString> Results;
int32 ReturnCode = 0;
if (LaunchP4(P4CmdString, Results, ReturnCode))
{
if (ReturnCode == 0)
{
for (const FString& Result : Results)
{
FString DepotPathName;
FString ExtraInfoAfterPound;
if (Result.Split(TEXT("#"), &DepotPathName, &ExtraInfoAfterPound))
{
if (DepotPathName.EndsWith(TEXT(".uasset")) || DepotPathName.EndsWith(TEXT(".umap")))
{
FString FullPackageName;
{
// Check for /Game/ assets
FString PostContentPath;
if (DepotPathName.Split(TEXT("LyraGame/Content/"), nullptr, &PostContentPath)) //@TODO: RENAME: Potential issue when modules are renamed
{
if (!PostContentPath.IsEmpty())
{
const FString PostContentPathWithoutExtension = FPaths::GetBaseFilename(PostContentPath, false);
FString PackageNameToTest = TEXT("/Game/") + PostContentPathWithoutExtension;
if (!UEditorValidator::IsInUncookedFolder(PackageNameToTest))
{
FullPackageName = PackageNameToTest;
}
}
}
}
if (FullPackageName.IsEmpty())
{
// Check for plugin assets
FString PostPluginsPath;
if (DepotPathName.Split(TEXT("LyraGame/Plugins/"), nullptr, &PostPluginsPath))
{
const int32 ContentFolderIdx = PostPluginsPath.Find(TEXT("/Content/"));
if (ContentFolderIdx != INDEX_NONE)
{
int32 PluginFolderIdx = PostPluginsPath.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd, ContentFolderIdx - 1);
if (PluginFolderIdx == INDEX_NONE)
{
// No leading /. Directly in the /Plugins/ folder
PluginFolderIdx = 0;
}
else
{
// Skip the leading /. A subfolder in the /Plugins/ folder
PluginFolderIdx++;
}
const int32 PostContentFolderIdx = ContentFolderIdx + FCString::Strlen(TEXT("/Content/"));
const FString PostContentPath = PostPluginsPath.RightChop(PostContentFolderIdx);
const FString PluginName = PostPluginsPath.Mid(PluginFolderIdx, ContentFolderIdx - PluginFolderIdx);
if (!PostContentPath.IsEmpty() && !PluginName.IsEmpty())
{
TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(PluginName);
if (Plugin.IsValid() && Plugin->IsEnabled())
{
const FString PostContentPathWithoutExtension = FPaths::GetBaseFilename(PostContentPath, false);
FullPackageName = FString::Printf(TEXT("/%s/%s"), *PluginName, *PostContentPathWithoutExtension);
}
}
}
}
}
if (!FullPackageName.IsEmpty())
{
if (ExtraInfoAfterPound.Contains(TEXT("delete")))
{
DeletedPackageNames.AddUnique(FullPackageName);
}
else
{
OutChangedPackageNames.AddUnique(FullPackageName);
}
}
}
else
{
FString PostLyraGamePath;
if (DepotPathName.Split(TEXT("/LyraGame/"), nullptr, &PostLyraGamePath))
{
if (DepotPathName.EndsWith(TEXT(".cpp")))
{
OutChangedCode.Add(PostLyraGamePath);
}
else if (DepotPathName.EndsWith(TEXT(".h")))
{
OutChangedCode.Add(PostLyraGamePath);
FString ChangedHeaderLocalFilename = GetLocalPathFromDepotPath(DepotPathName);
if (!ChangedHeaderLocalFilename.IsEmpty())
{
UEditorValidator::GetChangedAssetsForCode(AssetRegistry, ChangedHeaderLocalFilename, OutChangedPackageNames);
}
}
else
{
OutChangedOtherFiles.Add(PostLyraGamePath);
}
}
}
}
}
return true;
}
else
{
UE_LOG(LogLyraContentValidation, Error, TEXT("p4 returned non-zero return code %d"), ReturnCode);
}
}
return false;
}
void UContentValidationCommandlet::GetAllPackagesInPath(IAssetRegistry& AssetRegistry, const FString& InPathString, TArray<FString>& OutPackageNames) const
{
TArray<FString> Paths;
InPathString.ParseIntoArray(Paths, TEXT("+"));
FARFilter Filter;
Filter.bRecursivePaths = true;
Filter.bIncludeOnlyOnDiskAssets = true;
for (const FString& Path : Paths)
{
Filter.PackagePaths.Add(FName(*Path));
}
TArray<FAssetData> AssetsInPaths;
if (AssetRegistry.GetAssets(Filter, AssetsInPaths))
{
for (const FAssetData& AssetData : AssetsInPaths)
{
OutPackageNames.Add(AssetData.PackageName.ToString());
}
}
}
void UContentValidationCommandlet::GetAllPackagesOfType(const FString& OfTypeString, TArray<FString>& OutPackageNames) const
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
TArray<FString> Types;
OfTypeString.ParseIntoArray(Types, TEXT("+"));
FARFilter Filter;
Filter.bRecursivePaths = true;
Filter.bIncludeOnlyOnDiskAssets = true;
for (const FString& Type : Types)
{
2022-09-13 07:18:28 +00:00
FTopLevelAssetPath TypePathName = UClass::TryConvertShortTypeNameToPathName<UStruct>(Type, ELogVerbosity::Error, TEXT("UContentValidationCommandlet"));
if (TypePathName.IsNull())
{
UE_LOG(LogLyraContentValidation, Error, TEXT("Failed to convert short class name \"%s\" to path name. Please use class path names."), *Type);
}
else
{
Filter.ClassPaths.Add(TypePathName);
}
2022-05-23 18:41:30 +00:00
}
TArray<FAssetData> AssetsOfType;
if (AssetRegistry.GetAssets(Filter, AssetsOfType))
{
for (const FAssetData& AssetData : AssetsOfType)
{
OutPackageNames.Add(AssetData.PackageName.ToString());
}
}
}
bool UContentValidationCommandlet::LaunchP4(const FString& Args, TArray<FString>& Output, int32& OutReturnCode) const
{
void* PipeRead = nullptr;
void* PipeWrite = nullptr;
verify(FPlatformProcess::CreatePipe(PipeRead, PipeWrite));
bool bInvoked = false;
OutReturnCode = -1;
FString StringOutput;
FProcHandle ProcHandle = FPlatformProcess::CreateProc(TEXT("p4.exe"), *Args, false, true, true, nullptr, 0, nullptr, PipeWrite);
if (ProcHandle.IsValid())
{
while (FPlatformProcess::IsProcRunning(ProcHandle))
{
StringOutput += FPlatformProcess::ReadPipe(PipeRead);
FPlatformProcess::Sleep(0.1f);
}
StringOutput += FPlatformProcess::ReadPipe(PipeRead);
FPlatformProcess::GetProcReturnCode(ProcHandle, &OutReturnCode);
bInvoked = true;
}
else
{
UE_LOG(LogLyraContentValidation, Error, TEXT("Failed to launch p4."));
}
FPlatformProcess::ClosePipe(PipeRead, PipeWrite);
StringOutput.ParseIntoArrayLines(Output);
return bInvoked;
}
FString UContentValidationCommandlet::GetLocalPathFromDepotPath(const FString& DepotPathName) const
{
FString ReturnString;
const FString& SSCIniFile = SourceControlHelpers::GetSettingsIni();
FString Workspace;
GConfig->GetString(TEXT("PerforceSourceControl.PerforceSourceControlSettings"), TEXT("Workspace"), Workspace, SSCIniFile);
if (Workspace.IsEmpty())
{
FString ParameterValue;
if (FParse::Value(FCommandLine::Get(), TEXT("P4Client="), ParameterValue))
{
Workspace = ParameterValue;
}
}
if (!Workspace.IsEmpty())
{
TArray<FString> WhereResults;
int32 ReturnCode = 0;
FString P4WhereCommand = FString::Printf(TEXT("-ztag -c%s where %s"), *Workspace, *DepotPathName);
if (LaunchP4(P4WhereCommand, WhereResults, ReturnCode))
{
if (WhereResults.Num() >= 2)
{
ReturnString = WhereResults[2];
ReturnString.RemoveFromStart(TEXT("... path "));
FPaths::NormalizeFilename(ReturnString);
}
else
{
UE_LOG(LogLyraContentValidation, Warning, TEXT("GetAllChangedFiles failed to run p4 'where'. WhereResults[0] = '%s'. Not adding any validation for %s"), WhereResults.Num() > 0 ? *WhereResults[0] : TEXT("Invalid"), *DepotPathName);
}
}
}
return ReturnString;
}