// Copyright Epic Games, Inc. All Rights Reserved. #include "ProcessWrapper.h" #include "GenericPlatform/GenericPlatformTime.h" #include "HAL/PlatformProcess.h" #include "Logging/SubmitToolLog.h" #include "Misc/DateTime.h" #include "ProcessPipes.h" FProcessWrapper::FProcessWrapper(const FString& InProcessName, const FString& InPath, const FString& InArgs, const FOnCompleted& InOnCompleted, const FOnOutputLine& InOnOutputLine, const FString& InWorkingDir, const bool InbLaunchHidden, const bool bLaunchReallyHidden, const bool InbLaunchDetached, bool InbOwnsProcessLifetime) : ProcessName(InProcessName) , Path(InPath) , Args(InArgs) , WorkingDir(InWorkingDir) , bLaunchesHidden(InbLaunchHidden) , bLaunchesReallyHidden(bLaunchReallyHidden) , bLaunchDetached(InbLaunchDetached) , bOwnsProcessLifetime(InbOwnsProcessLifetime) , Pipes(MakeUnique()) , OnCompleted(InOnCompleted) , OnOutputLine(InOnOutputLine) {} FProcessWrapper::~FProcessWrapper() { if (bOwnsProcessLifetime) { Stop(); } else { Cleanup(); } } bool FProcessWrapper::Start(bool bWaitForExit) { if(IsRunning()) { OutputLine(FString::Format(TEXT("Process %s already running, ignored start request"), { *ProcessName }), EProcessOutputType::ProcessError); return false; } OutputRemainder = FString(); ExecutingTime = 0; if (!Pipes->Create()) { OutputLine(FString::Format(TEXT("Error creating pipes in process {0}"), { *ProcessName }), EProcessOutputType::ProcessError); ExitCode = 11; bIsComplete = true; return false; } bStarted = true; OutputLine(FString::Format(TEXT("Running process {0}: {1} {2}"), { *ProcessName, *Path, *Args }), EProcessOutputType::ProcessInfo); ProcessHandle = MakeUnique(FPlatformProcess::CreateProc ( *Path, *Args, bLaunchDetached, bLaunchesHidden, bLaunchesReallyHidden, nullptr, 0, WorkingDir.Len() ? *WorkingDir : nullptr, Pipes->GetStdOutForProcess(), Pipes->GetStdInForProcess()) ); if (!ProcessHandle->IsValid()) { OutputLine(FString::Format(TEXT("Error creating process {0}"), { *ProcessName }), EProcessOutputType::ProcessError); ExitCode = 11; bIsComplete = true; return false; } if(bWaitForExit) { FDateTime Before = FDateTime::UtcNow(); FPlatformProcess::WaitForProc(*ProcessHandle); FTimespan Timespan = FDateTime::UtcNow() - Before; OnTick(Timespan.GetTotalSeconds()); return true; } else { TickerHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FProcessWrapper::OnTick)); return true; } } void FProcessWrapper::Stop() { if(IsRunning()) { OutputLine(FString::Format(TEXT("Process {0} was stopped"), { *ProcessName }), EProcessOutputType::ProcessInfo); FPlatformProcess::TerminateProc(*ProcessHandle, true); } ExitCode = 10; bIsComplete = true; Cleanup(); } bool FProcessWrapper::IsRunning() const { return ProcessHandle.IsValid() && FPlatformProcess::IsProcRunning(*ProcessHandle); } bool FProcessWrapper::OnTick(float Delta) { if(!ProcessHandle.IsValid()) { return false; } if(FPlatformProcess::IsProcRunning(*ProcessHandle)) { ReadOutput(false); ExecutingTime += Delta; return true; } ExecutingTime += Delta; // Flush output ReadOutput(true); FPlatformProcess::GetProcReturnCode(*ProcessHandle, &ExitCode); bIsComplete = true; OutputLine(FString::Printf(TEXT("Completed running process %s. Process took %s and exited with code %d"), *ProcessName, *FGenericPlatformTime::PrettyTime(ExecutingTime), ExitCode), EProcessOutputType::ProcessInfo); Cleanup(); if(OnCompleted.IsBound()) { OnCompleted.ExecuteIfBound(ExitCode); } return false; } void FProcessWrapper::Cleanup() { Pipes->Reset(); ProcessHandle = nullptr; FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle); TickerHandle = nullptr; } void FProcessWrapper::ReadOutput(bool FlushOutput) { if(!OnOutputLine.IsBound()) { return; } FString NewOutput = OutputRemainder + FPlatformProcess::ReadPipe(Pipes->GetStdOutForReading()); if(!NewOutput.IsEmpty() && (NewOutput.Contains(TEXT("\n")) || FlushOutput)) { // Find the last line, which may be truncated if(!FlushOutput) { int32 Position; NewOutput.FindLastChar('\n', Position); OutputRemainder = NewOutput.Mid(Position + 1); NewOutput.RemoveFromEnd(OutputRemainder); } TArray Lines; const TCHAR* Separators[] = { TEXT("\n"), TEXT("\r") }; NewOutput.ParseIntoArray(Lines, Separators, UE_ARRAY_COUNT(Separators)); for(const FString& Line : Lines) { OutputLine(Line, EProcessOutputType::SDTOutput); } } } void FProcessWrapper::OutputLine(const FString OutputLine, const EProcessOutputType& OutputType) { if(OnOutputLine.IsBound()) { OnOutputLine.ExecuteIfBound(OutputLine, OutputType); } }