Files
UnrealEngine/Engine/Source/Programs/SubmitTool/Private/Logic/PreSubmitOperations/VirtualizationOperation.cpp
2025-05-18 13:04:45 +08:00

300 lines
9.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VirtualizationOperation.h"
#include "AnalyticsEventAttribute.h"
#include "Configuration/Configuration.h"
#include "Logic/Validators/ValidatorDefinition.h"
#include "Misc/Paths.h"
#include "Models/ModelInterface.h"
const TCHAR* LexToString(FVirtualizationOperation::EVirtualizationErrorCode ErrorCode)
{
switch (ErrorCode)
{
case FVirtualizationOperation::EVirtualizationErrorCode::Success:
return TEXT("Success");
case FVirtualizationOperation::EVirtualizationErrorCode::NoBuildCommand:
return TEXT("NoBuildCommand");
case FVirtualizationOperation::EVirtualizationErrorCode::UBTNotFound:
return TEXT("UBTNotFound");
case FVirtualizationOperation::EVirtualizationErrorCode::UBTProcFailure:
return TEXT("UBTProcFailure");
case FVirtualizationOperation::EVirtualizationErrorCode::CompileFailed:
return TEXT("CompileFailed");
case FVirtualizationOperation::EVirtualizationErrorCode::UVTProcFailure:
return TEXT("UVTProcFailure");
case FVirtualizationOperation::EVirtualizationErrorCode::UVTError:
return TEXT("UVTError");
default:
// Normally I'd checkNoEntry() here but I don't see this as a reason to crash the SubmitTool
return TEXT("Unknown");
}
}
FVirtualizationOperation::FVirtualizationOperation(const FName& InNameId, const FSubmitToolParameters& InParameters, TSharedRef<FSubmitToolServiceProvider> InServiceProvider, const FString& InDefinition) :
FValidatorRunExecutable(InNameId, InParameters, InServiceProvider, InDefinition)
{
ParseDefinition(InDefinition);
}
void FVirtualizationOperation::ParseDefinition(const FString& InDefinition)
{
FStringOutputDevice Errors;
Definition = MakeUnique<FVirtualizationToolDefinition>();
FVirtualizationToolDefinition* ModifyableDefinition = const_cast<FVirtualizationToolDefinition*>(GetTypedDefinition<FVirtualizationToolDefinition>());
FVirtualizationToolDefinition::StaticStruct()->ImportText(*InDefinition, ModifyableDefinition, nullptr, 0, &Errors, FVirtualizationToolDefinition::StaticStruct()->GetName());
ModifyableDefinition->ExecutablePath = FConfiguration::SubstituteAndNormalizeFilename(ModifyableDefinition->ExecutablePath);
ModifyableDefinition->BuildCommand = FConfiguration::SubstituteAndNormalizeFilename(ModifyableDefinition->BuildCommand);
ModifyableDefinition->BuildCommandArgs = FConfiguration::Substitute(ModifyableDefinition->BuildCommandArgs);
if(!Errors.IsEmpty())
{
UE_LOG(LogSubmitTool, Error, TEXT("Error loading parameter file %s"), *Errors);
FModelInterface::SetErrorState();
}
}
bool FVirtualizationOperation::Activate()
{
FVirtualizationToolDefinition* ModifyableDefinition = const_cast<FVirtualizationToolDefinition*>(GetTypedDefinition<FVirtualizationToolDefinition>());
ModifyableDefinition->bValidateExecutableExists = false;
bIsValidSetup = FValidatorRunExecutable::Activate();
return bIsValidSetup;
}
bool FVirtualizationOperation::Validate(const FString& InCLDescription, const TArray<FSourceControlStateRef>& InFilteredFilesInCL, const TArray<const FTag*>& InTags)
{
const FVirtualizationToolDefinition* TypedDefinition = GetTypedDefinition<FVirtualizationToolDefinition>();
check(TypedDefinition != nullptr);
ErrorCode = EVirtualizationErrorCode::Success;
bLaunchProcess = false;
if (DoesExecutableNeedBuilding())
{
bCompileRequired = true;
if (TypedDefinition->BuildCommand.IsEmpty())
{
LogFailure(FString::Printf(TEXT("[%s] Virtualization tool is not present locally in %s and cannot be built"), *ValidatorName, *TypedDefinition->ExecutablePath));
ErrorCode = EVirtualizationErrorCode::NoBuildCommand;
return false;
}
if (!StartBuildingTool())
{
LogFailure(FString::Printf(TEXT("[%s] Virtualization tool is not present locally in %s and cannot be built"), *ValidatorName, *TypedDefinition->ExecutablePath));
return false;
}
}
if (!IsBuildingTool())
{
bLaunchProcess = true;
}
return true;
}
void FVirtualizationOperation::StopInternalValidations()
{
FValidatorRunExecutable::StopInternalValidations();
if (BuildProcessHandle.IsValid())
{
if (FPlatformProcess::IsProcRunning(*BuildProcessHandle))
{
FPlatformProcess::TerminateProc(*BuildProcessHandle, true);
Pipes.Reset();
}
}
}
void FVirtualizationOperation::OnProcessComplete(const FString& ProcessId, int32 ReturnCode)
{
if (ReturnCode != 0)
{
ErrorCode = EVirtualizationErrorCode::UVTError;
}
FValidatorRunExecutable::OnProcessComplete(ProcessId, ReturnCode);
}
const TArray<FAnalyticsEventAttribute> FVirtualizationOperation::GetTelemetryAttributes() const
{
TArray<FAnalyticsEventAttribute> Attributes = FValidatorRunExecutable::GetTelemetryAttributes();
Attributes = AppendAnalyticsEventAttributeArray(Attributes,
TEXT("ErrorCode"), LexToString(ErrorCode),
TEXT("CompileRequired"), bCompileRequired,
TEXT("CompileTime"), TotalCompileTime,
TEXT("CompileResult"), CompileResult
);
return Attributes;
}
bool FVirtualizationOperation::StartBuildingTool()
{
const FVirtualizationToolDefinition* TypedDefinition = GetTypedDefinition<FVirtualizationToolDefinition>();
FString BuildCommand = TypedDefinition->BuildCommand;
if (!FPaths::FileExists(BuildCommand))
{
LogFailure(FString::Printf(TEXT("[%s] Build File does not exist %s"),*ValidatorName, *BuildCommand));
ErrorCode = EVirtualizationErrorCode::UBTNotFound;
return false;
}
if (!Pipes.Create())
{
LogFailure(FString::Printf(TEXT("[%s] Error creating pipes"), *ValidatorName));
ErrorCode = EVirtualizationErrorCode::UBTProcFailure;
return false;
}
UE_LOG(LogValidators, Log, TEXT("[%s] Building Virtualization Tool"), *ValidatorName);
UE_LOG(LogValidatorsResult, Log, TEXT("[%s] Building Virtualization Tool"), *ValidatorName);
this->BuildProcessHandle = MakeUnique<FProcHandle>(
FPlatformProcess::CreateProc(
*BuildCommand,
*TypedDefinition->BuildCommandArgs,
false,
true,
true,
nullptr,
0,
nullptr,
Pipes.GetStdOutForProcess(),
Pipes.GetStdInForProcess()));
if (!this->BuildProcessHandle->IsValid())
{
Pipes.Reset();
LogFailure(FString::Printf(TEXT("[%s] Error creating process %s %s."), *ValidatorName, *BuildCommand, *TypedDefinition->BuildCommandArgs));
ErrorCode = EVirtualizationErrorCode::UBTProcFailure;
return false;
}
CompileStartTime = FPlatformTime::Seconds();
return true;
}
void FVirtualizationOperation::StartVirtualization()
{
const FVirtualizationToolDefinition* TypedDefinition = GetTypedDefinition<FVirtualizationToolDefinition>();
FString SubstArgs = FConfiguration::Substitute(TypedDefinition->ExecutableArguments);
// Note that ::QueueProcess will call ::ValidationFinished on failure preventing us from setting the error code
// before telemetry is reported. So we set the error code as a failure now then reset it back to Success if the
// process starts correctly. This way if the process does fail to start up the correct error code will be reported.
ErrorCode = EVirtualizationErrorCode::UVTProcFailure;
if (QueueProcess(TEXT("#1"), TypedDefinition->ExecutablePath, SubstArgs))
{
// Resetting the error code to success as the process was created and execution will continue
ErrorCode = EVirtualizationErrorCode::Success;
}
else
{
LogFailure(FString::Printf(TEXT("[%s] Error creating process %s %s."), *ValidatorName, *TypedDefinition->ExecutablePath, *SubstArgs));
}
}
void FVirtualizationOperation::Tick(float InDeltatime)
{
FValidatorRunExecutable::Tick(InDeltatime);
if (BuildProcessHandle.IsValid())
{
FString NewOutput = OutputRemainder + FPlatformProcess::ReadPipe(Pipes.GetStdOutForReading());
if (FPlatformProcess::IsProcRunning(*BuildProcessHandle))
{
int32 Position;
NewOutput.FindLastChar('\n', Position);
OutputRemainder = NewOutput.Mid(Position + 1);
NewOutput.RemoveFromEnd(OutputRemainder);
ProcessOutput(NewOutput);
}
else
{
ProcessOutput(NewOutput);
if (!FPlatformProcess::GetProcReturnCode(*(this->BuildProcessHandle), &CompileResult))
{
const FVirtualizationToolDefinition* TypedDefinition = GetTypedDefinition<FVirtualizationToolDefinition>();
LogFailure(FString::Printf(TEXT("[%s] Error accessing process result for %s."), *ValidatorName, *TypedDefinition->ExecutablePath));
CompileResult = -1;
}
// cleanup
Pipes.Reset();
FPlatformProcess::CloseProc(*BuildProcessHandle);
this->BuildProcessHandle = nullptr;
TotalCompileTime = FPlatformTime::Seconds() - CompileStartTime;
if (CompileResult == 0)
{
UE_LOG(LogValidators, Log, TEXT("[%s] Virtualization Tool built successfully - %.1f(s)"), *ValidatorName, TotalCompileTime);
UE_LOG(LogValidatorsResult, Log, TEXT("[%s] Virtualization built successfully - %.1f(s)"), *ValidatorName, TotalCompileTime);
bLaunchProcess = true;
}
else
{
LogFailure(FString::Printf(TEXT("[%s] Failed to build Virtualization tool (error code %d) - %.1f(s)"), *ValidatorName, CompileResult, TotalCompileTime));
ErrorCode = EVirtualizationErrorCode::CompileFailed;
ValidationFinished(false);
}
}
}
if (bLaunchProcess)
{
bLaunchProcess = false;
StartVirtualization();
}
}
void FVirtualizationOperation::ProcessOutput(const FString& InOutput)
{
if (InOutput.IsEmpty())
{
return;
}
TArray<FString> Lines;
const TCHAR* Separators[] = { TEXT("\n"), TEXT("\r") };
InOutput.ParseIntoArray(Lines, Separators, UE_ARRAY_COUNT(Separators));
for (const FString& Line : Lines)
{
if ( Line.Contains(" error ")
&& !Line.Contains(" Display: ")
&& !Line.Contains(" Warning: ")
&& !Line.Contains(" Log: "))
{
UE_LOG(LogValidators, Warning, TEXT("[%s]: %s"), *ValidatorName, *Line);
}
else
{
UE_LOG(LogValidators, Log, TEXT("[%s]: %s"), *ValidatorName, *Line);
}
}
}