// 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 InServiceProvider, const FString& InDefinition) : FValidatorRunExecutable(InNameId, InParameters, InServiceProvider, InDefinition) { ParseDefinition(InDefinition); } void FVirtualizationOperation::ParseDefinition(const FString& InDefinition) { FStringOutputDevice Errors; Definition = MakeUnique(); FVirtualizationToolDefinition* ModifyableDefinition = const_cast(GetTypedDefinition()); 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(GetTypedDefinition()); ModifyableDefinition->bValidateExecutableExists = false; bIsValidSetup = FValidatorRunExecutable::Activate(); return bIsValidSetup; } bool FVirtualizationOperation::Validate(const FString& InCLDescription, const TArray& InFilteredFilesInCL, const TArray& InTags) { const FVirtualizationToolDefinition* TypedDefinition = GetTypedDefinition(); 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 FVirtualizationOperation::GetTelemetryAttributes() const { TArray 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(); 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( 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(); 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(); 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 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); } } }