688 lines
24 KiB
C++
688 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 "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 "IMessageLogListing.h"
|
|
#include "MessageLogModule.h"
|
|
#include "Misc/UObjectToken.h"
|
|
|
|
#include "GameProjectGenerationModule.h"
|
|
#include "AnalyticsEventAttribute.h"
|
|
|
|
#include "ShaderCompiler.h"
|
|
#include "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);
|
|
FSoftObjectPath SoftAssetPath(AssetPath);
|
|
if (!SoftAssetPath.IsNull())
|
|
{
|
|
static const FName AssetRegistryModuleName(TEXT("AssetRegistry"));
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryModuleName);
|
|
AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(SoftAssetPath, 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, const TArray<FAnalyticsEventAttribute>* OptionalAnalyticsParamArray, 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"));
|
|
if (OptionalAnalyticsParamArray != nullptr)
|
|
{
|
|
FEditorAnalytics::ReportEvent(EventName + TEXT(".Start"), PlatformDisplayName.ToString(), bHasCode, *OptionalAnalyticsParamArray);
|
|
}
|
|
else
|
|
{
|
|
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
|