Integrating latest from github/staging
Integrating up through commit 5e1bdae
This commit is contained in:
@@ -70,18 +70,12 @@ namespace AzFramework
|
||||
/// Make path relative to the provided root.
|
||||
virtual void MakePathRelative(AZStd::string& /*fullPath*/, const char* /*rootPath*/) {}
|
||||
|
||||
/// Retrieves the asset root path for the application.
|
||||
virtual const char* GetAssetRoot() const { return nullptr; }
|
||||
|
||||
/// Gets the engine root path where the modules for the current engine are located.
|
||||
virtual const char* GetEngineRoot() const { return nullptr; }
|
||||
|
||||
/// Retrieves the app root path for the application.
|
||||
virtual const char* GetAppRoot() const { return nullptr; }
|
||||
|
||||
/// Sets the asset root path for the application.
|
||||
virtual void SetAssetRoot(const char* /*assetRoot*/) {}
|
||||
|
||||
#pragma push_macro("GetCommandLine")
|
||||
#undef GetCommandLine
|
||||
/// Get the Command Line arguments passed in.
|
||||
@@ -117,8 +111,8 @@ namespace AzFramework
|
||||
/// Resolve a path thats relative to the engine folder to an absolute path
|
||||
virtual void ResolveEnginePath(AZStd::string& /*engineRelativePath*/) const {}
|
||||
|
||||
/// Calculate the branch token from the current application's asset root
|
||||
virtual void CalculateBranchTokenForAppRoot(AZStd::string& token) const = 0;
|
||||
/// Calculate the branch token from the current application's engine root
|
||||
virtual void CalculateBranchTokenForEngineRoot(AZStd::string& token) const = 0;
|
||||
|
||||
/*!
|
||||
* Returns a Type Uuid of the component for the given componentId and entityId.
|
||||
|
||||
@@ -154,12 +154,12 @@ namespace AzFramework
|
||||
return AZ::Success(AZStd::move(loadedDescriptor));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Application::Application()
|
||||
: Application(nullptr, nullptr)
|
||||
: Application(nullptr, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Application::Application(int* argc, char*** argv)
|
||||
: ComponentApplication(
|
||||
argc ? *argc : 0,
|
||||
@@ -188,7 +188,6 @@ namespace AzFramework
|
||||
SetFileIOAliases();
|
||||
}
|
||||
|
||||
|
||||
ApplicationRequests::Bus::Handler::BusConnect();
|
||||
AZ::UserSettingsFileLocatorBus::Handler::BusConnect();
|
||||
NetSystemRequestBus::Handler::BusConnect();
|
||||
@@ -238,14 +237,8 @@ namespace AzFramework
|
||||
{
|
||||
AZ::Entity* systemEntity = Create(descriptor, startupParameters);
|
||||
|
||||
// Attempt to use the "CacheGameFolder" key in the settings registry to set the asset root
|
||||
// If that fails, fallback to using the App root as the asset root
|
||||
if (!m_settingsRegistry->Get(m_assetRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheGameFolder))
|
||||
{
|
||||
m_assetRoot = GetAppRoot();
|
||||
}
|
||||
// Sets FileIOAliases again in case the App root was overridden by the
|
||||
// startupParamets in ComponentApplication::Create
|
||||
// startupParameters in ComponentApplication::Create
|
||||
SetFileIOAliases();
|
||||
|
||||
if (systemEntity)
|
||||
@@ -268,7 +261,7 @@ namespace AzFramework
|
||||
void Application::PreModuleLoad()
|
||||
{
|
||||
// Calculate the engine root by reading the engine.json file
|
||||
AZStd::string engineJsonPath = AZStd::string_view{ m_appRoot };
|
||||
AZStd::string engineJsonPath = AZStd::string_view{ m_engineRoot };
|
||||
engineJsonPath += s_engineConfigFileName;
|
||||
AzFramework::StringFunc::Path::Normalize(engineJsonPath);
|
||||
AZ::IO::LocalFileIO localFileIO;
|
||||
@@ -276,7 +269,7 @@ namespace AzFramework
|
||||
|
||||
if (readJsonResult.IsSuccess())
|
||||
{
|
||||
SetRootPath(RootPathType::EngineRoot, m_appRoot.c_str());
|
||||
SetRootPath(RootPathType::EngineRoot, m_engineRoot.c_str());
|
||||
AZ_TracePrintf(s_azFrameworkWarningWindow, "Engine Path: %s\n", m_engineRoot.c_str());
|
||||
}
|
||||
else
|
||||
@@ -410,17 +403,16 @@ namespace AzFramework
|
||||
azrtti_typeid<AzFramework::StreamingInstall::StreamingInstallSystemComponent>(),
|
||||
azrtti_typeid<AzFramework::SpawnableSystemComponent>(),
|
||||
AZ::Uuid("{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}"), // ScriptDebugAgent
|
||||
});
|
||||
});
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
AZStd::string Application::ResolveFilePath(AZ::u32 providerId)
|
||||
// UserSettingsFileLocatorBus
|
||||
AZStd::string Application::ResolveFilePath([[maybe_unused]] AZ::u32 providerId)
|
||||
{
|
||||
(void)providerId;
|
||||
|
||||
AZStd::string result;
|
||||
AzFramework::StringFunc::Path::Join(GetAppRoot(), "UserSettings.xml", result, /*bCaseInsenitive*/false);
|
||||
AzFramework::StringFunc::Path::Join(GetEngineRoot(), "UserSettings.xml", result, /*bCaseInsenitive*/false);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -451,11 +443,6 @@ namespace AzFramework
|
||||
outModules.emplace_back(aznew AzFrameworkModule());
|
||||
}
|
||||
|
||||
const char* Application::GetAssetRoot() const
|
||||
{
|
||||
return m_assetRoot.c_str();
|
||||
}
|
||||
|
||||
const char* Application::GetAppRoot() const
|
||||
{
|
||||
return m_appRoot.c_str();
|
||||
@@ -516,21 +503,15 @@ namespace AzFramework
|
||||
engineRelativePath = fullPath;
|
||||
}
|
||||
|
||||
void Application::CalculateBranchTokenForAppRoot(AZStd::string& token) const
|
||||
void Application::CalculateBranchTokenForEngineRoot(AZStd::string& token) const
|
||||
{
|
||||
AzFramework::StringFunc::AssetPath::CalculateBranchToken(AZStd::string(m_appRoot), token);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
void Application::SetAssetRoot(const char* assetRoot)
|
||||
{
|
||||
SetRootPath(RootPathType::AssetRoot, assetRoot);
|
||||
AzFramework::StringFunc::AssetPath::CalculateBranchToken(AZStd::string(m_engineRoot), token);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
void Application::MakePathRootRelative(AZStd::string& fullPath)
|
||||
{
|
||||
MakePathRelative(fullPath, m_appRoot.c_str());
|
||||
MakePathRelative(fullPath, m_engineRoot.c_str());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -538,7 +519,12 @@ namespace AzFramework
|
||||
{
|
||||
// relative file paths wrt AssetRoot are always lowercase
|
||||
AZStd::to_lower(fullPath.begin(), fullPath.end());
|
||||
MakePathRelative(fullPath, m_assetRoot.c_str());
|
||||
AZStd::string cacheAssetPath;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(cacheAssetPath, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
|
||||
}
|
||||
MakePathRelative(fullPath, cacheAssetPath.c_str());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -592,8 +578,8 @@ namespace AzFramework
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
void Application::PumpSystemEventLoopWhileDoingWorkInNewThread(const AZStd::chrono::milliseconds& eventPumpFrequency,
|
||||
const AZStd::function<void()>& workForNewThread,
|
||||
const char* newThreadName)
|
||||
const AZStd::function<void()>& workForNewThread,
|
||||
const char* newThreadName)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
|
||||
|
||||
@@ -604,7 +590,7 @@ namespace AzFramework
|
||||
AZStd::thread newThread([&workForNewThread, &binarySemaphore, &newThreadName]
|
||||
{
|
||||
AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::AzFramework,
|
||||
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:ThreadWorker %s", newThreadName);
|
||||
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:ThreadWorker %s", newThreadName);
|
||||
|
||||
workForNewThread();
|
||||
binarySemaphore.release();
|
||||
@@ -615,7 +601,7 @@ namespace AzFramework
|
||||
}
|
||||
{
|
||||
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzFramework,
|
||||
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:WaitOnThread %s", newThreadName);
|
||||
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:WaitOnThread %s", newThreadName);
|
||||
newThread.join();
|
||||
}
|
||||
|
||||
@@ -678,18 +664,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RootPathType::AssetRoot:
|
||||
{
|
||||
AZ_Assert(sourceLen < m_assetRoot.max_size(), "String overflow for Asset Root: %s", source);
|
||||
m_assetRoot = source;
|
||||
|
||||
AZStd::replace(std::begin(m_assetRoot), std::end(m_assetRoot), AZ_WRONG_FILESYSTEM_SEPARATOR, AZ_CORRECT_FILESYSTEM_SEPARATOR);
|
||||
if (appendTrailingPathSep)
|
||||
{
|
||||
m_assetRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RootPathType::EngineRoot:
|
||||
{
|
||||
AZ_Assert(sourceLen < m_engineRoot.max_size(), "String overflow for Engine Root: %s", source);
|
||||
@@ -706,95 +680,102 @@ namespace AzFramework
|
||||
AZ_Assert(false, "Invalid RootPathType (%d)", static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void CreateUserCache(const AZ::IO::FixedMaxPath& cacheUserPath, AZ::IO::FileIOBase& fileIoBase)
|
||||
{
|
||||
// The number of max attempts ultimately dictates the number of Lumberyard instances that can run
|
||||
// simultaneously. This should be a reasonably high number so that it doesn't artificially limit
|
||||
// the number of instances (ex: parallel level exports via multiple Editor runs). It also shouldn't
|
||||
// be set *infinitely* high - each cache folder is GBs in size, and finding a free directory is a
|
||||
// linear search, so the more instances we allow, the longer the search will take.
|
||||
// 128 seems like a reasonable compromise.
|
||||
constexpr int maxAttempts = 128;
|
||||
|
||||
constexpr const char* userCachePathFilename{ "Cache" };
|
||||
AZ::IO::FixedMaxPath userCachePath = cacheUserPath / userCachePathFilename;
|
||||
#if AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
int attemptNumber;
|
||||
for (attemptNumber = 0; attemptNumber < maxAttempts; ++attemptNumber)
|
||||
{
|
||||
if (attemptNumber != 0)
|
||||
{
|
||||
userCachePath.ReplaceFilename(AZStd::string_view{ AZ::IO::FixedMaxPathString::format("%s%i", userCachePathFilename, attemptNumber) });
|
||||
}
|
||||
|
||||
// if the directory already exists, check for locked file
|
||||
auto cacheLockFilePath = userCachePath / "lockfile.txt";
|
||||
|
||||
constexpr auto LockFileMode = AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY
|
||||
| AZ::IO::SystemFile::SF_OPEN_CREATE
|
||||
| AZ::IO::SystemFile::SF_OPEN_CREATE_PATH;
|
||||
if (AZ::IO::SystemFile lockFileHandle; lockFileHandle.Open(cacheLockFilePath.c_str(), LockFileMode))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (attemptNumber >= maxAttempts)
|
||||
{
|
||||
userCachePath.ReplaceFilename(userCachePathFilename);
|
||||
AZ_TracePrintf("Application", "Couldn't find a valid asset cache folder after %i attempts."
|
||||
" Setting cache folder to %s\n", maxAttempts, userCachePath.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
fileIoBase.SetAlias("@usercache@", userCachePath.c_str());
|
||||
}
|
||||
|
||||
void Application::SetFileIOAliases()
|
||||
{
|
||||
if (AZ::IO::FileIOBase::GetInstance() == m_archiveFileIO.get())
|
||||
if (m_archiveFileIO)
|
||||
{
|
||||
auto fileIoBase = m_archiveFileIO.get();
|
||||
// Set up the default file aliases based on the settings registry
|
||||
fileIoBase->SetAlias("@root@", GetAppRoot());
|
||||
fileIoBase->SetAlias("@assets@", GetAssetRoot());
|
||||
fileIoBase->SetAlias("@engroot@", GetAppRoot());
|
||||
fileIoBase->SetAlias("@devroot@", GetAppRoot());
|
||||
fileIoBase->SetAlias("@devassets@", GetAppRoot());
|
||||
fileIoBase->SetAlias("@assets@", "");
|
||||
fileIoBase->SetAlias("@root@", GetEngineRoot());
|
||||
fileIoBase->SetAlias("@engroot@", GetEngineRoot());
|
||||
fileIoBase->SetAlias("@projectroot@", GetEngineRoot());
|
||||
fileIoBase->SetAlias("@exefolder@", GetExecutableFolder());
|
||||
|
||||
{
|
||||
AZ::SettingsRegistryInterface::FixedValueString pathAliases;
|
||||
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
|
||||
AZ::IO::FixedMaxPath pathAliases;
|
||||
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
|
||||
{
|
||||
fileIoBase->SetAlias("@root@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@projectcache@", pathAliases.c_str());
|
||||
}
|
||||
pathAliases.clear();
|
||||
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheGameFolder))
|
||||
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
|
||||
{
|
||||
fileIoBase->SetAlias("@projectplatformcache@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@assets@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@root@", pathAliases.c_str()); // Deprecated Use @projectplatformcache@
|
||||
}
|
||||
pathAliases.clear();
|
||||
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
|
||||
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
|
||||
{
|
||||
fileIoBase->SetAlias("@engroot@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@devroot@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@devroot@", pathAliases.c_str()); // Deprecated - Use @engroot@
|
||||
}
|
||||
pathAliases.clear();
|
||||
if (m_settingsRegistry->Get(pathAliases, AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder))
|
||||
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath))
|
||||
{
|
||||
fileIoBase->SetAlias("@devassets@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@devassets@", pathAliases.c_str()); // Deprecated - Use @projectsourceassets@
|
||||
fileIoBase->SetAlias("@projectroot@", pathAliases.c_str());
|
||||
fileIoBase->SetAlias("@projectsourceassets@", (pathAliases / "Assets").c_str());
|
||||
}
|
||||
}
|
||||
|
||||
AZ::IO::FixedMaxPath projectUserPath;
|
||||
if (m_settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
|
||||
{
|
||||
auto userPath = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "user", fileIoBase->GetAlias("@root@"));
|
||||
fileIoBase->SetAlias("@user@", userPath.c_str());
|
||||
fileIoBase->SetAlias("@user@", projectUserPath.c_str());
|
||||
AZ::IO::FixedMaxPath projectLogPath = projectUserPath / "log";
|
||||
fileIoBase->SetAlias("@log@", projectLogPath.c_str());
|
||||
fileIoBase->CreatePath(projectLogPath.c_str()); // Create the log directory at this point
|
||||
|
||||
CreateUserCache(projectUserPath, *fileIoBase);
|
||||
}
|
||||
|
||||
{
|
||||
auto logPath = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "log", fileIoBase->GetAlias("@user@"));
|
||||
fileIoBase->SetAlias("@log@", logPath.c_str());
|
||||
|
||||
// Create the Log folder if it doesn't exist
|
||||
fileIoBase->CreatePath("@log@");
|
||||
}
|
||||
|
||||
auto cachePathOriginal = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "cache", fileIoBase->GetAlias("@user@"));
|
||||
// The number of max attempts ultimately dictates the number of Lumberyard instances that can run
|
||||
// simultaneously. This should be a reasonably high number so that it doesn't artificially limit
|
||||
// the number of instances (ex: parallel level exports via multiple Editor runs). It also shouldn't
|
||||
// be set *infinitely* high - each cache folder is GBs in size, and finding a free directory is a
|
||||
// linear search, so the more instances we allow, the longer the search will take.
|
||||
// 128 seems like a reasonable compromise.
|
||||
constexpr int maxAttempts = 128;
|
||||
|
||||
AZ::StringFunc::Path::FixedString cachePath(cachePathOriginal);
|
||||
|
||||
#if AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
int attemptNumber;
|
||||
for (attemptNumber = 0; attemptNumber < maxAttempts; ++attemptNumber)
|
||||
{
|
||||
if (attemptNumber != 0)
|
||||
{
|
||||
cachePath = AZ::StringFunc::Path::FixedString::format("%s%i", cachePathOriginal.c_str(), attemptNumber);
|
||||
}
|
||||
|
||||
fileIoBase->CreatePath(cachePath.c_str());
|
||||
// if the directory already exists, check for locked file
|
||||
auto cacheLockFilePath = AZ::StringFunc::Path::FixedString::format("%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "lockfile.txt", cachePath.c_str());
|
||||
|
||||
AZ::IO::HandleType lockFileHandle;
|
||||
if (fileIoBase->Open(cacheLockFilePath.c_str(), AZ::IO::OpenMode::ModeWrite, lockFileHandle))
|
||||
{
|
||||
fileIoBase->Close(lockFileHandle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (attemptNumber >= maxAttempts)
|
||||
{
|
||||
cachePath = cachePathOriginal;
|
||||
AZ_TracePrintf("Application", "Couldn't find a valid asset cache folder after %i attempts."
|
||||
" Setting cache folder to cachePath %s\n", maxAttempts, cachePath.c_str());
|
||||
}
|
||||
#endif
|
||||
fileIoBase->SetAlias("@cache@", cachePath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,11 +100,10 @@ namespace AzFramework
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//! ApplicationRequests::Bus::Handler
|
||||
const char* GetAssetRoot() const override;
|
||||
const char* GetEngineRoot() const override { return m_engineRoot.c_str(); }
|
||||
const char* GetAppRoot() const override;
|
||||
void ResolveEnginePath(AZStd::string& engineRelativePath) const override;
|
||||
void CalculateBranchTokenForAppRoot(AZStd::string& token) const override;
|
||||
void CalculateBranchTokenForEngineRoot(AZStd::string& token) const override;
|
||||
|
||||
#pragma push_macro("GetCommandLine")
|
||||
#undef GetCommandLine
|
||||
@@ -112,7 +111,6 @@ namespace AzFramework
|
||||
#pragma pop_macro("GetCommandLine")
|
||||
const CommandLine* GetApplicationCommandLine() override { return &m_commandLine; }
|
||||
|
||||
void SetAssetRoot(const char* assetRoot) override;
|
||||
void MakePathRootRelative(AZStd::string& fullPath) override;
|
||||
void MakePathAssetRootRelative(AZStd::string& fullPath) override;
|
||||
void MakePathRelative(AZStd::string& fullPath, const char* rootPath) override;
|
||||
@@ -180,9 +178,6 @@ namespace AzFramework
|
||||
|
||||
AZ::StringFunc::Path::FixedString m_configFilePath;
|
||||
|
||||
AZ::StringFunc::Path::FixedString m_assetRoot;
|
||||
AZ::StringFunc::Path::FixedString m_engineRoot; ///> Location of the engine root folder that this application is based on
|
||||
|
||||
AZStd::unique_ptr<AZ::IO::LocalFileIO> m_directFileIO; ///> The Direct file IO instance is a LocalFileIO.
|
||||
AZStd::unique_ptr<AZ::IO::FileIOBase> m_archiveFileIO; ///> The Default file IO instance is a ArchiveFileIO.
|
||||
AZStd::unique_ptr<AZ::IO::Archive> m_archive; ///> The AZ::IO::Instance
|
||||
@@ -194,7 +189,6 @@ namespace AzFramework
|
||||
enum class RootPathType
|
||||
{
|
||||
AppRoot,
|
||||
AssetRoot,
|
||||
EngineRoot
|
||||
};
|
||||
void SetRootPath(RootPathType type, const char* source);
|
||||
|
||||
@@ -92,7 +92,8 @@ namespace AZ::IO::ArchiveInternal
|
||||
convertedPath->Native().replace(0, aliasToLookFor.size(), aliasToReplaceWith);
|
||||
}
|
||||
// lowercase path if it starts with either the @assets@ or @root@ alias
|
||||
if (convertedPath->Native().starts_with("@assets@") || convertedPath->Native().starts_with("@root@"))
|
||||
if (convertedPath->Native().starts_with("@assets@") || convertedPath->Native().starts_with("@root@")
|
||||
|| convertedPath->Native().starts_with("@projectplatformcache@"))
|
||||
{
|
||||
AZStd::to_lower(convertedPath->Native().begin(), convertedPath->Native().end());
|
||||
}
|
||||
|
||||
@@ -604,4 +604,14 @@ namespace AZ::IO
|
||||
}
|
||||
return realUnderlyingFileIO->ResolvePath(resolvedPath, path);
|
||||
}
|
||||
|
||||
bool ArchiveFileIO::ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const
|
||||
{
|
||||
FileIOBase* realUnderlyingFileIO = FileIOBase::GetDirectInstance();
|
||||
if (!realUnderlyingFileIO)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return realUnderlyingFileIO->ReplaceAlias(replacedAliasPath, path);
|
||||
}
|
||||
}//namespace AZ:IO
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace AZ::IO
|
||||
bool ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const override;
|
||||
bool ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const override;
|
||||
using FileIOBase::ResolvePath;
|
||||
bool ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const override;
|
||||
bool GetFilename(IO::HandleType fileHandle, char* filename, AZ::u64 filenameSize) const override;
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Serialization/ObjectStream.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/parallel/lock.h>
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
@@ -525,7 +526,10 @@ namespace AzFramework
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_registryMutex);
|
||||
|
||||
// Get asset root from application.
|
||||
EBUS_EVENT_RESULT(m_assetRoot, AzFramework::ApplicationRequests::Bus, GetAssetRoot);
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(m_assetRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
|
||||
}
|
||||
|
||||
// Reflect registry for serialization.
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace AzFramework
|
||||
|
||||
//! Name of the game project that is used for negotiating a connection with the AssetProcessor
|
||||
//! The AssetProcessor needs to be processing Assets for the specified game project
|
||||
//! (Can be queried from Settings Registry - "sys_game_folder")
|
||||
//! (Can be queried from Settings Registry - "project_name" under the ProjectSettingsRootKey)
|
||||
AZStd::fixed_string<64> m_projectName;
|
||||
//! The IP address to use either connect to or from the AssetProcessor
|
||||
//! (Can be queried from Settings Registry - "remote_ip")
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace AzFramework
|
||||
namespace AssetSystem
|
||||
{
|
||||
constexpr char BranchToken[] = "assetProcessor_branch_token";
|
||||
constexpr char ProjectName[] = "sys_game_folder";
|
||||
constexpr char Assets[] = "assets";
|
||||
constexpr char AssetProcessorRemoteIp[] = "remote_ip";
|
||||
constexpr char AssetProcessorRemotePort[] = "remote_port";
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace AzFramework::AssetSystem::Platform
|
||||
|
||||
// Declare platform specific LaunchAssetProcessor function
|
||||
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view appRoot,
|
||||
AZStd::string_view projectName);
|
||||
AZStd::string_view projectPath);
|
||||
}
|
||||
|
||||
namespace AzFramework
|
||||
@@ -67,23 +67,21 @@ namespace AzFramework
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
|
||||
// Add the engine path to the launch command if available from the Settings Registry
|
||||
AZ::SettingsRegistryInterface::FixedValueString engineRootFolder;
|
||||
// Add the app-root to the launch command if available from the Settings Registry
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Get(engineRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
}
|
||||
|
||||
// Add the active game project to the launch from the Settings Registry
|
||||
const auto gameProjectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/sys_game_folder",
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
|
||||
AZ::SettingsRegistryInterface::FixedValueString gameProjectName;
|
||||
// Add the active project's path to the launch command if available from the Settings Registry
|
||||
AZ::SettingsRegistryInterface::FixedValueString projectPath;
|
||||
if (settingsRegistry)
|
||||
{
|
||||
settingsRegistry->Get(gameProjectName, gameProjectKey);
|
||||
settingsRegistry->Get(projectPath, AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
|
||||
}
|
||||
|
||||
if (!Platform::LaunchAssetProcessor(executableDirectory, engineRootFolder, gameProjectName))
|
||||
if (!Platform::LaunchAssetProcessor(executableDirectory, engineRootFolder, projectPath))
|
||||
{
|
||||
// if we are unable to launch asset processor
|
||||
AzFramework::AssetSystemInfoBus::Broadcast(&AzFramework::AssetSystem::AssetSystemInfoNotifications::OnError, AssetSystemErrors::ASSETSYSTEM_FAILED_TO_LAUNCH_ASSETPROCESSOR);
|
||||
@@ -179,24 +177,22 @@ namespace AzFramework
|
||||
|
||||
{
|
||||
// Read Branch Token from Settings Registry
|
||||
AZ::s64 branchToken64;
|
||||
if (!settingsRegistry->Get(branchToken64, AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
|
||||
AZStd::string branchToken;
|
||||
if (!settingsRegistry->Get(branchToken, AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::BranchToken)))
|
||||
{
|
||||
// The first time the AssetProcessor runs within a branch the bootstrap.cfg does not have a branch token set
|
||||
// Therefore it is not an error for the branch token to not be in the bootstrap.cfg file
|
||||
AZStd::string branchToken;
|
||||
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, branchToken);
|
||||
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, branchToken);
|
||||
AZ_TracePrintfOnce("AssetSystemComponent", "Failed to read branch token from bootstrap. Calculating Branch Token: %s\n", branchToken.c_str());
|
||||
outputConnectionSettings.m_branchToken = branchToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputConnectionSettings.m_branchToken = AZStd::fixed_string<32>::format("0x%08X", aznumeric_cast<AZ::u32>(branchToken64));
|
||||
outputConnectionSettings.m_branchToken = AZStd::string_view{ branchToken };
|
||||
if (outputConnectionSettings.m_branchToken.empty())
|
||||
{
|
||||
AZStd::string branchToken;
|
||||
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, branchToken);
|
||||
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, branchToken);
|
||||
AZ_TracePrintfOnce("AssetSystemComponent", "Branch token read from bootstrap is empty. Calculating Branch Token: %s\n", branchToken.c_str());
|
||||
outputConnectionSettings.m_branchToken = branchToken;
|
||||
}
|
||||
@@ -205,19 +201,14 @@ namespace AzFramework
|
||||
|
||||
{
|
||||
// Read Project Name from Settings Registry
|
||||
AZ::SettingsRegistryInterface::FixedValueString projectName;
|
||||
if (!settingsRegistry->Get(projectName, AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::ProjectName)))
|
||||
AZ::SettingsRegistryInterface::FixedValueString projectName = AZ::Utils::GetProjectName();
|
||||
if (projectName.empty())
|
||||
{
|
||||
AZ_Error("AssetSystemComponent", false, "Failed to read project name from bootstrap");
|
||||
AZ_Error("AssetSystemComponent", false, "Failed to read project name from registry");
|
||||
result = false;
|
||||
}
|
||||
|
||||
outputConnectionSettings.m_projectName = projectName;
|
||||
if (outputConnectionSettings.m_projectName.empty())
|
||||
{
|
||||
AZ_Error("AssetSystemComponent", false, "Project name read from bootstrap is empty");
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the direction in which a connection to the Asset Processor should be from using the Settings Registry
|
||||
|
||||
@@ -244,9 +244,9 @@ namespace AzFramework
|
||||
AZStd::string FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType fileTagType)
|
||||
{
|
||||
AZStd::string destinationFilePath;
|
||||
const char* appRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
|
||||
AzFramework::StringFunc::Path::ConstructFull(appRoot, EngineName, fileTagType == FileTagType::Exclude ? ExcludeFileName : IncludeFileName, AzFramework::FileTag::FileTagAsset::Extension(), destinationFilePath, true);
|
||||
const char* engineRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
|
||||
AzFramework::StringFunc::Path::ConstructFull(engineRoot, EngineName, fileTagType == FileTagType::Exclude ? ExcludeFileName : IncludeFileName, AzFramework::FileTag::FileTagAsset::Extension(), destinationFilePath, true);
|
||||
return destinationFilePath;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Debug/Trace.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <AzFramework/Gem/GemInfo.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
GemInfo::GemInfo(AZStd::string name)
|
||||
: m_gemName(AZStd::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
bool GetGemsInfo(AZStd::vector<GemInfo>& gemInfoList, AZ::SettingsRegistryInterface& settingsRegistry)
|
||||
{
|
||||
AZStd::vector<AZ::IO::FixedMaxPath> gemModuleSourcePaths;
|
||||
|
||||
struct GemSourcePathsVisitor
|
||||
: AZ::SettingsRegistryInterface::Visitor
|
||||
{
|
||||
GemSourcePathsVisitor(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::vector<GemInfo>& gemInfoList)
|
||||
: m_settingsRegistry(settingsRegistry)
|
||||
, m_gemInfoList(gemInfoList)
|
||||
{
|
||||
}
|
||||
|
||||
void Visit(AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type,
|
||||
AZStd::string_view value) override
|
||||
{
|
||||
AZStd::string_view jsonPointerPath{ path };
|
||||
// Remove the array index from the path and check if the JSON path ends with "/SourcePaths"
|
||||
AZ::StringFunc::TokenizeLast(jsonPointerPath, '/');
|
||||
if (jsonPointerPath.ends_with("/SourcePaths"))
|
||||
{
|
||||
AZStd::string_view gemName;
|
||||
// The parent key of the "SourcePaths" field is the gem name
|
||||
AZ::StringFunc::TokenizeLast(jsonPointerPath, '/'); // Peel off "/SourcePaths"
|
||||
// Retrieve Gem name
|
||||
if (auto gemNameToken = AZ::StringFunc::TokenizeLast(jsonPointerPath, '/'); gemNameToken.has_value())
|
||||
{
|
||||
gemName = *gemNameToken;
|
||||
}
|
||||
|
||||
auto FindGemInfoByName = [gemName](const GemInfo& gemInfo)
|
||||
{
|
||||
return gemName == gemInfo.m_gemName;
|
||||
};
|
||||
auto gemInfoFoundIter = AZStd::find_if(m_gemInfoList.begin(), m_gemInfoList.end(), FindGemInfoByName);
|
||||
GemInfo& gemInfo = gemInfoFoundIter != m_gemInfoList.end() ? *gemInfoFoundIter : m_gemInfoList.emplace_back(gemName);
|
||||
|
||||
AZ::IO::Path& gemAbsPath = gemInfo.m_absoluteSourcePaths.emplace_back(value);
|
||||
// Resolve any file aliases first - Do not use ResolvePath() as that assumes
|
||||
// any relative path is underneath the @assets@ alias
|
||||
if (auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); fileIoBase != nullptr)
|
||||
{
|
||||
AZ::IO::FixedMaxPath replacedAliasPath;
|
||||
if (fileIoBase->ReplaceAlias(replacedAliasPath, value))
|
||||
{
|
||||
gemAbsPath = AZ::IO::PathView(replacedAliasPath);
|
||||
}
|
||||
}
|
||||
|
||||
// The current assumption is that the gem source path is the relative to the engine root
|
||||
AZ::IO::FixedMaxPath engineRootPath;
|
||||
m_settingsRegistry.Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
gemAbsPath = (engineRootPath / gemAbsPath).LexicallyNormal();
|
||||
}
|
||||
}
|
||||
|
||||
AZ::SettingsRegistryInterface& m_settingsRegistry;
|
||||
AZStd::vector<GemInfo>& m_gemInfoList;
|
||||
};
|
||||
|
||||
GemSourcePathsVisitor visitor{ settingsRegistry, gemInfoList };
|
||||
constexpr auto gemListKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::OrganizationRootKey)
|
||||
+ "/Gems";
|
||||
settingsRegistry.Visit(visitor, gemListKey);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class SettingsRegistryInterface;
|
||||
}
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//! This struct stores gem related information
|
||||
struct GemInfo
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(GemInfo, AZ::SystemAllocator, 0);
|
||||
GemInfo(AZStd::string name);
|
||||
GemInfo() = default;
|
||||
AZStd::string m_gemName; //!< A friendly display name, not to be used for any pathing stuff.
|
||||
AZStd::vector<AZ::IO::Path> m_absoluteSourcePaths; //!< Where the gem's source path folder are located(as an absolute path)
|
||||
|
||||
static constexpr const char* GetGemAssetFolder() { return "Assets"; }
|
||||
};
|
||||
|
||||
//! Returns a list of GemInfo of all the gems that are active for the for the specified game project.
|
||||
//! Please note that the game project could be in a different location to the engine therefore we need the assetRoot param.
|
||||
bool GetGemsInfo(AZStd::vector<GemInfo>& gemInfoList, AZ::SettingsRegistryInterface& settingsRegistry);
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <AzCore/Casting/lossy_cast.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <cctype>
|
||||
|
||||
namespace AZ
|
||||
@@ -479,10 +480,9 @@ namespace AZ
|
||||
azstrncpy(resolvedPath, resolvedPathSize, path, pathLen + 1);
|
||||
|
||||
//see if the absolute path uses @assets@ or @root@, if it does lowercase the relative part
|
||||
if (!LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@assets@")))
|
||||
{
|
||||
LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@root@"));
|
||||
}
|
||||
[[maybe_unused]] bool lowercasePath = LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@assets@"))
|
||||
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@root@"))
|
||||
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@projectplatformcache@"));
|
||||
|
||||
ToUnixSlashes(resolvedPath, resolvedPathSize);
|
||||
return true;
|
||||
@@ -707,10 +707,9 @@ namespace AZ
|
||||
size_t keyLen = alias.first.length();
|
||||
if (azstrnicmp(resolvedPath, key, keyLen) == 0) // we only support aliases at the front of the path
|
||||
{
|
||||
if(azstrnicmp(key, "@assets@", 8) == 0 || azstrnicmp(key, "@root@", 6) == 0)
|
||||
{
|
||||
AZStd::to_lower(resolvedPath, resolvedPath + resolvedPathSize);
|
||||
}
|
||||
[[maybe_unused]] bool lowercasePath = LowerIfBeginsWith(resolvedPath, resolvedPathSize, "@assets@")
|
||||
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, "@root@")
|
||||
|| LowerIfBeginsWith(resolvedPath, resolvedPathSize, "@projectplatformcache@");
|
||||
|
||||
const char* dest = alias.second.c_str();
|
||||
size_t destLen = alias.second.length();
|
||||
@@ -742,6 +741,44 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LocalFileIO::ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
replacedAliasPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
AZStd::string_view pathStrView = path.Native();
|
||||
for (const auto& [aliasKey, aliasValue] : m_aliases)
|
||||
{
|
||||
if (AZ::StringFunc::StartsWith(pathStrView, aliasKey))
|
||||
{
|
||||
// Reduce of the size result result path by the size of the and add the resolved alias size
|
||||
AZStd::string_view postAliasView = pathStrView.substr(aliasKey.size());
|
||||
size_t requiredFixedMaxPathSize = postAliasView.size();
|
||||
requiredFixedMaxPathSize += aliasValue.size();
|
||||
|
||||
// The replaced alias path is greater than 1024 characters, return false
|
||||
if (requiredFixedMaxPathSize > replacedAliasPath.Native().max_size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
replacedAliasPath.Native() = AZ::IO::FixedMaxPathString(AZStd::string_view{ aliasValue });
|
||||
replacedAliasPath.Native() += postAliasView;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathStrView.size() > replacedAliasPath.Native().max_size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
replacedAliasPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalFileIO::GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const
|
||||
{
|
||||
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace AZ
|
||||
bool ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const override;
|
||||
bool ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const override;
|
||||
using FileIOBase::ResolvePath;
|
||||
bool ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const override;
|
||||
|
||||
bool GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const override;
|
||||
bool ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const;
|
||||
|
||||
@@ -830,6 +830,15 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkFileIO::ReplaceAlias([[maybe_unused]] AZ::IO::FixedMaxPath& replaceAliasPath, [[maybe_unused]] const AZ::IO::PathView& path) const
|
||||
{
|
||||
REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::ReplaceAlias(path=%.*s)",
|
||||
aznumeric_cast<int>(path.Native().size()), path.Native.data()).c_str());
|
||||
REMOTEFILE_LOG_APPEND(AZStd::string::format("NetworkFileIO::ReplaceAlias(path=%.*s) return false",
|
||||
aznumeric_cast<int>(path.Native().size()), path.Native().data()).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkFileIO::GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const
|
||||
{
|
||||
REMOTEFILE_LOG_CALL(AZStd::string::format("NetworkFileIO()::GetFilename(fileHandle=%u, filename=%s, filenamesize=%u)", fileHandle, filename?filename:"nullptr", filenameSize).c_str());
|
||||
@@ -1388,6 +1397,11 @@ namespace AZ
|
||||
return m_excludedFileIO ? m_excludedFileIO->ResolvePath(resolvedPath, path) : false;
|
||||
}
|
||||
|
||||
bool RemoteFileIO::ReplaceAlias(AZ::IO::FixedMaxPath& replaceAliasPath, const AZ::IO::PathView& path) const
|
||||
{
|
||||
return m_excludedFileIO ? m_excludedFileIO->ReplaceAlias(replaceAliasPath, path) : false;
|
||||
}
|
||||
|
||||
#ifdef REMOTEFILEIO_CACHE_FILETREE
|
||||
bool RemoteFileIO::Exists(const char* filePath)
|
||||
{
|
||||
|
||||
@@ -113,6 +113,7 @@ namespace AZ
|
||||
bool ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const override;
|
||||
bool ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const override;
|
||||
using FileIOBase::ResolvePath;
|
||||
bool ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const override;
|
||||
bool GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const override;
|
||||
bool IsRemoteIOEnabled() override;
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -203,6 +204,7 @@ namespace AZ
|
||||
bool ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const override;
|
||||
bool ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const override;
|
||||
using FileIOBase::ResolvePath;
|
||||
bool ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const override;
|
||||
|
||||
#ifdef REMOTEFILEIO_CACHE_FILETREE
|
||||
bool Exists(const char* filePath) override;
|
||||
|
||||
@@ -59,7 +59,6 @@ namespace AzFramework
|
||||
namespace
|
||||
{
|
||||
const char* suffix = ".log";
|
||||
const char* loggingDirectoryName = "logs";
|
||||
const int maxLogFiles = 5;
|
||||
|
||||
void TimeNowAsString(char* buffer, size_t len)
|
||||
@@ -155,7 +154,7 @@ namespace AzFramework
|
||||
m_directoryName = AZStd::string(baseDirectory);
|
||||
|
||||
//Construct the file path
|
||||
if (!AzFramework::StringFunc::Path::ConstructFull(baseDirectory, loggingDirectoryName, fileName, suffix, m_filePath))
|
||||
if (!AzFramework::StringFunc::Path::ConstructFull(baseDirectory, fileName, suffix, m_filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -193,7 +192,7 @@ namespace AzFramework
|
||||
AZStd::string backupFileName;
|
||||
|
||||
//Construct the backup file path
|
||||
if (!AzFramework::StringFunc::Path::ConstructFull(m_directoryName.c_str(), loggingDirectoryName, m_fileName.c_str(), newSuffix.c_str(), backupFileName))
|
||||
if (!AzFramework::StringFunc::Path::ConstructFull(m_directoryName.c_str(), m_fileName.c_str(), newSuffix.c_str(), backupFileName))
|
||||
{
|
||||
AZ_Warning("Log Component", false, "Unable to construct the backup file path");
|
||||
return "";
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
// Type of communication between parent and child processes
|
||||
enum ProcessCommunicationType
|
||||
{
|
||||
COMMUNICATOR_TYPE_STDINOUT,
|
||||
COMMUNICATOR_TYPE_NONE,
|
||||
//COMMUNICATOR_TYPE_IPC
|
||||
};
|
||||
|
||||
enum ProcessPriority
|
||||
{
|
||||
// we don't support raising priority
|
||||
PROCESSPRIORITY_NORMAL,
|
||||
PROCESSPRIORITY_BELOWNORMAL, // below other normal priorities
|
||||
PROCESSPRIORITY_IDLE, // lowest possible priority
|
||||
};
|
||||
|
||||
struct ProcessData;
|
||||
class ProcessOutput;
|
||||
class ProcessCommunicator;
|
||||
class ProcessCommunicatorForChildProcess;
|
||||
class StdProcessCommunicator;
|
||||
class StdProcessCommunicatorForChildProcess;
|
||||
class CommunicatorHandleImpl;
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
void ProcessOutput::Clear()
|
||||
{
|
||||
outputResult.clear();
|
||||
errorResult.clear();
|
||||
}
|
||||
|
||||
bool ProcessOutput::HasOutput() const
|
||||
{
|
||||
return !outputResult.empty();
|
||||
}
|
||||
|
||||
bool ProcessOutput::HasError() const
|
||||
{
|
||||
return !errorResult.empty();
|
||||
}
|
||||
|
||||
AZ::u32 ProcessCommunicator::BlockUntilErrorAvailable(AZStd::string& readBuffer)
|
||||
{
|
||||
// block until errors can actually be read
|
||||
ReadError(readBuffer.data(), 0);
|
||||
// at this point errors can be read, return the peek amount
|
||||
return PeekError();
|
||||
}
|
||||
|
||||
AZ::u32 ProcessCommunicator::BlockUntilOutputAvailable(AZStd::string& readBuffer)
|
||||
{
|
||||
// block until output can actually be read
|
||||
ReadOutput(readBuffer.data(), 0);
|
||||
// at this point output can be read, return the peek amount
|
||||
return PeekOutput();
|
||||
}
|
||||
|
||||
void ProcessCommunicator::ReadIntoProcessOutput(ProcessOutput& processOutput)
|
||||
{
|
||||
OutputStatus status;
|
||||
char readBuffer[s_readBufferSize];
|
||||
|
||||
// read from the process until the handle is no longer valid
|
||||
while (true)
|
||||
{
|
||||
WaitForReadyOutputs(status);
|
||||
ReadFromOutputs(processOutput, status, readBuffer, s_readBufferSize);
|
||||
|
||||
if (!status.outputDeviceReady && !status.errorsDeviceReady)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessCommunicator::ReadFromOutputs(ProcessOutput& processOutput, OutputStatus& status, char* buffer, AZ::u32 bufferSize)
|
||||
{
|
||||
AZ::u32 bytesRead = 0;
|
||||
|
||||
if (status.shouldReadOutput)
|
||||
{
|
||||
// Send in the size - 1 to leave room for us to write out the 0 in
|
||||
// bytesRead position on the next line
|
||||
bytesRead = ReadOutput(buffer, bufferSize - 1);
|
||||
buffer[bytesRead] = 0;
|
||||
processOutput.outputResult.append(buffer, bytesRead);
|
||||
}
|
||||
|
||||
if (status.shouldReadErrors)
|
||||
{
|
||||
// Send in the size - 1 to leave room for us to write out the 0 in
|
||||
// bytesRead position on the next line
|
||||
bytesRead = ReadError(buffer, bufferSize - 1);
|
||||
buffer[bytesRead] = 0;
|
||||
processOutput.errorResult.append(buffer, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
AZ::u32 ProcessCommunicatorForChildProcess::BlockUntilInputAvailable(AZStd::string& readBuffer)
|
||||
{
|
||||
ReadInput(readBuffer.data(), 0);
|
||||
return PeekInput();
|
||||
}
|
||||
|
||||
StdInOutProcessCommunicator::StdInOutProcessCommunicator()
|
||||
: m_stdInWrite(new CommunicatorHandleImpl())
|
||||
, m_stdOutRead(new CommunicatorHandleImpl())
|
||||
, m_stdErrRead(new CommunicatorHandleImpl())
|
||||
{
|
||||
}
|
||||
|
||||
StdInOutProcessCommunicator::~StdInOutProcessCommunicator()
|
||||
{
|
||||
CloseAllHandles();
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicator::IsValid() const
|
||||
{
|
||||
return m_initialized && (m_stdInWrite->IsValid() || m_stdOutRead->IsValid() || m_stdErrRead->IsValid());
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicator::ReadError(void* readBuffer, AZ::u32 bufferSize)
|
||||
{
|
||||
AZ_Assert(m_stdErrRead->IsValid(), "Error read handle is invalid, unable to read error stream");
|
||||
return ReadDataFromHandle(m_stdErrRead, readBuffer, bufferSize);
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicator::PeekError()
|
||||
{
|
||||
AZ_Assert(m_stdErrRead->IsValid(), "Error read handle is invalid, unable to read error stream");
|
||||
return PeekHandle(m_stdErrRead);
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicator::ReadOutput(void* readBuffer, AZ::u32 bufferSize)
|
||||
{
|
||||
AZ_Assert(m_stdOutRead->IsValid(), "Output read handle is invalid, unable to read output stream");
|
||||
return ReadDataFromHandle(m_stdOutRead, readBuffer, bufferSize);
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicator::PeekOutput()
|
||||
{
|
||||
AZ_Assert(m_stdOutRead->IsValid(), "Output read handle is invalid, unable to read output stream");
|
||||
return PeekHandle(m_stdOutRead);
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicator::WriteInput(const void* writeBuffer, AZ::u32 bytesToWrite)
|
||||
{
|
||||
AZ_Assert(m_stdInWrite->IsValid(), "Input write handle is invalid, unable to write input stream");
|
||||
return WriteDataToHandle(m_stdInWrite, writeBuffer, bytesToWrite);
|
||||
}
|
||||
|
||||
void StdInOutProcessCommunicator::CloseAllHandles()
|
||||
{
|
||||
m_stdInWrite->Close();
|
||||
m_stdOutRead->Close();
|
||||
m_stdErrRead->Close();
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
StdInOutProcessCommunicatorForChildProcess::StdInOutProcessCommunicatorForChildProcess()
|
||||
: m_stdInRead(new CommunicatorHandleImpl())
|
||||
, m_stdOutWrite(new CommunicatorHandleImpl())
|
||||
, m_stdErrWrite(new CommunicatorHandleImpl())
|
||||
{
|
||||
}
|
||||
|
||||
StdInOutProcessCommunicatorForChildProcess::~StdInOutProcessCommunicatorForChildProcess()
|
||||
{
|
||||
CloseAllHandles();
|
||||
}
|
||||
|
||||
bool StdInOutProcessCommunicatorForChildProcess::IsValid() const
|
||||
{
|
||||
return m_initialized && (m_stdInRead->IsValid() || m_stdOutWrite->IsValid() || m_stdErrWrite->IsValid());
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicatorForChildProcess::WriteError(const void* writeBuffer, AZ::u32 bytesToWrite)
|
||||
{
|
||||
return WriteDataToHandle(m_stdErrWrite, writeBuffer, bytesToWrite);
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicatorForChildProcess::WriteOutput(const void* writeBuffer, AZ::u32 bytesToWrite)
|
||||
{
|
||||
return WriteDataToHandle(m_stdOutWrite, writeBuffer, bytesToWrite);
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicatorForChildProcess::PeekInput()
|
||||
{
|
||||
return PeekHandle(m_stdInRead);
|
||||
}
|
||||
|
||||
AZ::u32 StdInOutProcessCommunicatorForChildProcess::ReadInput(void* buffer, AZ::u32 bufferSize)
|
||||
{
|
||||
return ReadDataFromHandle(m_stdInRead, buffer, bufferSize);
|
||||
}
|
||||
|
||||
void StdInOutProcessCommunicatorForChildProcess::CloseAllHandles()
|
||||
{
|
||||
m_stdInRead->Close();
|
||||
m_stdOutWrite->Close();
|
||||
m_stdErrWrite->Close();
|
||||
m_initialized = false;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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/base.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzFramework/Process/ProcessCommon_fwd.h>
|
||||
#include <AzFramework/AzFramework_Traits_Platform.h>
|
||||
|
||||
#if AZ_TRAIT_AZFRAMEWORK_PROCESSLAUNCH_DEFAULT
|
||||
#include <Default/AzFramework/Process/ProcessCommon_Default.h>
|
||||
#else
|
||||
#include <AzFramework/Process/ProcessCommon.h>
|
||||
#endif
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class ProcessOutput
|
||||
{
|
||||
public:
|
||||
AZStd::string outputResult;
|
||||
AZStd::string errorResult;
|
||||
|
||||
void Clear();
|
||||
bool HasOutput() const;
|
||||
bool HasError() const;
|
||||
};
|
||||
|
||||
class ProcessCommunicator
|
||||
{
|
||||
public:
|
||||
|
||||
struct OutputStatus
|
||||
{
|
||||
bool outputDeviceReady = false;
|
||||
bool errorsDeviceReady = false;
|
||||
bool shouldReadOutput = false;
|
||||
bool shouldReadErrors = false;
|
||||
};
|
||||
|
||||
ProcessCommunicator() = default;
|
||||
virtual ~ProcessCommunicator() = default;
|
||||
|
||||
// Check if communicator is in a valid state
|
||||
virtual bool IsValid() const = 0;
|
||||
|
||||
// Read error data into a given buffer size (returns amount of data read)
|
||||
// Blocking call (until child process writes data)
|
||||
virtual AZ::u32 ReadError(void* readBuffer, AZ::u32 bufferSize) = 0;
|
||||
|
||||
// Peek if error data is ready to be read (returns amount of data available to read)
|
||||
// Non-blocking call
|
||||
virtual AZ::u32 PeekError() = 0;
|
||||
|
||||
// Read output data into a given buffer size (returns amount of data read)
|
||||
// Blocking call (until child process writes data)
|
||||
virtual AZ::u32 ReadOutput(void* readBuffer, AZ::u32 bufferSize) = 0;
|
||||
|
||||
// Peek if output data is ready to be read (returns amount of data available to read)
|
||||
// Non-blocking call
|
||||
virtual AZ::u32 PeekOutput() = 0;
|
||||
|
||||
// Write input data to child process (returns amount of data sent)
|
||||
// Blocking call (until child process reads data)
|
||||
virtual AZ::u32 WriteInput(const void* writeBuffer, AZ::u32 bytesToWrite) = 0;
|
||||
|
||||
// Waits for errors to be ready to read
|
||||
// Blocking call (until child process writes errors)
|
||||
AZ::u32 BlockUntilErrorAvailable(AZStd::string& readBuffer);
|
||||
|
||||
// Waits for output to be ready to read
|
||||
// Blocking call (until child process writes output)
|
||||
AZ::u32 BlockUntilOutputAvailable(AZStd::string& readBuffer);
|
||||
|
||||
// Reads into process output until the communicator's output handles are no longer valid
|
||||
void ReadIntoProcessOutput(ProcessOutput& processOutput);
|
||||
|
||||
protected:
|
||||
AZ_DISABLE_COPY(ProcessCommunicator);
|
||||
|
||||
// Waits for output or error to be ready for reading
|
||||
virtual void WaitForReadyOutputs(OutputStatus& outputStatus) const = 0;
|
||||
|
||||
void ReadFromOutputs(ProcessOutput& processOutput,
|
||||
OutputStatus& status, char* buffer, AZ::u32 bufferSize);
|
||||
|
||||
private:
|
||||
static const size_t s_readBufferSize = 16 * 1024;
|
||||
};
|
||||
|
||||
class ProcessCommunicatorForChildProcess
|
||||
{
|
||||
public:
|
||||
|
||||
ProcessCommunicatorForChildProcess() = default;
|
||||
virtual ~ProcessCommunicatorForChildProcess() = default;
|
||||
|
||||
// Check if communicator is in a valid state
|
||||
virtual bool IsValid() const = 0;
|
||||
|
||||
// Write error data to parent process (returns amount of data sent)
|
||||
// Blocking call (until parent process reads data)
|
||||
virtual AZ::u32 WriteError(const void* writeBuffer, AZ::u32 bytesToWrite) = 0;
|
||||
|
||||
// Write output data to parent process (returns amount of data sent)
|
||||
// Blocking call (until parent process reads data)
|
||||
virtual AZ::u32 WriteOutput(const void* writeBuffer, AZ::u32 bytesToWrite) = 0;
|
||||
|
||||
// Peek if input data is ready to be read (returns amount of data available to read)
|
||||
// Non-blocking call
|
||||
virtual AZ::u32 PeekInput() = 0;
|
||||
|
||||
// Read input data into a given buffer size (returns amount of data read)
|
||||
// Blocking call (until parent process writes data)
|
||||
virtual AZ::u32 ReadInput(void* readBuffer, AZ::u32 bufferSize) = 0;
|
||||
|
||||
// Waits for input to be ready to read
|
||||
// Blocking call (until parent process writes errors)
|
||||
AZ::u32 BlockUntilInputAvailable(AZStd::string& readBuffer);
|
||||
|
||||
protected:
|
||||
AZ_DISABLE_COPY(ProcessCommunicatorForChildProcess);
|
||||
};
|
||||
|
||||
using StdProcessCommunicatorHandle = AZStd::unique_ptr<CommunicatorHandleImpl>;
|
||||
|
||||
class StdInOutCommunication
|
||||
{
|
||||
public:
|
||||
virtual ~StdInOutCommunication() = default;
|
||||
|
||||
protected:
|
||||
AZ::u32 PeekHandle(StdProcessCommunicatorHandle& handle);
|
||||
AZ::u32 ReadDataFromHandle(StdProcessCommunicatorHandle& handle, void* readBuffer, AZ::u32 bufferSize);
|
||||
AZ::u32 WriteDataToHandle(StdProcessCommunicatorHandle& handle, const void* writeBuffer, AZ::u32 bytesToWrite);
|
||||
};
|
||||
|
||||
class StdProcessCommunicator
|
||||
: public ProcessCommunicator
|
||||
{
|
||||
public:
|
||||
virtual bool CreatePipesForProcess(AzFramework::ProcessData* processData) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Communicator to talk to processes via std::in and std::out
|
||||
*
|
||||
* to do this, it must provide handles for the child process to
|
||||
* inherit before process creation
|
||||
*/
|
||||
class StdInOutProcessCommunicator
|
||||
: public StdProcessCommunicator
|
||||
, public StdInOutCommunication
|
||||
{
|
||||
public:
|
||||
StdInOutProcessCommunicator();
|
||||
~StdInOutProcessCommunicator();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzFramework::ProcessCommunicator overrides
|
||||
bool IsValid() const override;
|
||||
AZ::u32 ReadError(void* readBuffer, AZ::u32 bufferSize) override;
|
||||
AZ::u32 PeekError() override;
|
||||
AZ::u32 ReadOutput(void* readBuffer, AZ::u32 bufferSize) override;
|
||||
AZ::u32 PeekOutput() override;
|
||||
AZ::u32 WriteInput(const void* writeBuffer, AZ::u32 bytesToWrite) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzFramework::StdProcessCommunicator overrides
|
||||
bool CreatePipesForProcess(ProcessData* processData) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected:
|
||||
void CreateHandles();
|
||||
void CloseAllHandles();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzFramework::ProcessCommunicator overrides
|
||||
void WaitForReadyOutputs(OutputStatus& outputStatus) const override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AZStd::unique_ptr<CommunicatorHandleImpl> m_stdInWrite;
|
||||
AZStd::unique_ptr<CommunicatorHandleImpl> m_stdOutRead;
|
||||
AZStd::unique_ptr<CommunicatorHandleImpl> m_stdErrRead;
|
||||
bool m_initialized = false;
|
||||
};
|
||||
|
||||
class StdProcessCommunicatorForChildProcess
|
||||
: public ProcessCommunicatorForChildProcess
|
||||
{
|
||||
public:
|
||||
virtual bool AttachToExistingPipes() = 0;
|
||||
};
|
||||
|
||||
class StdInOutProcessCommunicatorForChildProcess
|
||||
: public StdProcessCommunicatorForChildProcess
|
||||
, public StdInOutCommunication
|
||||
{
|
||||
public:
|
||||
StdInOutProcessCommunicatorForChildProcess();
|
||||
~StdInOutProcessCommunicatorForChildProcess();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzFramework::ProcessCommunicatorForChildProcess overrides
|
||||
bool IsValid() const override;
|
||||
AZ::u32 WriteError(const void* writeBuffer, AZ::u32 bytesToWrite) override;
|
||||
AZ::u32 WriteOutput(const void* writeBuffer, AZ::u32 bytesToWrite) override;
|
||||
AZ::u32 PeekInput() override;
|
||||
AZ::u32 ReadInput(void* buffer, AZ::u32 bufferSize) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzFramework::StdProcessCommunicatorForChildProcess overrides
|
||||
bool AttachToExistingPipes() override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected:
|
||||
void CreateHandles();
|
||||
void CloseAllHandles();
|
||||
|
||||
StdProcessCommunicatorHandle m_stdInRead;
|
||||
StdProcessCommunicatorHandle m_stdOutWrite;
|
||||
StdProcessCommunicatorHandle m_stdErrWrite;
|
||||
bool m_initialized = false;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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/smart_ptr/scoped_ptr.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
bool ProcessWatcher::LaunchProcessAndRetrieveOutput(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType, AzFramework::ProcessOutput& outProcessOutput)
|
||||
{
|
||||
// launch the process
|
||||
|
||||
AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType));
|
||||
if (!pWatcher)
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the communicator and ensure it is valid
|
||||
ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
|
||||
if (!pCommunicator || !pCommunicator->IsValid())
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
pCommunicator->ReadIntoProcessOutput(outProcessOutput);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ProcessWatcher::SpawnProcess(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType)
|
||||
{
|
||||
InitProcessData(communicationType == COMMUNICATOR_TYPE_STDINOUT);
|
||||
|
||||
if (communicationType == COMMUNICATOR_TYPE_STDINOUT)
|
||||
{
|
||||
StdProcessCommunicator* pStdCommunicator = CreateStdCommunicator();
|
||||
if (pStdCommunicator->CreatePipesForProcess(m_pWatcherData.get()))
|
||||
{
|
||||
m_pCommunicator = pStdCommunicator;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Communicator failure, just clean it up
|
||||
delete pStdCommunicator;
|
||||
}
|
||||
}
|
||||
else if (communicationType == COMMUNICATOR_TYPE_NONE)
|
||||
{
|
||||
//Implemented, but don't do anything.
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "communicationType %d not implemented", communicationType);
|
||||
}
|
||||
|
||||
return ProcessLauncher::LaunchProcess(processLaunchInfo, *m_pWatcherData);
|
||||
}
|
||||
|
||||
class ProcessCommunicator* ProcessWatcher::GetCommunicator()
|
||||
{
|
||||
return m_pCommunicator;
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<ProcessCommunicatorForChildProcess> ProcessWatcher::GetCommunicatorForChildProcess(ProcessCommunicationType communicationType)
|
||||
{
|
||||
if (communicationType == COMMUNICATOR_TYPE_STDINOUT)
|
||||
{
|
||||
StdProcessCommunicatorForChildProcess* communicator = CreateStdCommunicatorForChildProcess();
|
||||
if (!communicator->AttachToExistingPipes())
|
||||
{
|
||||
// Delete the communicator if attaching fails, it is useless
|
||||
delete communicator;
|
||||
communicator = nullptr;
|
||||
}
|
||||
return AZStd::shared_ptr<ProcessCommunicatorForChildProcess>{
|
||||
communicator
|
||||
};
|
||||
}
|
||||
else if (communicationType == COMMUNICATOR_TYPE_NONE)
|
||||
{
|
||||
AZ_Assert(false, "No communicator for communicationType %d", communicationType);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "communicationType %d not implemented", communicationType);
|
||||
}
|
||||
return AZStd::shared_ptr<ProcessCommunicatorForChildProcess>{};
|
||||
}
|
||||
} // AzFramework
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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/base.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzFramework/Process/ProcessCommon_fwd.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProcessLauncher
|
||||
{
|
||||
enum ProcessLaunchResult : AZ::u32
|
||||
{
|
||||
PLR_Success, // Process Launched Normally
|
||||
PLR_MissingFile, // Missing file or command
|
||||
};
|
||||
|
||||
struct ProcessLaunchInfo
|
||||
{
|
||||
//! This is the process to execute. Do not escape spaces here.
|
||||
AZStd::string m_processExecutableString;
|
||||
|
||||
/**
|
||||
* Command line parameters, concatenated.
|
||||
* In order to prevent a proliferation of ifdefs all over client code to convert into various standards,
|
||||
* instead we assume windows style use of "double quotes" to escape spaces
|
||||
* for example: params="hello world" "/Users/JOE SMITH/Desktop"
|
||||
* On windows, the command line will be passed as-is to the shell (with quotes)
|
||||
* on UNIX/OSX, the command line will be converted as appropriate (quotes removed, but used to chop up parameters)
|
||||
*/
|
||||
AZStd::string m_commandlineParameters;
|
||||
|
||||
/**
|
||||
* (optional) If you specify a working directory, the command will be executed with that directory as the current directory.
|
||||
* Do not use quotes around the working directory string.
|
||||
*/
|
||||
AZStd::string m_workingDirectory;
|
||||
ProcessPriority m_processPriority = PROCESSPRIORITY_NORMAL;
|
||||
AZStd::vector<AZStd::string>* m_environmentVariables = nullptr;
|
||||
mutable ProcessLaunchResult m_launchResult = PLR_Success;
|
||||
|
||||
//Not Supported On Mac
|
||||
bool m_showWindow = true;
|
||||
};
|
||||
|
||||
static const AZ::u32 INFINITE_TIMEOUT = (AZ::u32) -1;
|
||||
bool LaunchProcess(const ProcessLaunchInfo& processLaunchInfo, ProcessData& processData);
|
||||
bool LaunchUnwatchedProcess(const ProcessLaunchInfo& processLaunchInfo);
|
||||
} // namespace ProccessLauncher
|
||||
|
||||
class ProcessWatcher
|
||||
{
|
||||
public:
|
||||
// Use LaunchProcess to launch a child process at a given path with a commandline and communication type, optional environment variables (null means inherit from parent environment)
|
||||
static ProcessWatcher* LaunchProcess(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType);
|
||||
|
||||
// Use LaunchProcessAndRetrieveOutput to launch a process via LaunchProcess and return its output, as of now used for fire-and-forget executables (exe's that do something and close immediately)
|
||||
static bool LaunchProcessAndRetrieveOutput(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType, AzFramework::ProcessOutput& outProcessOutput);
|
||||
|
||||
// GetCommunicatorForChildProcess is used when you are implementing the given child process and want a better interface than std::in/std::out (not required)
|
||||
static AZStd::shared_ptr<class ProcessCommunicatorForChildProcess> GetCommunicatorForChildProcess(ProcessCommunicationType communicationType);
|
||||
|
||||
// GetCommunicator returns a ProcessCommunicator to communicate with the child process
|
||||
ProcessCommunicator* GetCommunicator();
|
||||
|
||||
// Check if child process is running, outExitCode returns exit code if process terminated
|
||||
bool IsProcessRunning(AZ::u32* outExitCode = nullptr);
|
||||
|
||||
// Wait for process to exit, waitTime is in seconds, returns true if process exited, false if still running
|
||||
bool WaitForProcessToExit(AZ::u32 waitTimeInSeconds, AZ::u32* outExitCode = nullptr);
|
||||
|
||||
// Terminate child process with a given exit code (if still running)
|
||||
void TerminateProcess(AZ::u32 exitCode);
|
||||
|
||||
// Delete ProcessWatcher when done with child process
|
||||
virtual ~ProcessWatcher();
|
||||
protected:
|
||||
bool SpawnProcess(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType);
|
||||
|
||||
private:
|
||||
|
||||
StdProcessCommunicator* CreateStdCommunicator();
|
||||
static StdProcessCommunicatorForChildProcess* CreateStdCommunicatorForChildProcess();
|
||||
|
||||
void InitProcessData(bool stdCommunication);
|
||||
|
||||
ProcessWatcher();
|
||||
|
||||
ProcessWatcher(const ProcessWatcher&) = delete;
|
||||
ProcessWatcher& operator= (const ProcessWatcher&) = delete;
|
||||
|
||||
AZStd::unique_ptr<ProcessData> m_pWatcherData;
|
||||
ProcessCommunicator* m_pCommunicator;
|
||||
ProcessCommunicator* m_pChildCommunicator;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@@ -11,65 +11,29 @@
|
||||
*/
|
||||
|
||||
#include <AzFramework/ProjectManager/ProjectManager.h>
|
||||
#include <AzFramework/Engine/Engine.h>
|
||||
|
||||
#include <AzCore/Platform.h>
|
||||
#include <AzCore/Settings/CommandLine.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <AzFramework/AzFramework_Traits_Platform.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/Engine/Engine.h>
|
||||
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
namespace ProjectManager
|
||||
{
|
||||
// Check if any project path appears to have been provided on the command line
|
||||
bool HasCommandLineProjectName(const int argc, char* argv[])
|
||||
{
|
||||
constexpr int numOptionPrefixes = 3;
|
||||
static const char* optionPrefixes[numOptionPrefixes] = { "/", "--", "-" };
|
||||
constexpr int numOptionNames = 2;
|
||||
static const char* optionNames[numOptionNames] = { "projectpath", R"(regset="/Amazon/AzCore/Bootstrap/sys_game_folder)" };
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
int thisPrefix = 0;
|
||||
for (; thisPrefix < numOptionPrefixes; ++thisPrefix)
|
||||
{
|
||||
if (strncmp(argv[i], optionPrefixes[thisPrefix], strlen(optionPrefixes[thisPrefix])) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If the argument doesn't start with any of our switch start parameters, this isn't an argument giving us a project
|
||||
if (thisPrefix == numOptionPrefixes)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// We compare the portion of the string after our prefix
|
||||
int startIndex = strlen(optionPrefixes[thisPrefix]);
|
||||
// If the whole argument was just one of the prefixes, this also isn't what we were looking for
|
||||
if (startIndex == strlen(argv[i]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int switchNum = 0;
|
||||
for (; switchNum < numOptionNames; ++switchNum)
|
||||
{
|
||||
// Start the string comparison at startIndex for each string - after the option indicator
|
||||
if (azstrnicmp(&argv[i][startIndex], optionNames[switchNum], strlen(optionNames[switchNum])) == 0)
|
||||
{
|
||||
int expectedOptionLength = strlen(optionNames[switchNum]) + startIndex;
|
||||
// The option is what we're looking for if it had a space after it (it was the whole argument) or it has an equals next
|
||||
if (strlen(argv[i]) == (expectedOptionLength) || ((strlen(argv[i]) > expectedOptionLength ) && argv[i][expectedOptionLength] == '='))
|
||||
{
|
||||
// We found one of the acceptable arguments
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Check for a project name, if not found, attempt to launch project manager and shut down
|
||||
ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[])
|
||||
{
|
||||
// If we were able to locate a path to a project, we're done
|
||||
if (HasProjectName(argc, argv))
|
||||
if (HasProjectPath(argc, argv))
|
||||
{
|
||||
return ProjectPathCheckResult::ProjectPathFound;
|
||||
}
|
||||
@@ -83,142 +47,88 @@ namespace AzFramework
|
||||
return ProjectPathCheckResult::ProjectManagerLaunchFailed;
|
||||
}
|
||||
|
||||
bool HasProjectPath(const int argc, char* argv[])
|
||||
{
|
||||
bool hasProjectPath = false;
|
||||
bool ownsAllocator = false;
|
||||
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
|
||||
ownsAllocator = true;
|
||||
}
|
||||
{
|
||||
AZ::CommandLine commandLine;
|
||||
commandLine.Parse(argc, argv);
|
||||
AZ::SettingsRegistryImpl settingsRegistry;
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry);
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(settingsRegistry, commandLine, false);
|
||||
auto sysGameFolderKey = AZ::SettingsRegistryInterface::FixedValueString(
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
AZ::IO::FixedMaxPath registryPath;
|
||||
hasProjectPath = settingsRegistry.Get(registryPath.Native(), sysGameFolderKey);
|
||||
}
|
||||
if (ownsAllocator)
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
|
||||
}
|
||||
return hasProjectPath;
|
||||
}
|
||||
|
||||
bool LaunchProjectManager()
|
||||
{
|
||||
bool launchSuccess = false;
|
||||
#if (AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER)
|
||||
bool ownsSystemAllocator = false;
|
||||
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
|
||||
{
|
||||
ownsSystemAllocator = true;
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
|
||||
}
|
||||
{
|
||||
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_Error("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_Error("ProjectManager", false, "%s not found at %s!", projectsScript, projectManagerPath.c_str());
|
||||
return false;
|
||||
}
|
||||
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<8> 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";
|
||||
pythonPath /= AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL;
|
||||
auto cmdPath = AZ::IO::FixedMaxPathString::format("%s %s%s --executable_path=%s --parent_pid=%" PRId64, pythonPath.Native().c_str(),
|
||||
debugOption.c_str(), (projectManagerPath / projectsScript).c_str(), executablePath, AZ::Platform::GetCurrentProcessId());
|
||||
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
|
||||
processLaunchInfo.m_commandlineParameters = cmdPath;
|
||||
processLaunchInfo.m_showWindow = false;
|
||||
launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
|
||||
}
|
||||
if(ownsSystemAllocator)
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
|
||||
}
|
||||
#endif // #if defined(AZ_FRAMEWORK_USE_PROJECT_MANAGER)
|
||||
return launchSuccess;
|
||||
}
|
||||
} // ProjectManager
|
||||
|
||||
bool ProjectManager::HasProjectName(const int argc, char* argv[])
|
||||
{
|
||||
return HasCommandLineProjectName(argc, argv) || HasBootstrapProjectName();
|
||||
}
|
||||
|
||||
// This is a transition method until all projects/tests/etc have been converted to expect and use the projectpath parameter
|
||||
// If bootstrap.cfg exists and has a valid project name we should assume we're launching using it
|
||||
// After that time it can be removed
|
||||
bool ProjectManager::HasBootstrapProjectName(AZStd::string_view projectFolder)
|
||||
{
|
||||
AZ::IO::FixedMaxPath enginePath = Engine::FindEngineRoot(projectFolder);
|
||||
if (enginePath.empty())
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "Couldn't find engine root");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto bootstrapPath = enginePath / "bootstrap.cfg";
|
||||
|
||||
if (!AZ::IO::SystemFile::Exists(bootstrapPath.c_str()))
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "No bootstrap file found at %s", bootstrapPath.c_str());
|
||||
return false;
|
||||
}
|
||||
AZStd::fixed_string< MaxBootstrapFileSize> bootstrapString;
|
||||
auto fileSize = AZ::IO::SystemFile::Length(bootstrapPath.c_str());
|
||||
if (fileSize >= MaxBootstrapFileSize)
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "Bootstrap read size at %s is %zu", bootstrapPath.c_str(), fileSize);
|
||||
bootstrapString.resize_no_construct(MaxBootstrapFileSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
bootstrapString.resize_no_construct(fileSize);
|
||||
}
|
||||
AZ::IO::SystemFile::SizeType bytesRead = AZ::IO::SystemFile::Read(bootstrapPath.c_str(), bootstrapString.data(), MaxBootstrapFileSize - 1);
|
||||
if (bytesRead == (MaxBootstrapFileSize - 1))
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "Bootstrap read size at %s was %zu", bootstrapPath.c_str(), bytesRead);
|
||||
}
|
||||
if (!ContentHasProjectName(bootstrapString))
|
||||
{
|
||||
AZ_TracePrintf("ProjectManager", "Bootstrap at %s did not contain project name", bootstrapPath.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is a transition method until all projects/tests/etc have been converted to expect and use the projectpath parameter
|
||||
// If bootstrap.cfg exists and has a valid project name we should assume we're launching using it
|
||||
// After that time it can be removed
|
||||
bool ProjectManager::ContentHasProjectName(AZStd::fixed_string< MaxBootstrapFileSize>& bootstrapString)
|
||||
{
|
||||
static const char* const projectKey = "sys_game_folder";
|
||||
size_t searchStart = bootstrapString.find(projectKey);
|
||||
while (searchStart != bootstrapString.npos)
|
||||
{
|
||||
// Once we've found the key we need to search the line forward and backwards. Commented out lines shouldn't count
|
||||
// and if there's no value after the equals then it's also not set
|
||||
auto checkPos = searchStart;
|
||||
// We're at the start already if this is position 0
|
||||
bool foundLineStart = checkPos == 0;
|
||||
if (checkPos)
|
||||
{
|
||||
--checkPos;
|
||||
}
|
||||
while (checkPos > 0 && bootstrapString[checkPos] != '-')
|
||||
{
|
||||
if (bootstrapString[checkPos] == '\n')
|
||||
{
|
||||
// Looks like a valid key
|
||||
foundLineStart = true;
|
||||
break;
|
||||
}
|
||||
if (!std::isspace(bootstrapString[checkPos]))
|
||||
{
|
||||
// This appears to be some other character appearing before our key, this isn't valid
|
||||
break;
|
||||
}
|
||||
--checkPos;
|
||||
}
|
||||
if (!foundLineStart)
|
||||
{
|
||||
// Commented line or other content preceding our key, keep searching
|
||||
searchStart = bootstrapString.find(projectKey, searchStart + 1);
|
||||
continue;
|
||||
}
|
||||
checkPos = searchStart + strlen(projectKey);
|
||||
bool foundEquals = false;
|
||||
while (checkPos < bootstrapString.length())
|
||||
{
|
||||
if (bootstrapString[checkPos] == '\n')
|
||||
{
|
||||
// We've reached the end of the line and didn't find anything that seems to be a value for our key
|
||||
break;
|
||||
}
|
||||
if (std::isspace(bootstrapString[checkPos]))
|
||||
{
|
||||
// Whitespace - keep searching back
|
||||
++checkPos;
|
||||
continue;
|
||||
}
|
||||
if (bootstrapString[checkPos] == '=')
|
||||
{
|
||||
foundEquals = true;
|
||||
++checkPos;
|
||||
continue;
|
||||
}
|
||||
if (foundEquals)
|
||||
{
|
||||
auto nameEnd = bootstrapString.find_first_of(" \n", checkPos);
|
||||
if (nameEnd == bootstrapString.npos)
|
||||
{
|
||||
// End of content, this is valid
|
||||
nameEnd = bootstrapString.length();
|
||||
}
|
||||
constexpr size_t nameMax = 100;
|
||||
if (nameEnd - checkPos > nameMax)
|
||||
{
|
||||
AZ_Warning("ProjectManager", false, "Project name exceeded %zu characters (%zu)", nameMax, nameEnd - checkPos);
|
||||
return false;
|
||||
}
|
||||
AZStd::fixed_string<nameMax + 1> projectName(&bootstrapString[checkPos], nameEnd - checkPos);
|
||||
AZ_TracePrintf("ProjectManager", "Found project name of %s", projectName.c_str());
|
||||
// This is not a space, we've found our key, and we've found some sort of non space entry, we count this as "it looks like we have a value entered"
|
||||
return true;
|
||||
}
|
||||
// there was some other content on this line after our key before the equals that was not a space, this isn't our key
|
||||
searchStart = bootstrapString.find(projectKey, searchStart + 1);
|
||||
break;
|
||||
}
|
||||
searchStart = bootstrapString.find(projectKey, searchStart + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // AzFramework
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace AzFramework
|
||||
constexpr AZ::IO::SystemFile::SizeType MaxBootstrapFileSize = 1024 * 10;
|
||||
|
||||
// Check if any project name can be found anywhere
|
||||
bool HasProjectName(const int argc, char* argv[]);
|
||||
bool HasProjectPath(const int argc, char* argv[]);
|
||||
// Check if any project name can be found on the command line
|
||||
bool HasCommandLineProjectName(const int argc, char* argv[]);
|
||||
// Check if a relative project is being used through bootstrap
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace AzFramework
|
||||
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<SpawnableSystemComponent, AZ::Component>();
|
||||
serializeContext->RegisterGenericType<AZ::Data::Asset<Spawnable>>();
|
||||
}
|
||||
}
|
||||
@@ -62,7 +63,7 @@ namespace AzFramework
|
||||
else
|
||||
{
|
||||
AZ_Warning("Spawnables", false, "No root spawnable assigned or root spawanble couldnt' be loaded.\n"
|
||||
"The root spawnable can be assigned in the Settings Registry under the key '$s'.\n", RootSpawnableRegistryKey);
|
||||
"The root spawnable can be assigned in the Settings Registry under the key '%s'.\n", RootSpawnableRegistryKey);
|
||||
}
|
||||
|
||||
m_rootSpawnableInitialized = true;
|
||||
|
||||
@@ -145,6 +145,8 @@ set(FILES
|
||||
Components/NonUniformScaleComponent.cpp
|
||||
FileFunc/FileFunc.h
|
||||
FileFunc/FileFunc.cpp
|
||||
Gem/GemInfo.cpp
|
||||
Gem/GemInfo.h
|
||||
StringFunc/StringFunc.h
|
||||
InGameUI/UiFrameworkBus.h
|
||||
IO/LocalFileIO.cpp
|
||||
@@ -267,6 +269,11 @@ set(FILES
|
||||
Physics/WorldEventhandler.h
|
||||
Physics/ScriptCanvasPhysicsUtils.h
|
||||
Physics/ScriptCanvasPhysicsUtils.cpp
|
||||
Process/ProcessCommunicator.cpp
|
||||
Process/ProcessCommunicator.h
|
||||
Process/ProcessWatcher.cpp
|
||||
Process/ProcessWatcher.h
|
||||
Process/ProcessCommon_fwd.h
|
||||
ProjectManager/ProjectManager.h
|
||||
ProjectManager/ProjectManager.cpp
|
||||
Render/GameIntersectorComponent.h
|
||||
|
||||
@@ -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