Integrating latest from github/staging
Integrating up through commit 5e1bdae
This commit is contained in:
@@ -16,3 +16,6 @@
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 1
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PROCESSLAUNCH_DEFAULT 0
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class CommunicatorHandleImpl
|
||||
{
|
||||
public:
|
||||
~CommunicatorHandleImpl() = default;
|
||||
|
||||
bool IsValid() const { return false; }
|
||||
bool IsBroken() const { return false; }
|
||||
int GetHandle() const { return -1; }
|
||||
|
||||
void Break() {}
|
||||
void Close() {}
|
||||
void SetHandle([[maybe_unused]] int handle) {}
|
||||
|
||||
};
|
||||
|
||||
struct StartupInfo
|
||||
{
|
||||
~StartupInfo();
|
||||
|
||||
void SetupHandlesForChildProcess();
|
||||
void CloseAllHandles();
|
||||
|
||||
int m_inputHandleForChild = -1;
|
||||
int m_outputHandleForChild = -1;
|
||||
int m_errorHandleForChild = -1;
|
||||
};
|
||||
|
||||
struct ProcessData
|
||||
{
|
||||
StartupInfo m_startupInfo;
|
||||
int m_childProcessId = 0;
|
||||
bool m_childProcessIsDone = false;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
AZ::u32 StdInOutCommunication::PeekHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::ReadDataFromHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle, [[maybe_unused]] void* readBuffer, [[maybe_unused]] AZ::u32 bufferSize)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::WriteDataToHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle, [[maybe_unused]] const void* writeBuffer, [[maybe_unused]] AZ::u32 bytesToWrite)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicator::CreatePipesForProcess([[maybe_unused]] ProcessData* processData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void StdInOutProcessCommunicator::WaitForReadyOutputs([[maybe_unused]] OutputStatus& status) const
|
||||
{
|
||||
status.outputDeviceReady = false;
|
||||
status.errorsDeviceReady = false;
|
||||
status.shouldReadOutput = status.shouldReadErrors = false;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicatorForChildProcess::AttachToExistingPipes()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
|
||||
StartupInfo::~StartupInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StartupInfo::SetupHandlesForChildProcess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StartupInfo::CloseAllHandles()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchUnwatchedProcess([[maybe_unused]] const ProcessLaunchInfo& processLaunchInfo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchProcess([[maybe_unused]] const ProcessLaunchInfo& processLaunchInfo, [[maybe_unused]] ProcessData& processData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()
|
||||
{
|
||||
return new StdInOutProcessCommunicator();
|
||||
}
|
||||
|
||||
StdProcessCommunicatorForChildProcess* ProcessWatcher::CreateStdCommunicatorForChildProcess()
|
||||
{
|
||||
return new StdInOutProcessCommunicatorForChildProcess();
|
||||
}
|
||||
|
||||
ProcessWatcher* ProcessWatcher::LaunchProcess([[maybe_unused]] const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, [[maybe_unused]] ProcessCommunicationType communicationType)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ProcessWatcher::InitProcessData([[maybe_unused]] bool stdProcessData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ProcessWatcher::ProcessWatcher()
|
||||
: m_pCommunicator(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ProcessWatcher::~ProcessWatcher()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ProcessWatcher::IsProcessRunning([[maybe_unused]] AZ::u32* outExitCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::WaitForProcessToExit([[maybe_unused]] AZ::u32 waitTimeInSeconds, [[maybe_unused]] AZ::u32* outExitCode /*= nullptr*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProcessWatcher::TerminateProcess([[maybe_unused]] AZ::u32 exitCode)
|
||||
{
|
||||
|
||||
}
|
||||
} //namespace AzFramework
|
||||
-27
@@ -1,27 +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>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProjectManager
|
||||
{
|
||||
bool LaunchProjectManager()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // ProjectManager
|
||||
} // AzFramework
|
||||
|
||||
@@ -15,7 +15,6 @@ set(FILES
|
||||
AzFramework/API/ApplicationAPI_Platform.h
|
||||
AzFramework/API/ApplicationAPI_Android.h
|
||||
AzFramework/Application/Application_Android.cpp
|
||||
AzFramework/ProjectManager/ProjectManager_Android.cpp
|
||||
../Common/Unimplemented/AzFramework/Asset/AssetSystemComponentHelper_Unimplemented.cpp
|
||||
AzFramework/IO/LocalFileIO_Android.cpp
|
||||
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
|
||||
@@ -34,4 +33,7 @@ set(FILES
|
||||
AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Android.cpp
|
||||
AzFramework/Archive/ArchiveVars_Platform.h
|
||||
AzFramework/Archive/ArchiveVars_Android.h
|
||||
AzFramework/Process/ProcessCommon.h
|
||||
AzFramework/Process/ProcessWatcher_Android.cpp
|
||||
AzFramework/Process/ProcessCommunicator_Android.cpp
|
||||
)
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
#define PROCESSCOMMON_DEFAULT 1
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class CommunicatorHandleImpl
|
||||
{
|
||||
public:
|
||||
~CommunicatorHandleImpl() = default;
|
||||
|
||||
bool IsValid() const { return false; }
|
||||
bool IsBroken() const { return false; }
|
||||
int GetHandle() const { return -1; }
|
||||
|
||||
void Break() {}
|
||||
void Close() {}
|
||||
void SetHandle([[maybe_unused]] int handle) {}
|
||||
|
||||
};
|
||||
|
||||
struct StartupInfo
|
||||
{
|
||||
~StartupInfo();
|
||||
|
||||
void SetupHandlesForChildProcess();
|
||||
void CloseAllHandles();
|
||||
|
||||
int m_inputHandleForChild = -1;
|
||||
int m_outputHandleForChild = -1;
|
||||
int m_errorHandleForChild = -1;
|
||||
};
|
||||
|
||||
struct ProcessData
|
||||
{
|
||||
StartupInfo m_startupInfo;
|
||||
int m_childProcessId = 0;
|
||||
bool m_childProcessIsDone = false;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 "ProcessCommon_Default.h"
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
AZ::u32 StdInOutCommunication::PeekHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::ReadDataFromHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle, [[maybe_unused]] void* readBuffer, [[maybe_unused]] AZ::u32 bufferSize)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::WriteDataToHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle, [[maybe_unused]] const void* writeBuffer, [[maybe_unused]] AZ::u32 bytesToWrite)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicator::CreatePipesForProcess([[maybe_unused]] ProcessData* processData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void StdInOutProcessCommunicator::WaitForReadyOutputs([[maybe_unused]] OutputStatus& status) const
|
||||
{
|
||||
status.outputDeviceReady = false;
|
||||
status.errorsDeviceReady = false;
|
||||
status.shouldReadOutput = status.shouldReadErrors = false;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicatorForChildProcess::AttachToExistingPipes()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
|
||||
StartupInfo::~StartupInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StartupInfo::SetupHandlesForChildProcess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StartupInfo::CloseAllHandles()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchUnwatchedProcess([[maybe_unused]] const ProcessLaunchInfo& processLaunchInfo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchProcess([[maybe_unused]] const ProcessLaunchInfo& processLaunchInfo, [[maybe_unused]] ProcessData& processData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()
|
||||
{
|
||||
return new StdInOutProcessCommunicator();
|
||||
}
|
||||
|
||||
StdProcessCommunicatorForChildProcess* ProcessWatcher::CreateStdCommunicatorForChildProcess()
|
||||
{
|
||||
return new StdInOutProcessCommunicatorForChildProcess();
|
||||
}
|
||||
|
||||
ProcessWatcher* ProcessWatcher::LaunchProcess([[maybe_unused]] const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, [[maybe_unused]] ProcessCommunicationType communicationType)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ProcessWatcher::InitProcessData([[maybe_unused]] bool stdProcessData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ProcessWatcher::ProcessWatcher()
|
||||
: m_pCommunicator(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ProcessWatcher::~ProcessWatcher()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ProcessWatcher::IsProcessRunning([[maybe_unused]] AZ::u32* outExitCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::WaitForProcessToExit([[maybe_unused]] AZ::u32 waitTimeInSeconds, [[maybe_unused]] AZ::u32* outExitCode /*= nullptr*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProcessWatcher::TerminateProcess([[maybe_unused]] AZ::u32 exitCode)
|
||||
{
|
||||
|
||||
}
|
||||
} //namespace AzFramework
|
||||
-28
@@ -1,28 +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>
|
||||
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProjectManager
|
||||
{
|
||||
bool LaunchProjectManager()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // ProjectManager
|
||||
} // AzFramework
|
||||
|
||||
+15
-14
@@ -25,8 +25,9 @@ namespace AzFramework::AssetSystem::Platform
|
||||
{
|
||||
void AllowAssetProcessorToForeground()
|
||||
{}
|
||||
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)
|
||||
{
|
||||
pid_t firstChildPid = fork();
|
||||
if (firstChildPid == 0)
|
||||
@@ -55,22 +56,22 @@ namespace AzFramework::AssetSystem::Platform
|
||||
};
|
||||
int optionalArgPos = 3;
|
||||
|
||||
// Add the app-root to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString appRootArg;
|
||||
if (!appRoot.empty())
|
||||
// Add the engine path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString engineRootArg;
|
||||
if (!engineRoot.empty())
|
||||
{
|
||||
appRootArg = AZ::IO::FixedMaxPathString::format(R"(--app-root="%.*s")",
|
||||
aznumeric_cast<int>(appRoot.size()), appRoot.data());
|
||||
args[optionalArgPos++] = appRootArg.data();
|
||||
engineRootArg = AZ::IO::FixedMaxPathString::format(R"(--engine-path="%.*s")",
|
||||
aznumeric_cast<int>(engineRoot.size()), engineRoot.data());
|
||||
args[optionalArgPos++] = engineRootArg.data();
|
||||
}
|
||||
|
||||
// Add the active game project to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString projectArg;
|
||||
if (!gameProjectName.empty())
|
||||
// Add the active project path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString projectPathArg;
|
||||
if (!projectPath.empty())
|
||||
{
|
||||
projectArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/sys_game_folder=%.*s")",
|
||||
aznumeric_cast<int>(gameProjectName.size()), gameProjectName.data());
|
||||
args[optionalArgPos++] = projectArg.data();
|
||||
projectPathArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%.*s")",
|
||||
aznumeric_cast<int>(projectPath.size()), projectPath.data());
|
||||
args[optionalArgPos++] = projectPathArg.data();
|
||||
}
|
||||
|
||||
AZStd::apply(execl, args);
|
||||
|
||||
@@ -16,3 +16,6 @@
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 1
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL "python.sh"
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PROCESSLAUNCH_DEFAULT 0
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class CommunicatorHandleImpl
|
||||
{
|
||||
public:
|
||||
~CommunicatorHandleImpl() = default;
|
||||
|
||||
bool IsValid() const;
|
||||
bool IsBroken() const;
|
||||
int GetHandle() const;
|
||||
|
||||
void Break();
|
||||
void Close();
|
||||
void SetHandle(int handle);
|
||||
|
||||
protected:
|
||||
int m_handle = -1;
|
||||
bool m_broken = false;
|
||||
};
|
||||
|
||||
struct StartupInfo
|
||||
{
|
||||
~StartupInfo();
|
||||
|
||||
void SetupHandlesForChildProcess();
|
||||
void CloseAllHandles();
|
||||
|
||||
int m_inputHandleForChild = -1;
|
||||
int m_outputHandleForChild = -1;
|
||||
int m_errorHandleForChild = -1;
|
||||
};
|
||||
|
||||
struct ProcessData
|
||||
{
|
||||
StartupInfo m_startupInfo;
|
||||
int m_childProcessId = 0;
|
||||
bool m_childProcessIsDone = false;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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 <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
bool CommunicatorHandleImpl::IsValid() const
|
||||
{
|
||||
return fcntl(m_handle, F_GETFD) != -1;
|
||||
}
|
||||
|
||||
bool CommunicatorHandleImpl::IsBroken() const
|
||||
{
|
||||
return m_broken;
|
||||
}
|
||||
|
||||
int CommunicatorHandleImpl::GetHandle() const
|
||||
{
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::Break()
|
||||
{
|
||||
m_broken = true;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::Close()
|
||||
{
|
||||
close(m_handle);
|
||||
m_handle = -1;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::SetHandle(int handle)
|
||||
{
|
||||
m_handle = handle;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::PeekHandle(StdProcessCommunicatorHandle& handle)
|
||||
{
|
||||
if (handle->IsBroken() || (!handle->IsValid() && (errno == EBADF)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytesAvailable = 0;
|
||||
const int result = ioctl(handle->GetHandle(), FIONREAD, &bytesAvailable);
|
||||
if ((result == -1) && (errno == EBADF))
|
||||
{
|
||||
// Child process released pipe
|
||||
handle->Break();
|
||||
}
|
||||
|
||||
return bytesAvailable;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::ReadDataFromHandle(StdProcessCommunicatorHandle& handle, void* readBuffer, AZ::u32 bufferSize)
|
||||
{
|
||||
if (handle->IsBroken() || (!handle->IsValid() && (errno == EBADF)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Block if buffersize == 0
|
||||
if (bufferSize == 0)
|
||||
{
|
||||
fd_set set;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(handle->GetHandle(), &set);
|
||||
|
||||
int numReady = select(handle->GetHandle() + 1, &set, NULL, NULL, NULL);
|
||||
|
||||
// if numReady == -1 and errno == EINTR then the child process died unexpectedly and
|
||||
// the handle was closed. Not something to assert about in regards to trying to read
|
||||
// data from the child as there is not anything useful we can say or do in that case.
|
||||
// Normal code/data flow will work and we as the parent will know that the child is
|
||||
// dead and return any error codes the child may have written to the error stream.
|
||||
AZ_Assert(numReady != -1 || errno == EINTR, "Could not determine if any data is available for reading due to an error. Errno: %d", errno);
|
||||
|
||||
const bool wasSet = FD_ISSET(handle->GetHandle(), &set);
|
||||
AZ_Assert(wasSet, "handle was not set when we selected it for read");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t bytesRead = 0;
|
||||
bytesRead = read(handle->GetHandle(), readBuffer, bufferSize);
|
||||
if (bytesRead < 0)
|
||||
{
|
||||
AZ_Assert(errno != EIO, "ReadFile performed unexpected async io");
|
||||
if (errno == EBADF || errno == EINVAL)
|
||||
{
|
||||
// Child process exited, we may have read something, so return amount
|
||||
handle->Break();
|
||||
return bytesRead;
|
||||
}
|
||||
AZ_Assert(false, "Unexpected error from ReadFile %d", errno);
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
//EOF
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
handle->Break();
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::WriteDataToHandle(StdProcessCommunicatorHandle& handle, const void* writeBuffer, AZ::u32 bytesToWrite)
|
||||
{
|
||||
AZ_Assert(writeBuffer, "Write buffer is null");
|
||||
|
||||
if (!writeBuffer || handle->IsBroken() || (!handle->IsValid() && (errno == EBADF)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ssize_t bytesWritten = write(handle->GetHandle(), writeBuffer, bytesToWrite);
|
||||
if (bytesWritten < 0)
|
||||
{
|
||||
AZ_Assert(errno != EIO, "Parent performed unexpected async io when trying to write to child process.");
|
||||
if (errno == EPIPE)
|
||||
{
|
||||
// Child process exited, may have written something, so return amount
|
||||
handle->Break();
|
||||
return bytesWritten;
|
||||
}
|
||||
AZ_Assert(false, "Unexpected error trying to write to child process. errno = %d", errno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicator::CreatePipesForProcess(ProcessData* processData)
|
||||
{
|
||||
int pipeFileDescriptors[2] = { 0 };
|
||||
|
||||
// Create a pipe to monitor process std in (output from us)
|
||||
int result = pipe(pipeFileDescriptors);
|
||||
AZ_Assert(result != -1, "Failed to create pipe for std in pipe: errno = %d", errno);
|
||||
if (result == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
processData->m_startupInfo.m_inputHandleForChild = pipeFileDescriptors[0];
|
||||
m_stdInWrite->SetHandle(pipeFileDescriptors[1]);
|
||||
|
||||
// Create a pipe to monitor process std out (input to us)
|
||||
result = pipe(pipeFileDescriptors);
|
||||
AZ_Assert(result != -1, "Failed to create pipe for std out pipe: errno = %d", errno);
|
||||
if (result == -1)
|
||||
{
|
||||
processData->m_startupInfo.CloseAllHandles();
|
||||
CloseAllHandles();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stdOutRead->SetHandle(pipeFileDescriptors[0]);
|
||||
processData->m_startupInfo.m_outputHandleForChild = pipeFileDescriptors[1];
|
||||
|
||||
// Create a pipe to monitor process std error (input to us)
|
||||
result = pipe(pipeFileDescriptors);
|
||||
AZ_Assert(result != -1, "Failed to create pipe for std err pipe: errno = %d", errno);
|
||||
if (result == -1)
|
||||
{
|
||||
processData->m_startupInfo.CloseAllHandles();
|
||||
CloseAllHandles();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stdErrRead->SetHandle(pipeFileDescriptors[0]);
|
||||
processData->m_startupInfo.m_errorHandleForChild = pipeFileDescriptors[1];
|
||||
|
||||
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)
|
||||
{
|
||||
fd_set readSet;
|
||||
int maxHandle = 0;
|
||||
|
||||
FD_ZERO(&readSet);
|
||||
|
||||
if (status.outputDeviceReady)
|
||||
{
|
||||
int currentHandle = m_stdOutRead->GetHandle();
|
||||
FD_SET(currentHandle, &readSet);
|
||||
maxHandle = currentHandle > maxHandle ? currentHandle : maxHandle;
|
||||
}
|
||||
|
||||
if (status.errorsDeviceReady)
|
||||
{
|
||||
int currentHandle = m_stdErrRead->GetHandle();
|
||||
FD_SET(currentHandle, &readSet);
|
||||
maxHandle = currentHandle > maxHandle ? currentHandle : maxHandle;
|
||||
}
|
||||
|
||||
if (select(maxHandle + 1, &readSet, nullptr, nullptr, nullptr) != -1)
|
||||
{
|
||||
status.shouldReadOutput = (status.outputDeviceReady && FD_ISSET(m_stdOutRead->GetHandle(), &readSet));
|
||||
status.shouldReadErrors = (status.errorsDeviceReady && FD_ISSET(m_stdErrRead->GetHandle(), &readSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicatorForChildProcess::AttachToExistingPipes()
|
||||
{
|
||||
m_stdInRead->SetHandle(STDIN_FILENO);
|
||||
AZ_Assert(m_stdInRead->IsValid(), "In read handle is invalid");
|
||||
|
||||
m_stdOutWrite->SetHandle(STDOUT_FILENO);
|
||||
AZ_Assert(m_stdOutWrite->IsValid(), "Output write handle is invalid");
|
||||
|
||||
m_stdErrWrite->SetHandle(STDERR_FILENO);
|
||||
AZ_Assert(m_stdErrWrite->IsValid(), "Error write handle is invalid");
|
||||
|
||||
m_initialized = m_stdInRead->IsValid() && m_stdOutWrite->IsValid() && m_stdErrWrite->IsValid();
|
||||
return m_initialized;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* 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/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/resource.h> // for iopolicy
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProcessLauncher
|
||||
{
|
||||
/*! Checks to see if the child process specified by the id is done or not
|
||||
*
|
||||
* \param childProcessId - Id of the child process to check
|
||||
* \param outExitCode - any exit code the child process returned if it is not running
|
||||
* \return True if the process is still running, otherwise false
|
||||
*/
|
||||
bool IsChildProcessDone(int childProcessId, AZ::u32* outExitCode = nullptr)
|
||||
{
|
||||
int childState;
|
||||
const pid_t rc_pid = waitpid(static_cast<pid_t>(childProcessId), &childState, WNOHANG);
|
||||
if (rc_pid > 0)
|
||||
{
|
||||
if (WIFEXITED(childState)) // exited
|
||||
{
|
||||
const int exitStatus = WEXITSTATUS(childState);
|
||||
if (exitStatus != 0)
|
||||
{
|
||||
AZ_TracePrintf("ProcessWatcher", "Child process id %d terminated prematurely (exit status %d)\n", childProcessId, exitStatus);
|
||||
}
|
||||
if (outExitCode)
|
||||
{
|
||||
*outExitCode = exitStatus;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(WIFSIGNALED(childState)) // signaled
|
||||
{
|
||||
const int signalStatus = WTERMSIG(childState);
|
||||
AZ_TracePrintf("ProcessWatcher", "Child process id %d terminated prematurely (signal %d)\n", childProcessId, signalStatus);
|
||||
if (outExitCode)
|
||||
{
|
||||
*outExitCode = signalStatus;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false; // still running
|
||||
}
|
||||
}
|
||||
else if (rc_pid < 0)
|
||||
{
|
||||
if (errno == ECHILD)
|
||||
{
|
||||
AZ_TracePrintf("ProcessWatcher", "Child process id %d does not exist\n", childProcessId);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Bad argument passed to waitpid\n");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool IsIdChildProcess(pid_t processId)
|
||||
{
|
||||
return processId == 0;
|
||||
}
|
||||
|
||||
/*! Executes a command in the child process after the fork operation has been executed.
|
||||
* This function will never return. If the execvp command fails this will call _exit with
|
||||
* the errno value as the return value since continuing execution after a execvp command
|
||||
* is invalid (it will be running the parent's code and in its address space and will
|
||||
* cause many issues).
|
||||
*
|
||||
* \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer.
|
||||
* \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
|
||||
* \param processLaunchInfo - struct containing information about luanching the command
|
||||
* \param startupInfo - struct containing information needed to startup the command
|
||||
*/
|
||||
void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo)
|
||||
{
|
||||
if (!processLaunchInfo.m_workingDirectory.empty())
|
||||
{
|
||||
int res = chdir(processLaunchInfo.m_workingDirectory.c_str());
|
||||
if (res != 0)
|
||||
{
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str());
|
||||
// We *have* to _exit as we are the child process and simply
|
||||
// returning at this point would mean we would start running
|
||||
// the code from our parent process and that will just wreck
|
||||
// havoc.
|
||||
_exit(errno);
|
||||
}
|
||||
}
|
||||
|
||||
switch (processLaunchInfo.m_processPriority)
|
||||
{
|
||||
case PROCESSPRIORITY_BELOWNORMAL:
|
||||
nice(1);
|
||||
// also reduce disk impact:
|
||||
// setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_UTILITY);
|
||||
break;
|
||||
case PROCESSPRIORITY_IDLE:
|
||||
nice(20);
|
||||
// also reduce disk impact:
|
||||
// setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE);
|
||||
break;
|
||||
}
|
||||
|
||||
startupInfo.SetupHandlesForChildProcess();
|
||||
|
||||
execve(commandAndArgs[0], commandAndArgs, environmentVariables);
|
||||
|
||||
// If we get here then execve failed to run the requested program and
|
||||
// we have an error. In this case we need to exit the child process
|
||||
// to stop it from continuing to run as a clone of the parent
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno));
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
|
||||
_exit(errno);
|
||||
}
|
||||
}
|
||||
|
||||
StartupInfo::~StartupInfo()
|
||||
{
|
||||
CloseAllHandles();
|
||||
}
|
||||
|
||||
void StartupInfo::SetupHandlesForChildProcess()
|
||||
{
|
||||
if (m_inputHandleForChild != STDIN_FILENO)
|
||||
{
|
||||
dup2(m_inputHandleForChild, STDIN_FILENO);
|
||||
close(m_inputHandleForChild);
|
||||
}
|
||||
|
||||
if (m_outputHandleForChild != STDOUT_FILENO)
|
||||
{
|
||||
dup2(m_outputHandleForChild, STDOUT_FILENO);
|
||||
close(m_outputHandleForChild);
|
||||
}
|
||||
|
||||
if (m_errorHandleForChild != STDERR_FILENO)
|
||||
{
|
||||
dup2(m_errorHandleForChild, STDERR_FILENO);
|
||||
close(m_errorHandleForChild);
|
||||
}
|
||||
}
|
||||
|
||||
void StartupInfo::CloseAllHandles()
|
||||
{
|
||||
if (m_inputHandleForChild != -1)
|
||||
{
|
||||
close(m_inputHandleForChild);
|
||||
m_inputHandleForChild = -1;
|
||||
}
|
||||
|
||||
if (m_outputHandleForChild != -1)
|
||||
{
|
||||
close(m_outputHandleForChild);
|
||||
m_outputHandleForChild = -1;
|
||||
}
|
||||
|
||||
if (m_errorHandleForChild != -1)
|
||||
{
|
||||
close(m_errorHandleForChild);
|
||||
m_errorHandleForChild = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchUnwatchedProcess(const ProcessLaunchInfo& processLaunchInfo)
|
||||
{
|
||||
ProcessData processData = ProcessData();
|
||||
return LaunchProcess(processLaunchInfo, processData);
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchProcess(const ProcessLaunchInfo& processLaunchInfo, ProcessData& processData)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// note that the convention here is that it uses windows-shell style escaping of combined args with spaces in it
|
||||
// (so surrounding with quotes like param="hello world")
|
||||
// this is so that the callers (which could be numerous) do not have to worry about this and sprinkle ifdefs
|
||||
// all over their code.
|
||||
// We'll convert this to UNIX style command line parameters by counting and eliminating quotes:
|
||||
|
||||
AZStd::vector<AZStd::string> commandTokens;
|
||||
|
||||
AZStd::string outputString;
|
||||
bool inQuotes = false;
|
||||
for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos)
|
||||
{
|
||||
char currentChar = processLaunchInfo.m_commandlineParameters[pos];
|
||||
if (currentChar == '"')
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
else if ((currentChar == ' ') && (!inQuotes))
|
||||
{
|
||||
// its a space outside of quotes, so it ends the current parameter
|
||||
commandTokens.push_back(outputString);
|
||||
outputString.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Its a normal character, or its a space inside quotes
|
||||
outputString.push_back(currentChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (!outputString.empty())
|
||||
{
|
||||
commandTokens.push_back(outputString);
|
||||
outputString.clear();
|
||||
}
|
||||
|
||||
if (!processLaunchInfo.m_processExecutableString.empty())
|
||||
{
|
||||
commandTokens.insert(commandTokens.begin(), processLaunchInfo.m_processExecutableString);
|
||||
}
|
||||
|
||||
AZStd::string commandNameWithPath = processLaunchInfo.m_workingDirectory + " " + commandTokens[0];
|
||||
if (AZ::IO::SystemFile::Exists(commandNameWithPath.c_str()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Because of the way execve is defined we need to copy the strings from
|
||||
// AZ::string (using c_str() returns a const char*) into a non-const char*
|
||||
|
||||
// Need to add one more as exec requires the array's last element to be a null pointer
|
||||
char** commandAndArgs = new char*[commandTokens.size() + 1];
|
||||
for (int i = 0; i < commandTokens.size(); ++i)
|
||||
{
|
||||
const AZStd::string& token = commandTokens[i];
|
||||
commandAndArgs[i] = new char[token.size() + 1];
|
||||
commandAndArgs[i][0] = '\0';
|
||||
azstrcat(commandAndArgs[i], token.size(), token.c_str());
|
||||
}
|
||||
commandAndArgs[commandTokens.size()] = nullptr;
|
||||
|
||||
char** environmentVariables = nullptr;
|
||||
int numEnvironmentVars = 0;
|
||||
if (processLaunchInfo.m_environmentVariables)
|
||||
{
|
||||
const int numEnvironmentVars = processLaunchInfo.m_environmentVariables->size();
|
||||
// Adding one more as exec expects the array to have a nullptr as the last element
|
||||
environmentVariables = new char*[numEnvironmentVars + 1];
|
||||
for (int i = 0; i < numEnvironmentVars; i++)
|
||||
{
|
||||
const AZStd::string& envVarString = processLaunchInfo.m_environmentVariables->at(i);
|
||||
environmentVariables[i] = new char[envVarString.size() + 1];
|
||||
environmentVariables[i][0] = '\0';
|
||||
azstrcat(environmentVariables[i], envVarString.size(), envVarString.c_str());
|
||||
}
|
||||
environmentVariables[numEnvironmentVars] = NULL;
|
||||
}
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (IsIdChildProcess(child_pid))
|
||||
{
|
||||
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo);
|
||||
}
|
||||
processData.m_childProcessId = child_pid;
|
||||
|
||||
// Close these handles as they are only to be used by the child process
|
||||
processData.m_startupInfo.CloseAllHandles();
|
||||
|
||||
if (processLaunchInfo.m_environmentVariables)
|
||||
{
|
||||
for (int i = 0; i < numEnvironmentVars; i++)
|
||||
{
|
||||
delete [] environmentVariables[i];
|
||||
}
|
||||
delete [] environmentVariables;
|
||||
}
|
||||
|
||||
for (int i = 0; i < commandTokens.size(); i++)
|
||||
{
|
||||
delete [] commandAndArgs[i];
|
||||
}
|
||||
delete [] commandAndArgs;
|
||||
|
||||
// If an error occurs, exit the application.
|
||||
return child_pid >= 0;
|
||||
}
|
||||
|
||||
StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()
|
||||
{
|
||||
return new StdInOutProcessCommunicator();
|
||||
}
|
||||
|
||||
StdProcessCommunicatorForChildProcess* ProcessWatcher::CreateStdCommunicatorForChildProcess()
|
||||
{
|
||||
return new StdInOutProcessCommunicatorForChildProcess();
|
||||
}
|
||||
|
||||
ProcessWatcher* ProcessWatcher::LaunchProcess(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType)
|
||||
{
|
||||
ProcessWatcher* pWatcher = new ProcessWatcher {};
|
||||
if (!pWatcher->SpawnProcess(processLaunchInfo, communicationType))
|
||||
{
|
||||
delete pWatcher;
|
||||
return nullptr;
|
||||
}
|
||||
return pWatcher;
|
||||
}
|
||||
|
||||
void ProcessWatcher::InitProcessData(bool stdProcessData)
|
||||
{
|
||||
/** Nothing to do for this on macOS */
|
||||
}
|
||||
|
||||
ProcessWatcher::ProcessWatcher()
|
||||
: m_pCommunicator(nullptr)
|
||||
{
|
||||
m_pWatcherData = AZStd::make_unique<ProcessData>();
|
||||
}
|
||||
|
||||
ProcessWatcher::~ProcessWatcher()
|
||||
{
|
||||
if (IsProcessRunning())
|
||||
{
|
||||
TerminateProcess(0);
|
||||
}
|
||||
|
||||
delete m_pCommunicator;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::IsProcessRunning(AZ::u32* outExitCode)
|
||||
{
|
||||
AZ_Assert(m_pWatcherData, "No watcher data");
|
||||
if (!m_pWatcherData || m_pWatcherData->m_childProcessIsDone)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pWatcherData->m_childProcessIsDone = ProcessLauncher::IsChildProcessDone(m_pWatcherData->m_childProcessId, outExitCode);
|
||||
return !m_pWatcherData->m_childProcessIsDone;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::WaitForProcessToExit(AZ::u32 waitTimeInSeconds, AZ::u32* outExitCode /*= nullptr*/)
|
||||
{
|
||||
if (ProcessLauncher::IsChildProcessDone(m_pWatcherData->m_childProcessId, outExitCode))
|
||||
{
|
||||
// Already exited
|
||||
m_pWatcherData->m_childProcessIsDone = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isProcessDone = false;
|
||||
time_t startTime = time(0);
|
||||
time_t currentTime = startTime;
|
||||
AZ_Assert(currentTime != -1, "time(0) returned an invalid time");
|
||||
while (((currentTime - startTime) < waitTimeInSeconds) && !isProcessDone)
|
||||
{
|
||||
usleep(100);
|
||||
if (ProcessLauncher::IsChildProcessDone(m_pWatcherData->m_childProcessId, outExitCode))
|
||||
{
|
||||
isProcessDone = true;
|
||||
m_pWatcherData->m_childProcessIsDone = true;
|
||||
break;
|
||||
}
|
||||
currentTime = time(0);
|
||||
}
|
||||
//returns false if process is still running after time
|
||||
return isProcessDone;
|
||||
}
|
||||
|
||||
void ProcessWatcher::TerminateProcess(AZ::u32 exitCode)
|
||||
{
|
||||
AZ_Assert(m_pWatcherData, "No watcher data");
|
||||
if (!m_pWatcherData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsProcessRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
kill(m_pWatcherData->m_childProcessId, SIGKILL);
|
||||
}
|
||||
} //namespace AzFramework
|
||||
-27
@@ -1,27 +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>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProjectManager
|
||||
{
|
||||
bool LaunchProjectManager()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // ProjectManager
|
||||
} // AzFramework
|
||||
|
||||
@@ -16,7 +16,9 @@ set(FILES
|
||||
AzFramework/API/ApplicationAPI_Linux.h
|
||||
AzFramework/Application/Application_Linux.cpp
|
||||
AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp
|
||||
AzFramework/ProjectManager/ProjectManager_Linux.cpp
|
||||
AzFramework/Process/ProcessWatcher_Linux.cpp
|
||||
AzFramework/Process/ProcessCommon.h
|
||||
AzFramework/Process/ProcessCommunicator_Linux.cpp
|
||||
../Common/UnixLike/AzFramework/IO/LocalFileIO_UnixLike.cpp
|
||||
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
|
||||
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
|
||||
|
||||
+10
-10
@@ -20,8 +20,8 @@ namespace AzFramework::AssetSystem::Platform
|
||||
{
|
||||
void AllowAssetProcessorToForeground()
|
||||
{}
|
||||
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 };
|
||||
// In Mac the Editor and game is within a bundle, so the path to the sibling app
|
||||
@@ -30,19 +30,19 @@ namespace AzFramework::AssetSystem::Platform
|
||||
assetProcessorPath = assetProcessorPath.LexicallyNormal();
|
||||
|
||||
auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"(open -g "%s" --args --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;
|
||||
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 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 1
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL "python.sh"
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 0
|
||||
#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
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class CommunicatorHandleImpl
|
||||
{
|
||||
public:
|
||||
~CommunicatorHandleImpl() = default;
|
||||
|
||||
bool IsValid() const;
|
||||
bool IsBroken() const;
|
||||
int GetHandle() const;
|
||||
|
||||
void Break();
|
||||
void Close();
|
||||
void SetHandle(int handle);
|
||||
|
||||
protected:
|
||||
int m_handle = -1;
|
||||
bool m_broken = false;
|
||||
};
|
||||
|
||||
struct StartupInfo
|
||||
{
|
||||
~StartupInfo();
|
||||
|
||||
void SetupHandlesForChildProcess();
|
||||
void CloseAllHandles();
|
||||
|
||||
int m_inputHandleForChild = -1;
|
||||
int m_outputHandleForChild = -1;
|
||||
int m_errorHandleForChild = -1;
|
||||
};
|
||||
|
||||
struct ProcessData
|
||||
{
|
||||
StartupInfo m_startupInfo;
|
||||
int m_childProcessId = 0;
|
||||
bool m_childProcessIsDone = false;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
bool CommunicatorHandleImpl::IsValid() const
|
||||
{
|
||||
return fcntl(m_handle, F_GETFD) != -1;
|
||||
}
|
||||
|
||||
bool CommunicatorHandleImpl::IsBroken() const
|
||||
{
|
||||
return m_broken;
|
||||
}
|
||||
|
||||
int CommunicatorHandleImpl::GetHandle() const
|
||||
{
|
||||
return m_handle;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::Break()
|
||||
{
|
||||
m_broken = true;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::Close()
|
||||
{
|
||||
close(m_handle);
|
||||
m_handle = -1;
|
||||
}
|
||||
|
||||
void CommunicatorHandleImpl::SetHandle(int handle)
|
||||
{
|
||||
m_handle = handle;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::PeekHandle(StdProcessCommunicatorHandle& handle)
|
||||
{
|
||||
if (handle->IsBroken() || (!handle->IsValid() && (errno == EBADF)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytesAvailable = 0;
|
||||
const int result = ioctl(handle->GetHandle(), FIONREAD, &bytesAvailable);
|
||||
if ((result == -1) && (errno == EBADF))
|
||||
{
|
||||
// Child process released pipe
|
||||
handle->Break();
|
||||
}
|
||||
|
||||
return bytesAvailable;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::ReadDataFromHandle(StdProcessCommunicatorHandle& handle, void* readBuffer, AZ::u32 bufferSize)
|
||||
{
|
||||
if (handle->IsBroken() || (!handle->IsValid() && (errno == EBADF)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Block if buffersize == 0
|
||||
if (bufferSize == 0)
|
||||
{
|
||||
fd_set set;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(handle->GetHandle(), &set);
|
||||
|
||||
int numReady = select(handle->GetHandle() + 1, &set, NULL, NULL, NULL);
|
||||
|
||||
// if numReady == -1 and errno == EINTR then the child process died unexpectedly and
|
||||
// the handle was closed. Not something to assert about in regards to trying to read
|
||||
// data from the child as there is not anything useful we can say or do in that case.
|
||||
// Normal code/data flow will work and we as the parent will know that the child is
|
||||
// dead and return any error codes the child may have written to the error stream.
|
||||
AZ_Assert(numReady != -1 || errno == EINTR, "Could not determine if any data is available for reading due to an error. Errno: %d", errno);
|
||||
|
||||
const bool wasSet = FD_ISSET(handle->GetHandle(), &set);
|
||||
AZ_Assert(wasSet, "handle was not set when we selected it for read");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t bytesRead = 0;
|
||||
bytesRead = read(handle->GetHandle(), readBuffer, bufferSize);
|
||||
if (bytesRead < 0)
|
||||
{
|
||||
AZ_Assert(errno != EIO, "ReadFile performed unexpected async io");
|
||||
if (errno == EBADF || errno == EINVAL)
|
||||
{
|
||||
// Child process exited, we may have read something, so return amount
|
||||
handle->Break();
|
||||
return bytesRead;
|
||||
}
|
||||
AZ_Assert(false, "Unexpected error from ReadFile %d", errno);
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
//EOF
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
handle->Break();
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::WriteDataToHandle(StdProcessCommunicatorHandle& handle, const void* writeBuffer, AZ::u32 bytesToWrite)
|
||||
{
|
||||
AZ_Assert(writeBuffer, "Write buffer is null");
|
||||
|
||||
if (!writeBuffer || handle->IsBroken() || (!handle->IsValid() && (errno == EBADF)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const ssize_t bytesWritten = write(handle->GetHandle(), writeBuffer, bytesToWrite);
|
||||
if (bytesWritten < 0)
|
||||
{
|
||||
AZ_Assert(errno != EIO, "Parent performed unexpected async io when trying to write to child process.");
|
||||
if (errno == EPIPE)
|
||||
{
|
||||
// Child process exited, may have written something, so return amount
|
||||
handle->Break();
|
||||
return bytesWritten;
|
||||
}
|
||||
AZ_Assert(false, "Unexpected error trying to write to child process. errno = %d", errno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicator::CreatePipesForProcess(ProcessData* processData)
|
||||
{
|
||||
int pipeFileDescriptors[2] = { 0 };
|
||||
|
||||
// Create a pipe to monitor process std in (output from us)
|
||||
int result = pipe(pipeFileDescriptors);
|
||||
AZ_Assert(result != -1, "Failed to create pipe for std in pipe: errno = %d", errno);
|
||||
if (result == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
processData->m_startupInfo.m_inputHandleForChild = pipeFileDescriptors[0];
|
||||
m_stdInWrite->SetHandle(pipeFileDescriptors[1]);
|
||||
|
||||
// Create a pipe to monitor process std out (input to us)
|
||||
result = pipe(pipeFileDescriptors);
|
||||
AZ_Assert(result != -1, "Failed to create pipe for std out pipe: errno = %d", errno);
|
||||
if (result == -1)
|
||||
{
|
||||
processData->m_startupInfo.CloseAllHandles();
|
||||
CloseAllHandles();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stdOutRead->SetHandle(pipeFileDescriptors[0]);
|
||||
processData->m_startupInfo.m_outputHandleForChild = pipeFileDescriptors[1];
|
||||
|
||||
// Create a pipe to monitor process std error (input to us)
|
||||
result = pipe(pipeFileDescriptors);
|
||||
AZ_Assert(result != -1, "Failed to create pipe for std err pipe: errno = %d", errno);
|
||||
if (result == -1)
|
||||
{
|
||||
processData->m_startupInfo.CloseAllHandles();
|
||||
CloseAllHandles();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stdErrRead->SetHandle(pipeFileDescriptors[0]);
|
||||
processData->m_startupInfo.m_errorHandleForChild = pipeFileDescriptors[1];
|
||||
|
||||
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)
|
||||
{
|
||||
fd_set readSet;
|
||||
int maxHandle = 0;
|
||||
|
||||
FD_ZERO(&readSet);
|
||||
|
||||
if (status.outputDeviceReady)
|
||||
{
|
||||
int currentHandle = m_stdOutRead->GetHandle();
|
||||
FD_SET(currentHandle, &readSet);
|
||||
maxHandle = currentHandle > maxHandle ? currentHandle : maxHandle;
|
||||
}
|
||||
|
||||
if (status.errorsDeviceReady)
|
||||
{
|
||||
int currentHandle = m_stdErrRead->GetHandle();
|
||||
FD_SET(currentHandle, &readSet);
|
||||
maxHandle = currentHandle > maxHandle ? currentHandle : maxHandle;
|
||||
}
|
||||
|
||||
if (select(maxHandle + 1, &readSet, nullptr, nullptr, nullptr) != -1)
|
||||
{
|
||||
status.shouldReadOutput = (status.outputDeviceReady && FD_ISSET(m_stdOutRead->GetHandle(), &readSet));
|
||||
status.shouldReadErrors = (status.errorsDeviceReady && FD_ISSET(m_stdErrRead->GetHandle(), &readSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicatorForChildProcess::AttachToExistingPipes()
|
||||
{
|
||||
m_stdInRead->SetHandle(STDIN_FILENO);
|
||||
AZ_Assert(m_stdInRead->IsValid(), "In read handle is invalid");
|
||||
|
||||
m_stdOutWrite->SetHandle(STDOUT_FILENO);
|
||||
AZ_Assert(m_stdOutWrite->IsValid(), "Output write handle is invalid");
|
||||
|
||||
m_stdErrWrite->SetHandle(STDERR_FILENO);
|
||||
AZ_Assert(m_stdErrWrite->IsValid(), "Error write handle is invalid");
|
||||
|
||||
m_initialized = m_stdInRead->IsValid() && m_stdOutWrite->IsValid() && m_stdErrWrite->IsValid();
|
||||
return m_initialized;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
#endif // AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/resource.h> // for iopolicy
|
||||
#include <time.h>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace
|
||||
{
|
||||
/*! Checks to see if the child process specified by the id is done or not
|
||||
*
|
||||
* \param childProcessId - Id of the child process to check
|
||||
* \param outExitCode - any exit code the child process returned if it is not running
|
||||
* \return True if the process is still running, otherwise false
|
||||
*/
|
||||
bool IsChildProcessDone(int childProcessId, AZ::u32* outExitCode = nullptr)
|
||||
{
|
||||
// Check exit code
|
||||
int exitCode = 0;
|
||||
int result = waitpid(childProcessId, &exitCode, WNOHANG);
|
||||
|
||||
// result == 0 means child PID is still running, nothing to check
|
||||
if (result == -1)
|
||||
{
|
||||
AZ_TracePrintf("ProcessWatcher", "IsChildProcessDone could not determine child process status (waitpid errno %d). assuming process either failed to launch or terminated unexpectedly\n", errno);
|
||||
exitCode = 0;
|
||||
}
|
||||
else if (result == childProcessId)
|
||||
{
|
||||
// result == child PID indicates done
|
||||
int realExitCode = 0;
|
||||
if (WIFEXITED(exitCode))
|
||||
{
|
||||
realExitCode = WEXITSTATUS(exitCode);
|
||||
}
|
||||
else if (WIFSIGNALED(exitCode))
|
||||
{
|
||||
int termSig = WTERMSIG(exitCode);
|
||||
if (termSig != 0)
|
||||
{
|
||||
realExitCode = termSig;
|
||||
}
|
||||
|
||||
int coreDump = WCOREDUMP(exitCode);
|
||||
if (coreDump != 0)
|
||||
{
|
||||
realExitCode = coreDump;
|
||||
}
|
||||
}
|
||||
else if (WIFSTOPPED(exitCode))
|
||||
{
|
||||
int stopSig = WSTOPSIG(exitCode);
|
||||
realExitCode = stopSig;
|
||||
}
|
||||
exitCode = realExitCode;
|
||||
}
|
||||
|
||||
if (outExitCode)
|
||||
{
|
||||
*outExitCode = exitCode;
|
||||
}
|
||||
|
||||
return (result != 0);
|
||||
}
|
||||
|
||||
inline bool IsIdChildProcess(pid_t processId)
|
||||
{
|
||||
return processId == 0;
|
||||
}
|
||||
|
||||
/*! Executes a command in the child process after the fork operation has been executed.
|
||||
* This function will never return. If the execvp command fails this will call _exit with
|
||||
* the errno value as the return value since continuing execution after a execvp command
|
||||
* is invalid (it will be running the parent's code and in its address space and will
|
||||
* cause many issues).
|
||||
*
|
||||
* \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer.
|
||||
* \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
|
||||
* \param processLaunchInfo - struct containing information about luanching the command
|
||||
* \param startupInfo - struct containing information needed to startup the command
|
||||
*/
|
||||
void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo)
|
||||
{
|
||||
if (!processLaunchInfo.m_workingDirectory.empty())
|
||||
{
|
||||
int res = chdir(processLaunchInfo.m_workingDirectory.c_str());
|
||||
if (res != 0)
|
||||
{
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str());
|
||||
// We *have* to _exit as we are the child process and simply
|
||||
// returning at this point would mean we would start running
|
||||
// the code from our parent process and that will just wreck
|
||||
// havoc.
|
||||
_exit(errno);
|
||||
}
|
||||
}
|
||||
|
||||
switch (processLaunchInfo.m_processPriority)
|
||||
{
|
||||
case PROCESSPRIORITY_BELOWNORMAL:
|
||||
nice(1);
|
||||
// also reduce disk impact:
|
||||
setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_UTILITY);
|
||||
break;
|
||||
case PROCESSPRIORITY_IDLE:
|
||||
nice(20);
|
||||
// also reduce disk impact:
|
||||
setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE);
|
||||
break;
|
||||
}
|
||||
|
||||
startupInfo.SetupHandlesForChildProcess();
|
||||
|
||||
execve(commandAndArgs[0], commandAndArgs, environmentVariables);
|
||||
|
||||
// If we get here then execve failed to run the requested program and
|
||||
// we have an error. In this case we need to exit the child process
|
||||
// to stop it from continuing to run as a clone of the parent
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno));
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
|
||||
_exit(errno);
|
||||
}
|
||||
}
|
||||
|
||||
StartupInfo::~StartupInfo()
|
||||
{
|
||||
CloseAllHandles();
|
||||
}
|
||||
|
||||
void StartupInfo::SetupHandlesForChildProcess()
|
||||
{
|
||||
if (m_inputHandleForChild != STDIN_FILENO)
|
||||
{
|
||||
dup2(m_inputHandleForChild, STDIN_FILENO);
|
||||
close(m_inputHandleForChild);
|
||||
}
|
||||
|
||||
if (m_outputHandleForChild != STDOUT_FILENO)
|
||||
{
|
||||
dup2(m_outputHandleForChild, STDOUT_FILENO);
|
||||
close(m_outputHandleForChild);
|
||||
}
|
||||
|
||||
if (m_errorHandleForChild != STDERR_FILENO)
|
||||
{
|
||||
dup2(m_errorHandleForChild, STDERR_FILENO);
|
||||
close(m_errorHandleForChild);
|
||||
}
|
||||
}
|
||||
|
||||
void StartupInfo::CloseAllHandles()
|
||||
{
|
||||
if (m_inputHandleForChild != -1)
|
||||
{
|
||||
close(m_inputHandleForChild);
|
||||
m_inputHandleForChild = -1;
|
||||
}
|
||||
|
||||
if (m_outputHandleForChild != -1)
|
||||
{
|
||||
close(m_outputHandleForChild);
|
||||
m_outputHandleForChild = -1;
|
||||
}
|
||||
|
||||
if (m_errorHandleForChild != -1)
|
||||
{
|
||||
close(m_errorHandleForChild);
|
||||
m_errorHandleForChild = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchUnwatchedProcess(const ProcessLaunchInfo& processLaunchInfo)
|
||||
{
|
||||
ProcessData processData = ProcessData();
|
||||
return LaunchProcess(processLaunchInfo, processData);
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchProcess(const ProcessLaunchInfo& processLaunchInfo, ProcessData& processData)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// note that the convention here is that it uses windows-shell style escaping of combined args with spaces in it
|
||||
// (so surrounding with quotes like param="hello world")
|
||||
// this is so that the callers (which could be numerous) do not have to worry about this and sprinkle ifdefs
|
||||
// all over their code.
|
||||
// We'll convert this to UNIX style command line parameters by counting and eliminating quotes:
|
||||
|
||||
AZStd::vector<AZStd::string> commandTokens;
|
||||
|
||||
AZStd::string outputString;
|
||||
bool inQuotes = false;
|
||||
for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos)
|
||||
{
|
||||
char currentChar = processLaunchInfo.m_commandlineParameters[pos];
|
||||
if (currentChar == '"')
|
||||
{
|
||||
// Allow quote literals to go through as quotes which do NOT alter our "in quotes" bool below
|
||||
// This is to conform with our PC parameter strings which will sometimes include path parameters which
|
||||
// Can have spaces and commas and need to be output as paramname="\"Some pa,ram\"" in order to capture both correctly
|
||||
if (outputString.length() && outputString.back() == '\\')
|
||||
{
|
||||
outputString.back() = currentChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
}
|
||||
}
|
||||
else if ((currentChar == ' ') && (!inQuotes))
|
||||
{
|
||||
// its a space outside of quotes, so it ends the current parameter
|
||||
commandTokens.push_back(outputString);
|
||||
outputString.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Its a normal character, or its a space inside quotes
|
||||
outputString.push_back(currentChar);
|
||||
}
|
||||
}
|
||||
|
||||
if (!outputString.empty())
|
||||
{
|
||||
commandTokens.push_back(outputString);
|
||||
outputString.clear();
|
||||
}
|
||||
|
||||
if (!processLaunchInfo.m_processExecutableString.empty())
|
||||
{
|
||||
commandTokens.insert(commandTokens.begin(), processLaunchInfo.m_processExecutableString);
|
||||
}
|
||||
|
||||
AZStd::string commandNameWithPath = processLaunchInfo.m_workingDirectory + " " + commandTokens[0];
|
||||
if (AZ::IO::SystemFile::Exists(commandNameWithPath.c_str()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Because of the way execve is defined we need to copy the strings from
|
||||
// AZ::string (using c_str() returns a const char*) into a non-const char*
|
||||
|
||||
// Need to add one more as exec requires the array's last element to be a null pointer
|
||||
char** commandAndArgs = new char*[commandTokens.size() + 1];
|
||||
for (int i = 0; i < commandTokens.size(); ++i)
|
||||
{
|
||||
const AZStd::string& token = commandTokens[i];
|
||||
commandAndArgs[i] = new char[token.size() + 1];
|
||||
commandAndArgs[i][0] = '\0';
|
||||
azstrcat(commandAndArgs[i], token.size(), token.c_str());
|
||||
}
|
||||
commandAndArgs[commandTokens.size()] = nullptr;
|
||||
|
||||
char** environmentVariables = nullptr;
|
||||
int numEnvironmentVars = 0;
|
||||
if (processLaunchInfo.m_environmentVariables)
|
||||
{
|
||||
const int numEnvironmentVars = processLaunchInfo.m_environmentVariables->size();
|
||||
// Adding one more as exec expects the array to have a nullptr as the last element
|
||||
environmentVariables = new char*[numEnvironmentVars + 1];
|
||||
for (int i = 0; i < numEnvironmentVars; i++)
|
||||
{
|
||||
const AZStd::string& envVarString = processLaunchInfo.m_environmentVariables->at(i);
|
||||
environmentVariables[i] = new char[envVarString.size() + 1];
|
||||
environmentVariables[i][0] = '\0';
|
||||
azstrcat(environmentVariables[i], envVarString.size(), envVarString.c_str());
|
||||
}
|
||||
environmentVariables[numEnvironmentVars] = NULL;
|
||||
}
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (IsIdChildProcess(child_pid))
|
||||
{
|
||||
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo);
|
||||
}
|
||||
|
||||
processData.m_childProcessId = child_pid;
|
||||
|
||||
// Close these handles as they are only to be used by the child process
|
||||
processData.m_startupInfo.CloseAllHandles();
|
||||
|
||||
if (processLaunchInfo.m_environmentVariables)
|
||||
{
|
||||
for (int i = 0; i < numEnvironmentVars; i++)
|
||||
{
|
||||
delete [] environmentVariables[i];
|
||||
}
|
||||
delete [] environmentVariables;
|
||||
}
|
||||
|
||||
for (int i = 0; i < commandTokens.size(); i++)
|
||||
{
|
||||
delete [] commandAndArgs[i];
|
||||
}
|
||||
delete [] commandAndArgs;
|
||||
|
||||
// If an error occurs, exit the application.
|
||||
return child_pid >= 0;
|
||||
}
|
||||
|
||||
StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()
|
||||
{
|
||||
return new StdInOutProcessCommunicator();
|
||||
}
|
||||
|
||||
StdProcessCommunicatorForChildProcess* ProcessWatcher::CreateStdCommunicatorForChildProcess()
|
||||
{
|
||||
return new StdInOutProcessCommunicatorForChildProcess();
|
||||
}
|
||||
|
||||
ProcessWatcher* ProcessWatcher::LaunchProcess(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType)
|
||||
{
|
||||
ProcessWatcher* pWatcher = new ProcessWatcher {};
|
||||
if (!pWatcher->SpawnProcess(processLaunchInfo, communicationType))
|
||||
{
|
||||
delete pWatcher;
|
||||
return nullptr;
|
||||
}
|
||||
return pWatcher;
|
||||
}
|
||||
|
||||
void ProcessWatcher::InitProcessData(bool stdProcessData)
|
||||
{
|
||||
/** Nothing to do for this on macOS */
|
||||
}
|
||||
|
||||
ProcessWatcher::ProcessWatcher()
|
||||
: m_pCommunicator(nullptr)
|
||||
{
|
||||
m_pWatcherData = AZStd::make_unique<ProcessData>();
|
||||
}
|
||||
|
||||
ProcessWatcher::~ProcessWatcher()
|
||||
{
|
||||
if (IsProcessRunning())
|
||||
{
|
||||
TerminateProcess(0);
|
||||
}
|
||||
|
||||
delete m_pCommunicator;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::IsProcessRunning(AZ::u32* outExitCode)
|
||||
{
|
||||
AZ_Assert(m_pWatcherData, "No watcher data");
|
||||
if (!m_pWatcherData || m_pWatcherData->m_childProcessIsDone)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pWatcherData->m_childProcessIsDone = IsChildProcessDone(m_pWatcherData->m_childProcessId, outExitCode);
|
||||
return !m_pWatcherData->m_childProcessIsDone;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::WaitForProcessToExit(AZ::u32 waitTimeInSeconds, AZ::u32* outExitCode /*= nullptr*/)
|
||||
{
|
||||
AZ_UNUSED(outExitCode);
|
||||
|
||||
if (IsChildProcessDone(m_pWatcherData->m_childProcessId))
|
||||
{
|
||||
// Already exited
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isProcessDone = false;
|
||||
time_t startTime = time(0);
|
||||
time_t currentTime = startTime;
|
||||
AZ_Assert(currentTime != -1, "time(0) returned an invalid time");
|
||||
while (((currentTime - startTime) < waitTimeInSeconds) && !isProcessDone)
|
||||
{
|
||||
usleep(100);
|
||||
int wait_status = 0;
|
||||
int result = waitpid(m_pWatcherData->m_childProcessId, &wait_status, WNOHANG);
|
||||
if (result == m_pWatcherData->m_childProcessId)
|
||||
{
|
||||
isProcessDone = true;
|
||||
m_pWatcherData->m_childProcessIsDone = true;
|
||||
if (outExitCode)
|
||||
{
|
||||
*outExitCode = static_cast<AZ::u32>(WEXITSTATUS(wait_status));
|
||||
}
|
||||
}
|
||||
currentTime = time(0);
|
||||
}
|
||||
//returns false if process is still running after time
|
||||
return isProcessDone;
|
||||
}
|
||||
|
||||
void ProcessWatcher::TerminateProcess(AZ::u32 exitCode)
|
||||
{
|
||||
AZ_Assert(m_pWatcherData, "No watcher data");
|
||||
if (!m_pWatcherData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsProcessRunning())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
kill(m_pWatcherData->m_childProcessId, SIGKILL);
|
||||
waitpid(m_pWatcherData->m_childProcessId, NULL, 0);
|
||||
}
|
||||
} //namespace AzFramework
|
||||
|
||||
#endif // AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
-27
@@ -1,27 +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>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProjectManager
|
||||
{
|
||||
bool LaunchProjectManager()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // ProjectManager
|
||||
} // AzFramework
|
||||
|
||||
@@ -16,7 +16,9 @@ set(FILES
|
||||
AzFramework/API/ApplicationAPI_Mac.h
|
||||
AzFramework/Application/Application_Mac.mm
|
||||
AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp
|
||||
AzFramework/ProjectManager/ProjectManager_Mac.cpp
|
||||
AzFramework/Process/ProcessWatcher_Mac.cpp
|
||||
AzFramework/Process/ProcessCommon.h
|
||||
AzFramework/Process/ProcessCommunicator_Mac.cpp
|
||||
../Common/UnixLike/AzFramework/IO/LocalFileIO_UnixLike.cpp
|
||||
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
|
||||
../Common/Unimplemented/AzFramework/StreamingInstall/StreamingInstall_Unimplemented.cpp
|
||||
|
||||
+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
|
||||
|
||||
@@ -16,3 +16,6 @@
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_C11_LOCALTIME_S 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 1
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 0
|
||||
#define AZ_TRAIT_AZFRAMEWORK_PROCESSLAUNCH_DEFAULT 0
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class CommunicatorHandleImpl
|
||||
{
|
||||
public:
|
||||
~CommunicatorHandleImpl() = default;
|
||||
|
||||
bool IsValid() const { return false; }
|
||||
bool IsBroken() const { return false; }
|
||||
int GetHandle() const { return -1; }
|
||||
|
||||
void Break() {}
|
||||
void Close() {}
|
||||
void SetHandle([[maybe_unused]] int handle) {}
|
||||
|
||||
};
|
||||
|
||||
struct StartupInfo
|
||||
{
|
||||
~StartupInfo();
|
||||
|
||||
void SetupHandlesForChildProcess();
|
||||
void CloseAllHandles();
|
||||
|
||||
int m_inputHandleForChild = -1;
|
||||
int m_outputHandleForChild = -1;
|
||||
int m_errorHandleForChild = -1;
|
||||
};
|
||||
|
||||
struct ProcessData
|
||||
{
|
||||
StartupInfo m_startupInfo;
|
||||
int m_childProcessId = 0;
|
||||
bool m_childProcessIsDone = false;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
AZ::u32 StdInOutCommunication::PeekHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::ReadDataFromHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle, [[maybe_unused]] void* readBuffer, [[maybe_unused]] AZ::u32 bufferSize)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutCommunication::WriteDataToHandle([[maybe_unused]] StdProcessCommunicatorHandle& handle, [[maybe_unused]] const void* writeBuffer, [[maybe_unused]] AZ::u32 bytesToWrite)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicator::CreatePipesForProcess([[maybe_unused]] ProcessData* processData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void StdInOutProcessCommunicator::WaitForReadyOutputs([[maybe_unused]] OutputStatus& status) const
|
||||
{
|
||||
status.outputDeviceReady = false;
|
||||
status.errorsDeviceReady = false;
|
||||
status.shouldReadOutput = status.shouldReadErrors = false;
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicatorForChildProcess::AttachToExistingPipes()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
|
||||
StartupInfo::~StartupInfo()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StartupInfo::SetupHandlesForChildProcess()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StartupInfo::CloseAllHandles()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchUnwatchedProcess([[maybe_unused]] const ProcessLaunchInfo& processLaunchInfo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessLauncher::LaunchProcess([[maybe_unused]] const ProcessLaunchInfo& processLaunchInfo, [[maybe_unused]] ProcessData& processData)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StdProcessCommunicator* ProcessWatcher::CreateStdCommunicator()
|
||||
{
|
||||
return new StdInOutProcessCommunicator();
|
||||
}
|
||||
|
||||
StdProcessCommunicatorForChildProcess* ProcessWatcher::CreateStdCommunicatorForChildProcess()
|
||||
{
|
||||
return new StdInOutProcessCommunicatorForChildProcess();
|
||||
}
|
||||
|
||||
ProcessWatcher* ProcessWatcher::LaunchProcess([[maybe_unused]] const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, [[maybe_unused]] ProcessCommunicationType communicationType)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ProcessWatcher::InitProcessData([[maybe_unused]] bool stdProcessData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ProcessWatcher::ProcessWatcher()
|
||||
: m_pCommunicator(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ProcessWatcher::~ProcessWatcher()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ProcessWatcher::IsProcessRunning([[maybe_unused]] AZ::u32* outExitCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessWatcher::WaitForProcessToExit([[maybe_unused]] AZ::u32 waitTimeInSeconds, [[maybe_unused]] AZ::u32* outExitCode /*= nullptr*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProcessWatcher::TerminateProcess([[maybe_unused]] AZ::u32 exitCode)
|
||||
{
|
||||
|
||||
}
|
||||
} //namespace AzFramework
|
||||
-28
@@ -1,28 +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>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProjectManager
|
||||
{
|
||||
bool LaunchProjectManager()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} // ProjectManager
|
||||
} // AzFramework
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ set(FILES
|
||||
AzFramework/API/ApplicationAPI_Platform.h
|
||||
AzFramework/API/ApplicationAPI_iOS.h
|
||||
AzFramework/Application/Application_iOS.mm
|
||||
AzFramework/ProjectManager/ProjectManager_iOS.cpp
|
||||
../Common/Unimplemented/AzFramework/Asset/AssetSystemComponentHelper_Unimplemented.cpp
|
||||
../Common/UnixLike/AzFramework/IO/LocalFileIO_UnixLike.cpp
|
||||
../Common/Default/AzFramework/Network/AssetProcessorConnection_Default.cpp
|
||||
@@ -34,5 +33,8 @@ set(FILES
|
||||
../Common/Apple/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Apple.mm
|
||||
AzFramework/Archive/ArchiveVars_Platform.h
|
||||
AzFramework/Archive/ArchiveVars_iOS.h
|
||||
AzFramework/Process/ProcessCommon.h
|
||||
AzFramework/Process/ProcessWatcher_iOS.cpp
|
||||
AzFramework/Process/ProcessCommunicator_iOS.cpp
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user