Integrating latest from github/staging
Integrating up through commit 5e1bdae
This commit is contained in:
+11
-10
@@ -61,18 +61,19 @@ namespace AzFramework::AssetSystem::Platform
|
||||
}
|
||||
}
|
||||
|
||||
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view appRoot,
|
||||
AZStd::string_view gameProjectName)
|
||||
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view engineRoot,
|
||||
AZStd::string_view projectPath)
|
||||
{
|
||||
AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory };
|
||||
assetProcessorPath /= "AssetProcessor.exe";
|
||||
|
||||
auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"("%s" --start-hidden)", assetProcessorPath.c_str());
|
||||
// Add the app-root to the launch command if not empty
|
||||
if (!appRoot.empty())
|
||||
|
||||
// Add the engine path to the launch command if not empty
|
||||
if (!engineRoot.empty())
|
||||
{
|
||||
fullLaunchCommand += R"( --app-root=")";
|
||||
fullLaunchCommand += appRoot;
|
||||
fullLaunchCommand += R"( --engine-path=")";
|
||||
fullLaunchCommand += engineRoot;
|
||||
// Windows CreateProcess has issues with paths that end with a trailing backslash
|
||||
// so remove it if it exist
|
||||
if (fullLaunchCommand.ends_with(AZ::IO::WindowsPathSeparator))
|
||||
@@ -82,11 +83,11 @@ namespace AzFramework::AssetSystem::Platform
|
||||
fullLaunchCommand += '"';
|
||||
}
|
||||
|
||||
// Add the active game project to the launch command if not empty
|
||||
if (!gameProjectName.empty())
|
||||
// Add the active project path to the launch command if not empty
|
||||
if (!projectPath.empty())
|
||||
{
|
||||
fullLaunchCommand += R"( --gameFolder=")";
|
||||
fullLaunchCommand += gameProjectName;
|
||||
fullLaunchCommand += R"( --project-path=")";
|
||||
fullLaunchCommand += projectPath;
|
||||
fullLaunchCommand += '"';
|
||||
}
|
||||
|
||||
|
||||
@@ -16,3 +16,6 @@
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 1
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL "python.cmd"
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 1
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PROCESSLAUNCH_DEFAULT 0
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class CommunicatorHandleImpl
|
||||
{
|
||||
public:
|
||||
~CommunicatorHandleImpl() = default;
|
||||
|
||||
bool IsPipe() const;
|
||||
bool IsValid() const;
|
||||
bool IsBroken() const;
|
||||
const HANDLE& GetHandle() const;
|
||||
|
||||
void Break();
|
||||
void Close();
|
||||
void SetHandle(const HANDLE& handle, bool isPipe);
|
||||
|
||||
protected:
|
||||
HANDLE m_handle = INVALID_HANDLE_VALUE;
|
||||
bool m_pipe = false;
|
||||
bool m_broken = false;
|
||||
};
|
||||
|
||||
struct ProcessData
|
||||
{
|
||||
ProcessData();
|
||||
|
||||
void Init(bool stdCommunication);
|
||||
|
||||
DWORD WaitForJobOrProcess(AZ::u32 waitTimeInMilliseconds) const;
|
||||
|
||||
PROCESS_INFORMATION processInformation;
|
||||
BOOL inheritHandles;
|
||||
STARTUPINFOW startupInfo;
|
||||
|
||||
HANDLE jobHandle;
|
||||
JOBOBJECT_ASSOCIATE_COMPLETION_PORT jobCompletionPort;
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
bool CommunicatorHandleImpl::IsPipe() const
|
||||
{
|
||||
return m_pipe;
|
||||
}
|
||||
|
||||
bool CommunicatorHandleImpl::IsValid() const
|
||||
{
|
||||
return m_handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
bool CommunicatorHandleImpl::IsBroken() const
|
||||
{
|
||||
return m_broken;
|
||||
}
|
||||
|
||||
const HANDLE& CommunicatorHandleImpl::GetHandle() const
|
||||
{
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::Break()
|
||||
{
|
||||
m_broken = true;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::Close()
|
||||
{
|
||||
CloseHandle(m_handle);
|
||||
m_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::SetHandle(const HANDLE& handle, bool isPipe)
|
||||
{
|
||||
m_handle = handle;
|
||||
m_pipe = isPipe;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::PeekHandle(StdProcessCommunicatorHandle& handle)
|
||||
{
|
||||
if (handle->IsBroken() || !handle->IsValid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD bytesAvailable = 0;
|
||||
BOOL result;
|
||||
if (handle->IsPipe())
|
||||
{
|
||||
result = PeekNamedPipe(handle->GetHandle(), NULL, 0, NULL, &bytesAvailable, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = GetNumberOfConsoleInputEvents(handle->GetHandle(), &bytesAvailable);
|
||||
}
|
||||
|
||||
DWORD error = 0;
|
||||
if (!result)
|
||||
{
|
||||
error = GetLastError();
|
||||
if (error == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
// Child process released pipe
|
||||
handle->Break();
|
||||
}
|
||||
}
|
||||
|
||||
AZ_Assert(result || error == ERROR_BROKEN_PIPE, "Peek failed with unexpected error %d", error);
|
||||
return bytesAvailable;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::ReadDataFromHandle(StdProcessCommunicatorHandle& handle, void* readBuffer, AZ::u32 bufferSize)
|
||||
{
|
||||
if (handle->IsBroken() || !handle->IsValid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD bytesRead = 0;
|
||||
BOOL result = ReadFile(handle->GetHandle(), readBuffer, bufferSize, &bytesRead, NULL);
|
||||
DWORD error = 0;
|
||||
if (!result)
|
||||
{
|
||||
error = GetLastError();
|
||||
AZ_Assert(error != ERROR_IO_PENDING, "ReadFile performed unexpected async io");
|
||||
if (error == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
// Child process exited, we may have read something, so return amount
|
||||
handle->Break();
|
||||
return bytesRead;
|
||||
}
|
||||
AZ_Assert(false, "Unexpected error from ReadFile %d", error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::WriteDataToHandle(StdProcessCommunicatorHandle& handle, const void* writeBuffer, AZ::u32 bytesToWrite)
|
||||
{
|
||||
AZ_Assert(writeBuffer, "Write buffer is null");
|
||||
if (!writeBuffer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (handle->IsBroken() || !handle->IsValid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD bytesWritten = 0;
|
||||
BOOL result = WriteFile(handle->GetHandle(), writeBuffer, bytesToWrite, &bytesWritten, nullptr);
|
||||
DWORD error = 0;
|
||||
if (!result)
|
||||
{
|
||||
error = GetLastError();
|
||||
AZ_Assert(error != ERROR_IO_PENDING, "WriteFile performed unexpected async io");
|
||||
if (error == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
// Child process exited, may have written something, so return amount
|
||||
handle->Break();
|
||||
return bytesWritten;
|
||||
}
|
||||
AZ_Assert(false, "Unexpected error from WriteFile %d", error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicator::CreatePipesForProcess(ProcessData* processData)
|
||||
{
|
||||
SECURITY_ATTRIBUTES securityAttributes;
|
||||
|
||||
// Set the bInheritHandle flag so pipe handles are inherited.
|
||||
securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
securityAttributes.bInheritHandle = TRUE;
|
||||
securityAttributes.lpSecurityDescriptor = NULL;
|
||||
|
||||
BOOL Result;
|
||||
|
||||
// Create a pipe to monitor process std out (input to us)
|
||||
HANDLE handle = nullptr;
|
||||
Result = CreatePipe(&handle, &processData->startupInfo.hStdOutput, &securityAttributes, 0);
|
||||
AZ_Assert(Result, "CreatePipe failed for std out pipe");
|
||||
if (!Result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the read handle to the pipe for std out is not inherited
|
||||
Result = SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
|
||||
AZ_Assert(Result, "Unable to disable inheritance on std out read handle");
|
||||
if (!Result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_stdOutRead->SetHandle(handle, true);
|
||||
|
||||
// Create a pipe to monitor process std in (output from us)
|
||||
handle = nullptr;
|
||||
Result = CreatePipe(&processData->startupInfo.hStdInput, &handle, &securityAttributes, 0);
|
||||
AZ_Assert(Result, "CreatePipe failed for std in pipe");
|
||||
if (!Result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the write handle to the pipe for std in is not inherited
|
||||
Result = SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
|
||||
AZ_Assert(Result, "Unable to disable inheritance on std in write handle");
|
||||
if (!Result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_stdInWrite->SetHandle(handle, false);
|
||||
|
||||
// Create a pipe to monitor process std error (input to us)
|
||||
handle = nullptr;
|
||||
Result = CreatePipe(&handle, &processData->startupInfo.hStdError, &securityAttributes, 0);
|
||||
AZ_Assert(Result, "CreatePipe failed for std err pipe");
|
||||
if (!Result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the read handle to the pipe for std err is not inherited
|
||||
Result = SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
|
||||
AZ_Assert(Result, "Unable to disable inheritance on std err read handle");
|
||||
if (!Result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_stdErrRead->SetHandle(handle, true);
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void StdInOutProcessCommunicator::WaitForReadyOutputs(OutputStatus& status) const
|
||||
{
|
||||
status.outputDeviceReady = m_stdOutRead->IsValid() && !m_stdOutRead->IsBroken();
|
||||
status.errorsDeviceReady = m_stdErrRead->IsValid() && !m_stdErrRead->IsBroken();
|
||||
status.shouldReadOutput = status.shouldReadErrors = false;
|
||||
|
||||
if (status.outputDeviceReady || status.errorsDeviceReady)
|
||||
{
|
||||
DWORD waitResult = 0;
|
||||
HANDLE waitHandles[2];
|
||||
AZ::u32 handleCount = 0;
|
||||
|
||||
if (status.outputDeviceReady)
|
||||
{
|
||||
waitHandles[handleCount++] = m_stdOutRead->GetHandle();
|
||||
}
|
||||
|
||||
if (status.errorsDeviceReady)
|
||||
{
|
||||
waitHandles[handleCount++] = m_stdErrRead->GetHandle();
|
||||
}
|
||||
|
||||
waitResult = WaitForMultipleObjects(handleCount, waitHandles, false, INFINITE);
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
// If output handle was present, that's the one that signaled, otherwise it was stdError
|
||||
status.shouldReadOutput = status.outputDeviceReady;
|
||||
status.shouldReadErrors = !status.shouldReadOutput;
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
// this can only ever be stdError
|
||||
status.shouldReadErrors = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicatorForChildProcess::AttachToExistingPipes()
|
||||
{
|
||||
m_stdOutWrite->SetHandle(GetStdHandle(STD_OUTPUT_HANDLE), false);
|
||||
AZ_Assert(m_stdOutWrite->IsValid(), "Unable to get valid handle for STD_OUTPUT_HANDLE");
|
||||
|
||||
m_stdErrWrite->SetHandle(GetStdHandle(STD_ERROR_HANDLE), false);
|
||||
AZ_Assert(m_stdErrWrite->IsValid(), "Unable to get valid handle for STD_ERROR_HANDLE");
|
||||
|
||||
HANDLE stdInRead = GetStdHandle(STD_INPUT_HANDLE);
|
||||
bool isPipe = false;
|
||||
if (stdInRead != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DWORD dummy;
|
||||
isPipe = !GetConsoleMode(stdInRead, &dummy);
|
||||
}
|
||||
|
||||
m_stdInRead->SetHandle(stdInRead, isPipe);
|
||||
AZ_Assert(m_stdInRead->IsValid(), "Unable to get valid handle for STD_INPUT_HANDLE");
|
||||
|
||||
m_initialized = m_stdOutWrite->IsValid() && m_stdErrWrite->IsValid() && m_stdInRead->IsValid();
|
||||
return m_initialized;
|
||||
}
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
#include <AzFramework/Process/ProcessCommon.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
ProcessData::ProcessData()
|
||||
{
|
||||
Init(false);
|
||||
}
|
||||
|
||||
void ProcessData::Init(bool stdCommunication)
|
||||
{
|
||||
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
|
||||
startupInfo.cb = sizeof(STARTUPINFO);
|
||||
ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
|
||||
if (stdCommunication)
|
||||
{
|
||||
startupInfo.dwFlags |= STARTF_USESTDHANDLES;
|
||||
inheritHandles = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
inheritHandles = FALSE;
|
||||
}
|
||||
|
||||
jobHandle = nullptr;
|
||||
ZeroMemory(&jobCompletionPort, sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT));
|
||||
}
|
||||
|
||||
DWORD ProcessData::WaitForJobOrProcess(AZ::u32 waitTimeInMilliseconds) const
|
||||
{
|
||||
if (jobHandle)
|
||||
{
|
||||
// The completion query for JobObjects needs to be sliced in order to properly mimic the behaviour of WaitForSingleObject. This is
|
||||
// because GetQueuedCompletionStatus can have several completion codes queued and the likelihood of the only event we care about
|
||||
// being first in the queue is slim. The choice of 5 attempts is arbitrary but seems to be enough to deplete the queue regardless
|
||||
// of whatever value waitTimeInMilliseconds is.
|
||||
const AZ::u32 totalWaitSteps = 5;
|
||||
const AZ::u32 slicedWaitTime = (waitTimeInMilliseconds / totalWaitSteps);
|
||||
|
||||
DWORD completionCode;
|
||||
ULONG_PTR completionKey;
|
||||
LPOVERLAPPED overlapped;
|
||||
|
||||
for (AZ::u32 waitStep = 0; waitStep < totalWaitSteps; ++waitStep)
|
||||
{
|
||||
if (GetQueuedCompletionStatus(jobCompletionPort.CompletionPort, &completionCode, &completionKey, &overlapped, slicedWaitTime))
|
||||
{
|
||||
if (reinterpret_cast<HANDLE>(completionKey) == jobHandle && completionCode == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO)
|
||||
{
|
||||
return WAIT_OBJECT_0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetLastError() == ERROR_ABANDONED_WAIT_0)
|
||||
{
|
||||
return WAIT_ABANDONED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WAIT_TIMEOUT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return WaitForSingleObject(processInformation.hProcess, waitTimeInMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchUnwatchedProcess(const ProcessLaunchInfo& processLaunchInfo)
|
||||
{
|
||||
ProcessData processData;
|
||||
processData.Init(false);
|
||||
return LaunchProcess(processLaunchInfo, processData);
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchProcess(const ProcessLaunchInfo& processLaunchInfo, ProcessData& processData)
|
||||
{
|
||||
BOOL result = FALSE;
|
||||
|
||||
// Windows API requires non-const char* command line string
|
||||
AZStd::wstring editableCommandLine;
|
||||
AZStd::wstring processExecutableString;
|
||||
AZStd::wstring workingDirectory;
|
||||
AZStd::to_wstring(editableCommandLine, processLaunchInfo.m_commandlineParameters);
|
||||
AZStd::to_wstring(processExecutableString, processLaunchInfo.m_processExecutableString);
|
||||
AZStd::to_wstring(workingDirectory, processLaunchInfo.m_workingDirectory);
|
||||
|
||||
AZStd::string environmentVariableBlock;
|
||||
if (processLaunchInfo.m_environmentVariables)
|
||||
{
|
||||
for (const auto& environmentVariable : *processLaunchInfo.m_environmentVariables)
|
||||
{
|
||||
environmentVariableBlock += environmentVariable;
|
||||
environmentVariableBlock.append(1, '\0');
|
||||
}
|
||||
environmentVariableBlock.append(processLaunchInfo.m_environmentVariables->size() ? 1 : 2, '\0'); // Double terminated, only need one if we ended with a null terminated string already
|
||||
}
|
||||
|
||||
// Show or hide window
|
||||
processData.startupInfo.dwFlags |= STARTF_USESHOWWINDOW;
|
||||
processData.startupInfo.wShowWindow = processLaunchInfo.m_showWindow ? SW_SHOW : SW_HIDE;
|
||||
|
||||
DWORD createFlags = 0;
|
||||
switch (processLaunchInfo.m_processPriority)
|
||||
{
|
||||
case PROCESSPRIORITY_BELOWNORMAL:
|
||||
createFlags |= BELOW_NORMAL_PRIORITY_CLASS;
|
||||
break;
|
||||
case PROCESSPRIORITY_IDLE:
|
||||
createFlags |= IDLE_PRIORITY_CLASS;
|
||||
break;
|
||||
}
|
||||
|
||||
processData.jobHandle = CreateJobObject(nullptr, nullptr);
|
||||
if (processData.jobHandle)
|
||||
{
|
||||
processData.jobCompletionPort.CompletionKey = processData.jobHandle;
|
||||
processData.jobCompletionPort.CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);
|
||||
|
||||
if (processData.jobCompletionPort.CompletionPort
|
||||
&& SetInformationJobObject(processData.jobHandle, JobObjectAssociateCompletionPortInformation, &processData.jobCompletionPort, sizeof(processData.jobCompletionPort)))
|
||||
{
|
||||
createFlags |= CREATE_SUSPENDED;
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseHandle(processData.jobCompletionPort.CompletionPort);
|
||||
ZeroMemory(&processData.jobCompletionPort, sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT));
|
||||
|
||||
CloseHandle(processData.jobHandle);
|
||||
processData.jobHandle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the child process.
|
||||
result = CreateProcessW(processExecutableString.size() ? processExecutableString.c_str() : NULL,
|
||||
editableCommandLine.size() ? editableCommandLine.data() : NULL, // command line
|
||||
NULL, // process security attributes
|
||||
NULL, // primary thread security attributes
|
||||
processData.inheritHandles,// handles might be inherited
|
||||
createFlags, // creation flags
|
||||
environmentVariableBlock.size() ? environmentVariableBlock.data() : NULL, // environmentVariableBlock is a proper double null terminated block constructed above
|
||||
workingDirectory.empty() ? nullptr : workingDirectory.c_str(), // use parent's current directory
|
||||
&processData.startupInfo, // STARTUPINFO pointer
|
||||
&processData.processInformation); // receives PROCESS_INFORMATION
|
||||
|
||||
if (result != TRUE)
|
||||
{
|
||||
if (GetLastError() == ERROR_FILE_NOT_FOUND)
|
||||
{
|
||||
processLaunchInfo.m_launchResult = PLR_MissingFile;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// attempt to attach the process to a job object so any additional child processes
|
||||
// that get spawned will be terminated correctly, if requested
|
||||
if (processData.jobHandle)
|
||||
{
|
||||
if (!AssignProcessToJobObject(processData.jobHandle, processData.processInformation.hProcess))
|
||||
{
|
||||
CloseHandle(processData.jobCompletionPort.CompletionPort);
|
||||
ZeroMemory(&processData.jobCompletionPort, sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT));
|
||||
|
||||
CloseHandle(processData.jobHandle);
|
||||
processData.jobHandle = nullptr;
|
||||
}
|
||||
|
||||
ResumeThread(processData.processInformation.hThread);
|
||||
}
|
||||
}
|
||||
|
||||
// Close inherited handles
|
||||
CloseHandle(processData.startupInfo.hStdInput);
|
||||
CloseHandle(processData.startupInfo.hStdOutput);
|
||||
CloseHandle(processData.startupInfo.hStdError);
|
||||
return result == TRUE;
|
||||
}
|
||||
|
||||
|
||||
ProcessWatcher* ProcessWatcher::LaunchProcess(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, AzFramework::ProcessCommunicationType communicationType)
|
||||
{
|
||||
ProcessWatcher* pWatcher = new ProcessWatcher {};
|
||||
if (!pWatcher->SpawnProcess(processLaunchInfo, communicationType))
|
||||
{
|
||||
delete pWatcher;
|
||||
return nullptr;
|
||||
}
|
||||
return pWatcher;
|
||||
}
|
||||
|
||||
|
||||
ProcessWatcher::ProcessWatcher()
|
||||
: m_pCommunicator(nullptr)
|
||||
{
|
||||
m_pWatcherData = AZStd::make_unique<ProcessData>();
|
||||
}
|
||||
|
||||
ProcessWatcher::~ProcessWatcher()
|
||||
{
|
||||
if (IsProcessRunning())
|
||||
{
|
||||
TerminateProcess(0);
|
||||
}
|
||||
|
||||
delete m_pCommunicator;
|
||||
CloseHandle(m_pWatcherData->processInformation.hProcess);
|
||||
CloseHandle(m_pWatcherData->processInformation.hThread);
|
||||
if (m_pWatcherData->jobHandle)
|
||||
{
|
||||
CloseHandle(m_pWatcherData->jobCompletionPort.CompletionPort);
|
||||
CloseHandle(m_pWatcherData->jobHandle);
|
||||
}
|
||||
}
|
||||
|
||||
StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()
|
||||
{
|
||||
return new StdInOutProcessCommunicator();
|
||||
}
|
||||
|
||||
StdProcessCommunicatorForChildProcess* ProcessWatcher::CreateStdCommunicatorForChildProcess()
|
||||
{
|
||||
return new StdInOutProcessCommunicatorForChildProcess();
|
||||
}
|
||||
|
||||
void ProcessWatcher::InitProcessData(bool stdCommunication)
|
||||
{
|
||||
m_pWatcherData->Init(stdCommunication);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// Returns true if process exited, false if still running
|
||||
bool CheckExitCode(const AzFramework::ProcessData* processData, AZ::u32* outExitCode = nullptr)
|
||||
{
|
||||
// Check exit code
|
||||
DWORD exitCode;
|
||||
BOOL result;
|
||||
|
||||
if (processData->jobHandle)
|
||||
{
|
||||
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jobInfo;
|
||||
result = QueryInformationJobObject(processData->jobHandle,
|
||||
JobObjectBasicAccountingInformation,
|
||||
&jobInfo,
|
||||
sizeof(jobInfo),
|
||||
nullptr);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
exitCode = 0;
|
||||
AZ_Warning("ProcessWatcher", false, "QueryInformationJobObject failed (%d), assuming process either failed to launch or terminated unexpectedly\n", GetLastError());
|
||||
}
|
||||
else if (jobInfo.ActiveProcesses != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = GetExitCodeProcess(processData->processInformation.hProcess, &exitCode);
|
||||
if (!result)
|
||||
{
|
||||
exitCode = 0;
|
||||
AZ_TracePrintf("ProcessWatcher", "GetExitCodeProcess failed (%d), assuming process either failed to launch or terminated unexpectedly\n", GetLastError());
|
||||
}
|
||||
|
||||
if (exitCode != STILL_ACTIVE)
|
||||
{
|
||||
if (outExitCode)
|
||||
{
|
||||
*outExitCode = static_cast<AZ::u32>(exitCode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessWatcher::IsProcessRunning(AZ::u32* outExitCode)
|
||||
{
|
||||
AZ_Assert(m_pWatcherData, "No watcher data");
|
||||
if (!m_pWatcherData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CheckExitCode(m_pWatcherData.get(), outExitCode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify process is not signaled
|
||||
DWORD waitResult = m_pWatcherData->WaitForJobOrProcess(0);
|
||||
|
||||
// if wait timed out, process still running.
|
||||
return waitResult == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::WaitForProcessToExit(AZ::u32 waitTimeInSeconds, AZ::u32* outExitCode /*= nullptr*/)
|
||||
{
|
||||
if (CheckExitCode(m_pWatcherData.get()))
|
||||
{
|
||||
// Already exited
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify process is not signaled
|
||||
DWORD waitResult = m_pWatcherData->WaitForJobOrProcess(waitTimeInSeconds * 1000);
|
||||
if ((outExitCode) && (waitResult != WAIT_TIMEOUT))
|
||||
{
|
||||
CheckExitCode(m_pWatcherData.get(), outExitCode);
|
||||
}
|
||||
|
||||
// if wait timed out, process still running.
|
||||
return waitResult != WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void ProcessWatcher::TerminateProcess(AZ::u32 exitCode)
|
||||
{
|
||||
AZ_Assert(m_pWatcherData, "No watcher data");
|
||||
if (!m_pWatcherData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsProcessRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pWatcherData->jobHandle)
|
||||
{
|
||||
TerminateJobObject(m_pWatcherData->jobHandle, exitCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
::TerminateProcess(m_pWatcherData->processInformation.hProcess, exitCode);
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzFramework/ProjectManager/ProjectManager.h>
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzFramework/Engine/Engine.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProjectManager
|
||||
{
|
||||
bool LaunchProjectManager()
|
||||
{
|
||||
const char projectsScript[] = "projects.py";
|
||||
|
||||
AZ_Warning("ProjectManager", false, "No project provided - launching project selector.");
|
||||
|
||||
AZ::IO::FixedMaxPath enginePath = Engine::FindEngineRoot();
|
||||
if (enginePath.empty())
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "Couldn't find engine root");
|
||||
return false;
|
||||
}
|
||||
auto projectManagerPath = enginePath / "scripts" / "project_manager";
|
||||
|
||||
if (!AZ::IO::SystemFile::Exists((projectManagerPath / projectsScript).c_str()))
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "%s not found at %s!", projectsScript, projectManagerPath.c_str());
|
||||
}
|
||||
char executablePath[AZ_MAX_PATH_LEN];
|
||||
AZ::Utils::GetExecutablePathReturnType result = AZ::Utils::GetExecutablePath(executablePath, AZ_MAX_PATH_LEN);
|
||||
auto exeFolder = AZ::IO::PathView(executablePath).ParentPath().Filename().Native();
|
||||
AZStd::fixed_string<10> debugOption{ " " };
|
||||
if (exeFolder == "debug")
|
||||
{
|
||||
// We need to use the debug version of the python interpreter to load up our debug version of our libraries which work with the debug version of QT living in this folder
|
||||
debugOption = " debug ";
|
||||
}
|
||||
AZ::IO::FixedMaxPath pythonPath = enginePath / "python" / "python.cmd";
|
||||
auto cmdPath = AZ::IO::FixedMaxPathString::format("%s%s%s --executable_path=%s", pythonPath.Native().c_str(), debugOption.c_str(), (projectManagerPath / projectsScript).c_str(), executablePath);
|
||||
|
||||
|
||||
STARTUPINFO si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
auto workingPath = AZ::IO::FixedMaxPath{ executablePath }.ParentPath().Native();
|
||||
bool launchSuccess = ::CreateProcessA(nullptr, cmdPath.data(), nullptr, nullptr, FALSE, 0, nullptr, projectManagerPath.c_str(), &si, &pi) != 0;
|
||||
if (launchSuccess)
|
||||
{
|
||||
AZ_TracePrintf("ProjectManagerSystemComponent", "Launched Project Manager successfully, shutting down.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto someError = GetLastError();
|
||||
AZ_Warning("ProjectManagerSystemComponent", false, "Failed to launch project manager with error %d", someError);
|
||||
}
|
||||
return launchSuccess;
|
||||
}
|
||||
} // ProjectManager
|
||||
} // AzFramework
|
||||
|
||||
@@ -16,7 +16,9 @@ set(FILES
|
||||
AzFramework/API/ApplicationAPI_Windows.h
|
||||
AzFramework/Application/Application_Windows.cpp
|
||||
AzFramework/Asset/AssetSystemComponentHelper_Windows.cpp
|
||||
AzFramework/ProjectManager/ProjectManager_Windows.cpp
|
||||
AzFramework/Process/ProcessWatcher_Win.cpp
|
||||
AzFramework/Process/ProcessCommon.h
|
||||
AzFramework/Process/ProcessCommunicator_Win.cpp
|
||||
../Common/WinAPI/AzFramework/IO/LocalFileIO_WinAPI.cpp
|
||||
AzFramework/IO/LocalFileIO_Windows.cpp
|
||||
../Common/WinAPI/AzFramework/Network/AssetProcessorConnection_WinAPI.cpp
|
||||
|
||||
Reference in New Issue
Block a user