Integrating latest from github/staging
Integrating up through commit 5e1bdae
This commit is contained in:
+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
|
||||
|
||||
Reference in New Issue
Block a user