Files
UnrealEngineUWP/Engine/Source/Editor/UATHelper/UATHelperModule.cpp
ronald koppers 12979e53c2 FMessageLog::CriticalError has been deprecated.
FMessageLog::CriticalError has a delay before throwing an assert to allow compositing of the message. As a result the assert isn't thrown at the location of the problem, but somewhere in the surrounding area, which makes the problem harder to debug and generates misleading log entries. Due to these problems FMessageLog::CriticalError and EMessageSeverity::CriticalError have been deprecated. It's recommended to use checkf instead.

When FMessageLog goes out of scope or is destroyed explicitly it will flush its messages to the log, which in turn means the log may immediately use the messages. In a few places a reference to the message was kept that outlived the lifetime of the owning FMessageLog in order to add arguments later on. The found cases have been updated to make sure FMessageLog doesn't outlive the addition of the arguments so the arguments are correctly passed to the log.

#jira: UE-155605
#rb brooke.hubert francis.hurteau jerome.delattre
#preflight 62cc4fca9922f7e5129e4e0a

[CL 21042765 by ronald koppers in ue5-main branch]
2022-07-11 12:58:26 -04:00

681 lines
24 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UATHelperModule.h"
#include "CoreTypes.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "UObject/NameTypes.h"
#include "Logging/LogMacros.h"
#include "Templates/SharedPointer.h"
#include "Internationalization/Text.h"
#include "Internationalization/Internationalization.h"
#include "Misc/MessageDialog.h"
#include "Misc/Paths.h"
#include "Stats/Stats.h"
#include "Misc/MonitoredProcess.h"
#include "Modules/ModuleManager.h"
#include "Async/TaskGraphInterfaces.h"
#include "Framework/Docking/TabManager.h"
#include "Editor.h"
#include "EditorAnalytics.h"
#include "IUATHelperModule.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Editor/MainFrame/Public/Interfaces/IMainFrameModule.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Logging/TokenizedMessage.h"
#include "Logging/MessageLog.h"
#include "Developer/MessageLog/Public/IMessageLogListing.h"
#include "Developer/MessageLog/Public/MessageLogModule.h"
#include "Misc/UObjectToken.h"
#include "GameProjectGenerationModule.h"
#include "AnalyticsEventAttribute.h"
#include "ShaderCompiler.h"
#include "Developer/OutputLog/Public/OutputLogModule.h"
#define LOCTEXT_NAMESPACE "UATHelper"
DEFINE_LOG_CATEGORY_STATIC(UATHelper, Log, All);
/* Event Data
*****************************************************************************/
struct EventData
{
FString EventName;
bool bProjectHasCode;
double StartTime;
IUATHelperModule::UatTaskResultCallack ResultCallback;
};
/* FMainFrameActionCallbacks callbacks
*****************************************************************************/
class FMainFrameActionsNotificationTask
{
public:
FMainFrameActionsNotificationTask(TWeakPtr<SNotificationItem> InNotificationItemPtr, SNotificationItem::ECompletionState InCompletionState, const FText& InText, const FText& InLinkText = FText(), bool InExpireAndFadeout = true)
: CompletionState(InCompletionState)
, NotificationItemPtr(InNotificationItemPtr)
, Text(InText)
, LinkText(InLinkText)
, bExpireAndFadeout(InExpireAndFadeout)
{ }
static void HandleHyperlinkNavigate()
{
FMessageLog("PackagingResults").Open(EMessageSeverity::Error, true);
}
static void HandleDismissButtonClicked()
{
TSharedPtr<SNotificationItem> NotificationItem = ExpireNotificationItemPtr.Pin();
if (NotificationItem.IsValid())
{
NotificationItem->SetExpireDuration(0.0f);
NotificationItem->SetFadeOutDuration(0.0f);
NotificationItem->SetCompletionState(SNotificationItem::CS_Fail);
NotificationItem->ExpireAndFadeout();
ExpireNotificationItemPtr.Reset();
}
}
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
if ( NotificationItemPtr.IsValid() )
{
if (CompletionState == SNotificationItem::CS_Fail)
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"));
}
else
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue"));
}
TSharedPtr<SNotificationItem> NotificationItem = NotificationItemPtr.Pin();
NotificationItem->SetText(Text);
if (!LinkText.IsEmpty())
{
FText VLinkText(LinkText);
const TAttribute<FText> Message = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateLambda([VLinkText]()
{
return VLinkText;
}));
NotificationItem->SetHyperlink(FSimpleDelegate::CreateStatic(&HandleHyperlinkNavigate), Message);
}
ExpireNotificationItemPtr = NotificationItem;
if (bExpireAndFadeout)
{
NotificationItem->SetExpireDuration(6.0f);
NotificationItem->SetFadeOutDuration(0.5f);
NotificationItem->SetCompletionState(CompletionState);
NotificationItem->ExpireAndFadeout();
}
else
{
// Handling the notification expiration in callback
NotificationItem->SetCompletionState(CompletionState);
}
// Since the task was probably fairly long, we should try and grab the users attention if they have the option enabled.
const UEditorPerProjectUserSettings* SettingsPtr = GetDefault<UEditorPerProjectUserSettings>();
if (SettingsPtr->bGetAttentionOnUATCompletion)
{
IMainFrameModule* MainFrame = FModuleManager::LoadModulePtr<IMainFrameModule>("MainFrame");
if (MainFrame != nullptr)
{
TSharedPtr<SWindow> ParentWindow = MainFrame->GetParentWindow();
if (ParentWindow != nullptr)
{
ParentWindow->DrawAttention(FWindowDrawAttentionParameters(EWindowDrawAttentionRequestType::UntilActivated));
}
}
}
}
}
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
ENamedThreads::Type GetDesiredThread() { return ENamedThreads::GameThread; }
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMainFrameActionsNotificationTask, STATGROUP_TaskGraphTasks);
}
private:
static TWeakPtr<SNotificationItem> ExpireNotificationItemPtr;
SNotificationItem::ECompletionState CompletionState;
TWeakPtr<SNotificationItem> NotificationItemPtr;
FText Text;
FText LinkText;
bool bExpireAndFadeout;
};
TWeakPtr<SNotificationItem> FMainFrameActionsNotificationTask::ExpireNotificationItemPtr;
/**
* Helper class to deal with packaging issues encountered in UAT.
**/
class FPackagingErrorHandler
{
public:
private:
/**
* Create a message to send to the Message Log.
*
* @Param MessageString - The error we wish to send to the Message Log.
* @Param MessageType - The severity of the message, i.e. error, warning etc.
**/
static void AddMessageToMessageLog(FString MessageString, EMessageSeverity::Type MessageType)
{
if (!bSawSummary && (MessageType == EMessageSeverity::Error || MessageType == EMessageSeverity::Warning))
{
FAssetData AssetData;
// Parse the warning/error into an array and check whether there is an asset on the path
TArray<FString> MessageArray;
MessageString.ParseIntoArray(MessageArray, TEXT(": "), true);
FString AssetPath = MessageArray.Num() > 0 ? MessageArray[0] : TEXT("");
if (AssetPath.Len())
{
// Convert from the asset's full path provided by UE_ASSET_LOG back to an AssetData, if possible
FString LongPackageName;
FPaths::NormalizeFilename(AssetPath);
if (!FPaths::IsRelative(AssetPath) && FPackageName::TryConvertFilenameToLongPackageName(AssetPath, LongPackageName))
{
// Generate qualified asset path and query the registry
AssetPath = LongPackageName + TEXT(".") + FPackageName::GetShortName(LongPackageName);
FName AssetPathName(*AssetPath, FNAME_Find);
if (!AssetPathName.IsNone())
{
static const FName AssetRegistryModuleName(TEXT("AssetRegistry"));
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryModuleName);
AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(AssetPathName, true);
}
}
}
if (AssetData.IsValid())
{
// we have asset errors in the cook
if (MessageType == EMessageSeverity::Error)
{
bHasAssetErrors = true;
}
FMessageLog Message("PackagingResults");
TSharedRef<FTokenizedMessage> PackagingMsg = MessageType == EMessageSeverity::Error ? Message.Error() : Message.Warning();
PackagingMsg->AddToken(FTextToken::Create(FText::FromString(MessageArray.Num() > 1 ? MessageArray[1] : MessageString)));
if (AssetData.IsValid())
{
PackagingMsg->AddToken(FUObjectToken::Create(AssetData.GetAsset()));
}
}
}
// note: CookResults:Warning: actually outputs some unhandled errors.
FText MsgText = FText::FromString(MessageString);
TSharedRef<FTokenizedMessage> Message = FTokenizedMessage::Create(MessageType);
Message->AddToken(FTextToken::Create(MsgText));
FMessageLog MessageLog("PackagingResults");
MessageLog.AddMessage(Message);
}
/**
* Send Error to the Message Log.
*
* @Param MessageString - The error we wish to send to the Message Log.
* @Param MessageType - The severity of the message, i.e. error, warning etc.
**/
static void SyncMessageWithMessageLog(FString MessageString, EMessageSeverity::Type MessageType)
{
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.SendPackageErrorToMessageLog"),
STAT_FSimpleDelegateGraphTask_SendPackageErrorToMessageLog,
STATGROUP_TaskGraphTasks);
// Remove any new line terminators
MessageString.ReplaceInline(TEXT("\r"), TEXT(""));
MessageString.ReplaceInline(TEXT("\n"), TEXT(""));
/**
* Dispatch the error from packaging to the message log.
**/
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateStatic(&FPackagingErrorHandler::AddMessageToMessageLog, MessageString, MessageType),
GET_STATID(STAT_FSimpleDelegateGraphTask_SendPackageErrorToMessageLog),
nullptr, ENamedThreads::GameThread
);
}
// Whether there are asset errors in the cook, which can be navigated to in the content browser.
static bool bHasAssetErrors;
// Whether the cook summary has been seen in the log.
static bool bSawSummary;
public:
/**
* Determine if the output is an communication message we wish to process.
*
* @Param UATOutput - The current line of output from the UAT package process.
**/
static bool ProcessAndHandleCookMessageOutput(FString UATOutput)
{
FString LhsUATOutputMsg, ParsedCookIssue;
if (UATOutput.Split(TEXT("Shaders left to compile "), &LhsUATOutputMsg, &ParsedCookIssue))
{
if (GShaderCompilingManager)
{
if (ParsedCookIssue.IsNumeric())
{
int32 ShadersLeft = FCString::Atoi(*ParsedCookIssue);
GShaderCompilingManager->SetExternalJobs(ShadersLeft);
}
}
return false;
}
return true;
}
/**
* Determine if the output is an error we wish to send to the Message Log.
*
* @Param UATOutput - The current line of output from the UAT package process.
**/
static void ProcessAndHandleCookErrorOutput(FString UATOutput)
{
FString LhsUATOutputMsg, ParsedCookIssue;
// we don't want to report duplicate warnings/errors to the package results log
// so, only add messages between the cook start and the summary
if (UATOutput.Contains(TEXT("Warning/Error Summary")))
{
bSawSummary = true;
}
// note: CookResults:Warning: actually outputs some unhandled errors.
if ( UATOutput.Split(TEXT("CookResults:Warning: "), &LhsUATOutputMsg, &ParsedCookIssue) )
{
SyncMessageWithMessageLog(ParsedCookIssue, EMessageSeverity::Warning);
}
else if ( UATOutput.Split(TEXT("CookResults:Error: "), &LhsUATOutputMsg, &ParsedCookIssue) )
{
SyncMessageWithMessageLog(ParsedCookIssue, EMessageSeverity::Error);
}
else if (!bSawSummary && UATOutput.Split(TEXT("Warning: "), &LhsUATOutputMsg, &ParsedCookIssue))
{
SyncMessageWithMessageLog(ParsedCookIssue, EMessageSeverity::Warning);
}
else if (!bSawSummary && UATOutput.Split(TEXT("Error: "), &LhsUATOutputMsg, &ParsedCookIssue))
{
SyncMessageWithMessageLog(ParsedCookIssue, EMessageSeverity::Error);
}
}
/**
* Send the UAT Packaging error message to the Message Log.
*
* @Param ErrorCode - The UAT return code we received and wish to display the error message for.
**/
static void SendPackagingErrorToMessageLog(int32 ErrorCode)
{
SyncMessageWithMessageLog(FEditorAnalytics::TranslateErrorCode(ErrorCode), EMessageSeverity::Error);
}
/**
* Get Whether the UAT process returned asset errors via the log
**/
static bool GetHasAssetErrors() { return bHasAssetErrors; }
/**
* Clear the asset error state for the UAT process
**/
static void ClearAssetErrors() { bHasAssetErrors = false; bSawSummary = false; }
};
bool FPackagingErrorHandler::bHasAssetErrors = false;
bool FPackagingErrorHandler::bSawSummary = false;
DECLARE_CYCLE_STAT(TEXT("Requesting FUATHelperModule::HandleUatProcessCompleted message dialog to present the error message"), STAT_FUATHelperModule_HandleUatProcessCompleted_DialogMessage, STATGROUP_TaskGraphTasks);
class FUATHelperModule : public IUATHelperModule
{
public:
FUATHelperModule()
{
}
virtual void StartupModule() override
{
}
virtual void ShutdownModule() override
{
}
virtual void CreateUatTask( const FString& CommandLine, const FText& PlatformDisplayName, const FText& TaskName, const FText &TaskShortName, const FSlateBrush* TaskIcon, UatTaskResultCallack ResultCallback, const FString& ResultLocation)
{
// If this is a packaging or cooking task, clear the PackagingResults log
if (!TaskShortName.CompareToCaseIgnored(FText::FromString(TEXT("Packaging"))) ||
!TaskShortName.CompareToCaseIgnored(FText::FromString(TEXT("Cooking"))))
{
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
if (MessageLogModule.IsRegisteredLogListing(TEXT("PackagingResults")))
{
TSharedRef<IMessageLogListing> PackagingResultsListing = MessageLogModule.GetLogListing(TEXT("PackagingResults"));
PackagingResultsListing->ClearMessages();
}
}
FGameProjectGenerationModule& GameProjectModule = FModuleManager::LoadModuleChecked<FGameProjectGenerationModule>(TEXT("GameProjectGeneration"));
bool bHasCode = GameProjectModule.Get().ProjectHasCodeFiles();
// make sure UAT exists
if (!FPaths::FileExists(FSerializedUATProcess::GetUATPath()))
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("File"), FText::FromString(FSerializedUATProcess::GetUATPath()));
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("RequiredFileNotFoundMessage", "A required file could not be found:\n{File}"), Arguments));
TArray<FAnalyticsEventAttribute> ParamArray;
ParamArray.Add(FAnalyticsEventAttribute(TEXT("Time"), 0.0));
FString EventName = (CommandLine.Contains(TEXT("-package")) ? TEXT("Editor.Package") : TEXT("Editor.Cook"));
FEditorAnalytics::ReportEvent(EventName + TEXT(".Failed"), PlatformDisplayName.ToString(), bHasCode, EAnalyticsErrorCodes::UATNotFound, ParamArray);
return;
}
TSharedPtr<FSerializedUATProcess> UatProcess = MakeShareable(new FSerializedUATProcess(CommandLine));
FPackagingErrorHandler::ClearAssetErrors();
// create notification item
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("Platform"), PlatformDisplayName);
Arguments.Add(TEXT("TaskName"), TaskName);
FText NotificationFormat = (PlatformDisplayName.IsEmpty()) ? LOCTEXT("UatTaskInProgressNotificationNoPlatform", "{TaskName}...") : LOCTEXT("UatTaskInProgressNotification", "{TaskName} for {Platform}...");
FNotificationInfo Info( FText::Format( NotificationFormat, Arguments) );
Info.Image = TaskIcon;
Info.bFireAndForget = false;
Info.FadeOutDuration = 0.0f;
Info.ExpireDuration = 0.0f;
Info.Hyperlink = FSimpleDelegate::CreateStatic(&FUATHelperModule::HandleUatHyperlinkNavigate);
Info.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log");
Info.ButtonDetails.Add(
FNotificationButtonInfo(
LOCTEXT("UatTaskCancel", "Cancel"),
LOCTEXT("UatTaskCancelToolTip", "Cancels execution of this task."),
FSimpleDelegate::CreateStatic(&FUATHelperModule::HandleUatCancelButtonClicked, UatProcess),
SNotificationItem::CS_Pending
)
);
Info.ButtonDetails.Add(
FNotificationButtonInfo(
LOCTEXT("UatTaskDismiss", "Dismiss"),
FText(),
FSimpleDelegate::CreateStatic(&FMainFrameActionsNotificationTask::HandleDismissButtonClicked),
SNotificationItem::CS_Fail
)
);
TSharedPtr<SNotificationItem> NotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
TSharedPtr<SNotificationItem> OldNotification = NotificationItemPtr.Pin();
if(OldNotification.IsValid())
{
OldNotification->Fadeout();
}
if (!NotificationItem.IsValid())
{
return;
}
FString EventName = (CommandLine.Contains(TEXT("-package")) ? TEXT("Editor.Package") : TEXT("Editor.Cook"));
FEditorAnalytics::ReportEvent(EventName + TEXT(".Start"), PlatformDisplayName.ToString(), bHasCode);
NotificationItem->SetCompletionState(SNotificationItem::CS_Pending);
// launch the packager
NotificationItemPtr = NotificationItem;
EventData Data;
Data.StartTime = FPlatformTime::Seconds();
Data.EventName = EventName;
Data.bProjectHasCode = bHasCode;
Data.ResultCallback = ResultCallback;
UatProcess->OnCanceled().BindStatic(&FUATHelperModule::HandleUatProcessCanceled, NotificationItemPtr, PlatformDisplayName, TaskShortName, Data);
UatProcess->OnCompleted().BindStatic(&FUATHelperModule::HandleUatProcessCompleted, NotificationItemPtr, PlatformDisplayName, TaskShortName, Data, ResultLocation);
UatProcess->OnOutput().BindStatic(&FUATHelperModule::HandleUatProcessOutput, NotificationItemPtr, PlatformDisplayName, TaskShortName);
UatProcess->OnLaunchFailed().BindStatic(&FUATHelperModule::HandleUatLaunchFailed, NotificationItemPtr, PlatformDisplayName, TaskShortName, Data);
TWeakPtr<FSerializedUATProcess> UatProcessPtr(UatProcess);
FEditorDelegates::OnShutdownPostPackagesSaved.Add(FSimpleDelegate::CreateStatic(&FUATHelperModule::HandleUatCancelButtonClicked, UatProcessPtr));
UatProcess->Launch();
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileStart_Cue.CompileStart_Cue"));
}
static void HandleUatHyperlinkNavigate()
{
FOutputLogModule& OutputLogModule = FOutputLogModule::Get();
OutputLogModule.FocusOutputLog();
}
static void HandleUatResultHyperlinkNavigate(FString ResultLocation)
{
if (!ResultLocation.IsEmpty())
{
FPlatformProcess::ExploreFolder(*(ResultLocation));
}
}
static void HandleUatCancelButtonClicked(TSharedPtr<FSerializedUATProcess> PackagerProcess)
{
if ( PackagerProcess.IsValid() )
{
PackagerProcess->Cancel(true);
}
}
static void HandleUatCancelButtonClicked(TWeakPtr<FSerializedUATProcess> PackagerProcessPtr)
{
TSharedPtr<FMonitoredProcess> PackagerProcess = PackagerProcessPtr.Pin();
if ( PackagerProcess.IsValid() )
{
PackagerProcess->Cancel(true);
}
}
static void HandleUatProcessCanceled(TWeakPtr<SNotificationItem> NotificationItemPtr, FText PlatformDisplayName, FText TaskName, EventData Event)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("Platform"), PlatformDisplayName);
Arguments.Add(TEXT("TaskName"), TaskName);
TGraphTask<FMainFrameActionsNotificationTask>::CreateTask().ConstructAndDispatchWhenReady(
NotificationItemPtr,
SNotificationItem::CS_Fail,
FText::Format(LOCTEXT("UatProcessFailedNotification", "{TaskName} canceled!"), Arguments)
);
TArray<FAnalyticsEventAttribute> ParamArray;
const double TimeSec = FPlatformTime::Seconds() - Event.StartTime;
ParamArray.Add(FAnalyticsEventAttribute(TEXT("Time"), TimeSec));
FEditorAnalytics::ReportEvent(Event.EventName + TEXT(".Canceled"), PlatformDisplayName.ToString(), Event.bProjectHasCode, ParamArray);
if (Event.ResultCallback)
{
Event.ResultCallback(TEXT("Canceled"), TimeSec);
}
if (GShaderCompilingManager)
{
GShaderCompilingManager->SetExternalJobs(0);
}
// FMessageLog("PackagingResults").Warning(FText::Format(LOCTEXT("UatProcessCanceledMessageLog", "{TaskName} for {Platform} canceled by user"), Arguments));
}
static void HandleUatProcessCompleted(int32 ReturnCode, TWeakPtr<SNotificationItem> NotificationItemPtr, FText PlatformDisplayName, FText TaskName, EventData Event, FString ResultLocation)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("Platform"), PlatformDisplayName);
Arguments.Add(TEXT("TaskName"), TaskName);
const double TimeSec = FPlatformTime::Seconds() - Event.StartTime;
if ( ReturnCode == 0 )
{
if (!ResultLocation.IsEmpty())
{
if (TSharedPtr<SNotificationItem> SharedNotificationItemPtr = NotificationItemPtr.Pin())
{
SharedNotificationItemPtr->SetHyperlink(FSimpleDelegate::CreateStatic(&FUATHelperModule::HandleUatResultHyperlinkNavigate, ResultLocation), LOCTEXT("ShowOutputLocation", "Show in Explorer"));
}
}
TGraphTask<FMainFrameActionsNotificationTask>::CreateTask().ConstructAndDispatchWhenReady(
NotificationItemPtr,
SNotificationItem::CS_Success,
FText::Format(LOCTEXT("UatProcessSucceededNotification", "{TaskName} complete!"), Arguments)
);
TArray<FAnalyticsEventAttribute> ParamArray;
ParamArray.Add(FAnalyticsEventAttribute(TEXT("Time"), TimeSec));
FEditorAnalytics::ReportEvent(Event.EventName + TEXT(".Completed"), PlatformDisplayName.ToString(), Event.bProjectHasCode, ParamArray);
if (Event.ResultCallback)
{
Event.ResultCallback(TEXT("Completed"), TimeSec);
}
// FMessageLog("PackagingResults").Info(FText::Format(LOCTEXT("UatProcessSuccessMessageLog", "{TaskName} for {Platform} completed successfully"), Arguments));
}
else
{
TGraphTask<FMainFrameActionsNotificationTask>::CreateTask().ConstructAndDispatchWhenReady(
NotificationItemPtr,
SNotificationItem::CS_Fail,
FText::Format(LOCTEXT("PackagerFailedNotification", "{TaskName} failed!"), Arguments),
FPackagingErrorHandler::GetHasAssetErrors() ? LOCTEXT("ShowResultsLogHyperlink", "Show Results Log") : FText(),
false);
TArray<FAnalyticsEventAttribute> ParamArray;
ParamArray.Add(FAnalyticsEventAttribute(TEXT("Time"), TimeSec));
FEditorAnalytics::ReportEvent(Event.EventName + TEXT(".Failed"), PlatformDisplayName.ToString(), Event.bProjectHasCode, ReturnCode, ParamArray);
if (Event.ResultCallback)
{
Event.ResultCallback(TEXT("Failed"), TimeSec);
}
// Send the error to the Message Log.
if ( TaskName.EqualTo(LOCTEXT("PackagingTaskName", "Packaging")) )
{
FPackagingErrorHandler::SendPackagingErrorToMessageLog(ReturnCode);
}
// Present a message dialog if we want the error message to be prominent.
if ( FEditorAnalytics::ShouldElevateMessageThroughDialog(ReturnCode) )
{
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateLambda([=] (){
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(FEditorAnalytics::TranslateErrorCode(ReturnCode)));
}),
GET_STATID(STAT_FUATHelperModule_HandleUatProcessCompleted_DialogMessage),
nullptr,
ENamedThreads::GameThread
);
}
// FMessageLog("PackagingResults").Info(FText::Format(LOCTEXT("UatProcessFailedMessageLog", "{TaskName} for {Platform} failed"), Arguments));
}
if (GShaderCompilingManager)
{
GShaderCompilingManager->SetExternalJobs(0);
}
}
static void HandleUatProcessOutput(FString Output, TWeakPtr<SNotificationItem> NotificationItemPtr, FText PlatformDisplayName, FText TaskName)
{
if ( !Output.IsEmpty() && !Output.Equals("\r") )
{
bool bDisplayLog = true;
if (TaskName.EqualTo(LOCTEXT("PackagingTaskName", "Packaging")))
{
// Deal with any cook messages that may have been encountered.
bDisplayLog = FPackagingErrorHandler::ProcessAndHandleCookMessageOutput(Output);
}
if (bDisplayLog)
{
UE_LOG(UATHelper, Log, TEXT("%s (%s): %s"), *TaskName.ToString(), *PlatformDisplayName.ToString(), *Output);
}
if ( TaskName.EqualTo(LOCTEXT("PackagingTaskName", "Packaging")) )
{
// Deal with any cook errors that may have been encountered.
FPackagingErrorHandler::ProcessAndHandleCookErrorOutput(Output);
}
if (TaskName.EqualTo(LOCTEXT("CookingTaskName", "Cooking")))
{
// Deal with any cook errors that may have been encountered.
FPackagingErrorHandler::ProcessAndHandleCookErrorOutput(Output);
}
}
}
static void HandleUatLaunchFailed(TWeakPtr<SNotificationItem> NotificationItemPtr, FText PlatformDisplayName, FText TaskName, EventData Event)
{
GEditor->PlayEditorSound(TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"));
TGraphTask<FMainFrameActionsNotificationTask>::CreateTask().ConstructAndDispatchWhenReady(
NotificationItemPtr,
SNotificationItem::CS_Fail,
LOCTEXT("UatLaunchFailedNotification", "Failed to launch Unreal Automation Tool (UAT)!")
);
TArray<FAnalyticsEventAttribute> ParamArray;
ParamArray.Add(FAnalyticsEventAttribute(TEXT("Time"), 0.0));
FEditorAnalytics::ReportEvent(Event.EventName + TEXT(".Failed"), PlatformDisplayName.ToString(), Event.bProjectHasCode, EAnalyticsErrorCodes::UATLaunchFailure, ParamArray);
if (Event.ResultCallback)
{
Event.ResultCallback(TEXT("FailedToStart"), 0.0f);
}
}
private:
TWeakPtr<SNotificationItem> NotificationItemPtr;
};
IMPLEMENT_MODULE(FUATHelperModule, UATHelper)
#undef LOCTEXT_NAMESPACE