Integrating latest from github/staging
Integrating up through commit 5e1bdae
This commit is contained in:
@@ -290,15 +290,15 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
// Initialize the engine config object based on the current
|
||||
bool Initialize(const char* currentAppRoot)
|
||||
bool Initialize(const char* currentEngineRoot)
|
||||
{
|
||||
// Start with the app root as the engine root (legacy), but check to see if the engine root
|
||||
// is external to the app root
|
||||
azstrncpy(m_engineRoot, AZ_ARRAY_SIZE(m_engineRoot), currentAppRoot, strlen(currentAppRoot) + 1);
|
||||
azstrncpy(m_engineRoot, AZ_ARRAY_SIZE(m_engineRoot), currentEngineRoot, strlen(currentEngineRoot) + 1);
|
||||
|
||||
// From the appRoot, check and see if we can read any external engine reference in engine.json
|
||||
QString engineJsonFileName = QString(m_fileName);
|
||||
QString engineJsonFilePath = QDir(currentAppRoot).absoluteFilePath(engineJsonFileName);
|
||||
QString engineJsonFilePath = QDir(currentEngineRoot).absoluteFilePath(engineJsonFileName);
|
||||
|
||||
// From the appRoot, check and see if we can read any external engine reference in engine.json
|
||||
if (!QFile::exists(engineJsonFilePath))
|
||||
@@ -308,7 +308,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
if (!ReadEngineConfigIntoMap(engineJsonFilePath, m_engineConfigMap))
|
||||
{
|
||||
AZ_Warning(m_logWindow, false, "Defaulting root engine path to '%s'", currentAppRoot);
|
||||
AZ_Warning(m_logWindow, false, "Defaulting root engine path to '%s'", currentEngineRoot);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -397,9 +397,9 @@ namespace AzToolsFramework
|
||||
|
||||
void ToolsApplication::InitializeEngineConfig()
|
||||
{
|
||||
if (!m_engineConfigImpl->Initialize(GetAppRoot()))
|
||||
if (!m_engineConfigImpl->Initialize(GetEngineRoot()))
|
||||
{
|
||||
AZ_Warning(AzToolsFramework::Internal::s_startupLogWindow, false, "Defaulting engine root path to '%s'", GetAppRoot());
|
||||
AZ_Warning(AzToolsFramework::Internal::s_startupLogWindow, false, "Defaulting engine root path to '%s'", GetEngineRoot());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
#include <AzToolsFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
@@ -46,7 +46,7 @@ namespace AzToolsFramework
|
||||
class ConsoleEchoCommunicator
|
||||
{
|
||||
public:
|
||||
ConsoleEchoCommunicator(AzToolsFramework::ProcessCommunicator* communicator)
|
||||
ConsoleEchoCommunicator(AzFramework::ProcessCommunicator* communicator)
|
||||
: m_communicator(communicator)
|
||||
{
|
||||
}
|
||||
@@ -103,7 +103,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
AzToolsFramework::ProcessCommunicator* m_communicator = nullptr;
|
||||
AzFramework::ProcessCommunicator* m_communicator = nullptr;
|
||||
};
|
||||
|
||||
void ArchiveComponent::Activate()
|
||||
@@ -372,7 +372,7 @@ namespace AzToolsFramework
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
ProcessLauncher::ProcessLaunchInfo info;
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo info;
|
||||
info.m_commandlineParameters = exePath + " " + commandLineArgs;
|
||||
|
||||
info.m_showWindow = false;
|
||||
@@ -380,7 +380,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
info.m_workingDirectory = workingDir;
|
||||
}
|
||||
AZStd::unique_ptr<ProcessWatcher> watcher(ProcessWatcher::LaunchProcess(info, ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT));
|
||||
AZStd::unique_ptr<AzFramework::ProcessWatcher> watcher(AzFramework::ProcessWatcher::LaunchProcess(info, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT));
|
||||
|
||||
AZStd::string consoleOutput;
|
||||
AZ::u32 exitCode = static_cast<AZ::u32>(SevenZipExitCode::UserStoppedProcess);
|
||||
|
||||
@@ -17,444 +17,313 @@
|
||||
#include <AzCore/Module/ModuleManagerBus.h>
|
||||
#include <AzCore/Module/DynamicModuleHandle.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
||||
#include <AzToolsFramework/Asset/AssetUtils.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/JSON/error/en.h>
|
||||
AZ_PUSH_DISABLE_WARNING(4127 4251 4800, "-Wunknown-warning-option")
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
#include <QDirIterator>
|
||||
AZ_POP_DISABLE_WARNING
|
||||
#include <QString>
|
||||
|
||||
|
||||
namespace AzToolsFramework
|
||||
namespace AzToolsFramework::AssetUtils::Internal
|
||||
{
|
||||
namespace AssetUtils
|
||||
constexpr const char* AssetProcessorSettingsKey{ "/Amazon/AssetProcessor/Settings" };
|
||||
|
||||
constexpr const char* AssetConfigPlatformDir = "AssetProcessorConfig";
|
||||
constexpr const char* RestrictedPlatformDir = "restricted";
|
||||
|
||||
AZStd::vector<AZ::IO::Path> FindWildcardMatches(AZStd::string_view sourceFolder, AZStd::string_view relativeName)
|
||||
{
|
||||
namespace Internal
|
||||
if (relativeName.empty())
|
||||
{
|
||||
|
||||
const char AssetConfigPlatformDir[] = "AssetProcessorConfig";
|
||||
const char RestrictedPlatformDir[] = "restricted";
|
||||
|
||||
QStringList FindWildcardMatches(const QString& sourceFolder, QString relativeName)
|
||||
{
|
||||
if (relativeName.isEmpty())
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
const int pathLen = sourceFolder.length() + 1;
|
||||
|
||||
relativeName.replace('\\', '/');
|
||||
|
||||
QStringList returnList;
|
||||
QRegExp nameMatch{ relativeName, Qt::CaseInsensitive, QRegExp::Wildcard };
|
||||
QDirIterator diretoryIterator(sourceFolder, QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
|
||||
QStringList files;
|
||||
while (diretoryIterator.hasNext())
|
||||
{
|
||||
diretoryIterator.next();
|
||||
if (!diretoryIterator.fileInfo().isFile())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QString pathMatch{ diretoryIterator.filePath().mid(pathLen) };
|
||||
if (nameMatch.exactMatch(pathMatch))
|
||||
{
|
||||
returnList.append(diretoryIterator.filePath());
|
||||
}
|
||||
}
|
||||
return returnList;
|
||||
}
|
||||
|
||||
void ReadPlatformInfosFromConfigFile(QString configFile, QStringList& enabledPlatforms)
|
||||
{
|
||||
// in the inifile the platform can be missing (commented out)
|
||||
// in which case it is disabled implicitly by not being there
|
||||
// or it can be 'disabled' which means that it is explicitly disabled.
|
||||
// or it can be 'enabled' which means that it is explicitly enabled.
|
||||
|
||||
if (!QFile::exists(configFile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QSettings loader(configFile, QSettings::IniFormat);
|
||||
|
||||
// Read in enabled platforms
|
||||
loader.beginGroup("Platforms");
|
||||
QStringList keys = loader.allKeys();
|
||||
for (int idx = 0; idx < keys.count(); idx++)
|
||||
{
|
||||
QString val = loader.value(keys[idx]).toString();
|
||||
QString platform = keys[idx].toLower().trimmed();
|
||||
|
||||
val = val.toLower().trimmed();
|
||||
|
||||
if (val == "enabled")
|
||||
{
|
||||
if (!enabledPlatforms.contains(val))
|
||||
{
|
||||
enabledPlatforms.push_back(platform);
|
||||
}
|
||||
}
|
||||
else if (val == "disabled")
|
||||
{
|
||||
// disable platform explicitly.
|
||||
int index = enabledPlatforms.indexOf(platform);
|
||||
if (index != -1)
|
||||
{
|
||||
enabledPlatforms.removeAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
loader.endGroup();
|
||||
}
|
||||
|
||||
void AddGemConfigFiles(const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, QStringList& configFiles)
|
||||
{
|
||||
// there can only be one gam gem per project, so if we find one, we cache the name of it so that
|
||||
// later we can add it to the very end of the list, giving it the ability to override all other config files.
|
||||
QString gameConfigPath;
|
||||
|
||||
for (const GemInfo& gemElement : gemInfoList)
|
||||
{
|
||||
QString gemAbsolutePath(gemElement.m_absoluteFilePath.c_str());
|
||||
|
||||
QDir gemDir(gemAbsolutePath);
|
||||
QString absPathToConfigFile = gemDir.absoluteFilePath("AssetProcessorGemConfig.ini");
|
||||
if (gemElement.m_isGameGem)
|
||||
{
|
||||
gameConfigPath = absPathToConfigFile;
|
||||
}
|
||||
else
|
||||
{
|
||||
configFiles.push_back(absPathToConfigFile);
|
||||
}
|
||||
}
|
||||
|
||||
// if a 'game gem' was discovered during the above loop, we want to append it to the END of the list
|
||||
if (!gameConfigPath.isEmpty())
|
||||
{
|
||||
configFiles.push_back(gameConfigPath);
|
||||
}
|
||||
}
|
||||
|
||||
bool AddPlatformConfigFilePaths(const char* root, QStringList& configFilePaths)
|
||||
{
|
||||
QString configWildcardName{ "*" };
|
||||
configWildcardName.append(AssetProcessorPlatformConfigFileName);
|
||||
QDir sourceRoot(root);
|
||||
|
||||
// first collect public platform configs
|
||||
QStringList platformList = FindWildcardMatches(sourceRoot.filePath(AssetConfigPlatformDir), configWildcardName);
|
||||
|
||||
// then collect restricted platform configs
|
||||
QDirIterator it(sourceRoot.filePath(RestrictedPlatformDir), QDir::NoDotAndDotDot | QDir::Dirs);
|
||||
while (it.hasNext())
|
||||
{
|
||||
QDir platformDir(it.next());
|
||||
platformList << FindWildcardMatches(platformDir.filePath(AssetConfigPlatformDir), configWildcardName);
|
||||
}
|
||||
|
||||
for (const auto& thisConfig : platformList)
|
||||
{
|
||||
configFilePaths.append(thisConfig);
|
||||
}
|
||||
return (platformList.size() > 0);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const char* AssetProcessorPlatformConfigFileName = "AssetProcessorPlatformConfig.ini";
|
||||
const char* AssetProcessorGamePlatformConfigFileName = "AssetProcessorGamePlatformConfig.ini";
|
||||
const char GemsDirectoryName[] = "Gems";
|
||||
const int pathLen = sourceFolder.length() + 1;
|
||||
|
||||
GemInfo::GemInfo(AZStd::string name, AZStd::string relativeFilePath, AZStd::string absoluteFilePath, AZStd::string identifier, bool isGameGem, bool assetOnlyGem)
|
||||
: m_gemName(name)
|
||||
, m_relativeFilePath(relativeFilePath)
|
||||
, m_absoluteFilePath(absoluteFilePath)
|
||||
, m_identifier(identifier)
|
||||
, m_isGameGem(isGameGem)
|
||||
, m_assetOnly(assetOnlyGem)
|
||||
AZ::IO::Path sourceWildcard{ sourceFolder };
|
||||
|
||||
|
||||
AZStd::vector<AZ::IO::Path> returnList;
|
||||
|
||||
// Walks the sourceFolder and subdirectories to search for the relativeName as a wild card using a breathe-first search
|
||||
AZStd::queue<AZ::IO::Path> searchFolders;
|
||||
searchFolders.push(AZStd::move(sourceWildcard));
|
||||
while (!searchFolders.empty())
|
||||
{
|
||||
}
|
||||
|
||||
QStringList GetEnabledPlatforms(QStringList configFiles)
|
||||
{
|
||||
QStringList enabledPlatforms;
|
||||
|
||||
// note that the current host platform is enabled by default.
|
||||
enabledPlatforms.push_back(AzToolsFramework::AssetSystem::GetHostAssetPlatform());
|
||||
|
||||
for (const QString& configFile : configFiles)
|
||||
const AZ::IO::Path& searchFolder = searchFolders.front();
|
||||
AZ::IO::SystemFile::FindFileCB findFiles = [&returnList, &relativeName, &searchFolder, &searchFolders](AZStd::string_view fileView, bool isFile) -> bool
|
||||
{
|
||||
Internal::ReadPlatformInfosFromConfigFile(configFile, enabledPlatforms);
|
||||
}
|
||||
return enabledPlatforms;
|
||||
}
|
||||
|
||||
QStringList GetConfigFiles(const char* root, const char* assetRoot, const char* gameName, bool addPlatformConfigs, bool addGemsConfigs)
|
||||
{
|
||||
QStringList configFiles;
|
||||
QDir configRoot(root);
|
||||
|
||||
QString rootConfigFile = configRoot.filePath(AssetProcessorPlatformConfigFileName);
|
||||
|
||||
configFiles.push_back(rootConfigFile);
|
||||
if (addPlatformConfigs)
|
||||
{
|
||||
Internal::AddPlatformConfigFilePaths(root, configFiles);
|
||||
}
|
||||
|
||||
if (addGemsConfigs)
|
||||
{
|
||||
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> gemInfoList;
|
||||
if (!GetGemsInfo(root, assetRoot, gameName, gemInfoList))
|
||||
if (fileView == "." || fileView == "..")
|
||||
{
|
||||
AZ_Error("AzToolsFramework::AssetUtils", false, "Failed to read gems for game project (%s).\n", gameName);
|
||||
return {};
|
||||
return true;
|
||||
}
|
||||
|
||||
Internal::AddGemConfigFiles(gemInfoList, configFiles);
|
||||
}
|
||||
|
||||
QDir assetRootDir(assetRoot);
|
||||
assetRootDir.cd(gameName);
|
||||
|
||||
QString projectConfigFile = assetRootDir.filePath(AssetProcessorGamePlatformConfigFileName);
|
||||
|
||||
configFiles.push_back(projectConfigFile);
|
||||
|
||||
return configFiles;
|
||||
}
|
||||
|
||||
static GemInfo ParseGemInfo(const rapidjson::Document& gemJsonDocument)
|
||||
{
|
||||
constexpr AZStd::fixed_string<32> NameKey = "Name";
|
||||
constexpr AZStd::fixed_string<32> UuidKey = "Uuid";
|
||||
constexpr AZStd::fixed_string<32> LinkTypeKey = "LinkType";
|
||||
constexpr AZStd::fixed_string<32> IsGameGemKey = "IsGameGem";
|
||||
|
||||
GemInfo gemInfo;
|
||||
auto memberIter = gemJsonDocument.FindMember(NameKey.c_str());
|
||||
if (memberIter != gemJsonDocument.MemberEnd())
|
||||
{
|
||||
gemInfo.m_gemName.assign(memberIter->value.GetString(), memberIter->value.GetStringLength());
|
||||
}
|
||||
|
||||
memberIter = gemJsonDocument.FindMember(UuidKey.c_str());
|
||||
if (memberIter != gemJsonDocument.MemberEnd())
|
||||
{
|
||||
gemInfo.m_identifier.assign(memberIter->value.GetString(), memberIter->value.GetStringLength());
|
||||
}
|
||||
|
||||
memberIter = gemJsonDocument.FindMember(IsGameGemKey.c_str());
|
||||
if (memberIter != gemJsonDocument.MemberEnd())
|
||||
{
|
||||
gemInfo.m_isGameGem = memberIter->value.GetBool();
|
||||
}
|
||||
|
||||
|
||||
AZStd::string_view linkTypeString;
|
||||
memberIter = gemJsonDocument.FindMember(LinkTypeKey.c_str());
|
||||
if (memberIter != gemJsonDocument.MemberEnd())
|
||||
{
|
||||
linkTypeString = AZStd::string_view{ memberIter->value.GetString(), memberIter->value.GetStringLength() };
|
||||
}
|
||||
|
||||
gemInfo.m_assetOnly = linkTypeString == "NoCode";
|
||||
|
||||
return gemInfo;
|
||||
}
|
||||
|
||||
bool GetGemsInfo([[maybe_unused]] const char* root, [[maybe_unused]] const char* assetRoot, [[maybe_unused]] const char* gameName, AZStd::vector<GemInfo>& gemInfoList)
|
||||
{
|
||||
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry == nullptr)
|
||||
{
|
||||
AZ_Error("AzToolsFramework::AssetUtils", false, "Settings Registry does not exist, cannot retrieve information about loaded Gem modules");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fileIoBase = AZ::IO::FileIOBase::GetInstance();
|
||||
if (fileIoBase == nullptr)
|
||||
{
|
||||
AZ_Error("AzToolsFramework::AssetUtils", false, "File IO, cannot retrieve information about loaded Gem modules");
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr AZStd::string_view GemJsonFilename{ "gem.json" };
|
||||
AZStd::vector<AZ::IO::FixedMaxPath> gemModuleSourcePaths;
|
||||
|
||||
struct GemSourcePathsVisitor
|
||||
: AZ::SettingsRegistryInterface::Visitor
|
||||
{
|
||||
GemSourcePathsVisitor(AZStd::vector<AZ::IO::FixedMaxPath>& gemSourcePaths)
|
||||
: m_gemSourcePaths(gemSourcePaths)
|
||||
{}
|
||||
|
||||
void Visit(AZStd::string_view path, [[maybe_unused]] AZStd::string_view valueName, [[maybe_unused]] AZ::SettingsRegistryInterface::Type type,
|
||||
AZStd::string_view value) override
|
||||
if (isFile)
|
||||
{
|
||||
if (path.find("SourcePaths") != AZStd::string_view::npos
|
||||
&& AZStd::find(m_gemSourcePaths.begin(), m_gemSourcePaths.end(), value) == m_gemSourcePaths.end())
|
||||
if (AZStd::wildcard_match(relativeName, fileView))
|
||||
{
|
||||
m_gemSourcePaths.emplace_back(value);
|
||||
returnList.emplace_back(searchFolder / fileView);
|
||||
}
|
||||
}
|
||||
AZStd::vector<AZ::IO::FixedMaxPath>& m_gemSourcePaths;
|
||||
else
|
||||
{
|
||||
// Use the current search directory to append the fileView directory wild card
|
||||
searchFolders.push(searchFolder / fileView);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
GemSourcePathsVisitor visitor{ gemModuleSourcePaths };
|
||||
const auto gemListKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/Gems", AZ::SettingsRegistryMergeUtils::OrganizationRootKey);
|
||||
AZ::SettingsRegistry::Get()->Visit(visitor, gemListKey);
|
||||
|
||||
AZ::IO::FixedMaxPath engineRootPath;
|
||||
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
if (engineRootPath.empty())
|
||||
{
|
||||
AZ_TracePrintfOnce("AzToolsFramework::AssetUtils", "Engine Root Path is empty. The Gem Module Source Paths will have the @engroot@ alias prepended");
|
||||
engineRootPath = "@engroot@";
|
||||
}
|
||||
|
||||
AZStd::vector<char> gemJsonFileData;
|
||||
for (const AZ::IO::FixedMaxPath& gemSourcePath : gemModuleSourcePaths)
|
||||
{
|
||||
AZ::IO::FixedMaxPath gemJsonPath = engineRootPath / gemSourcePath / GemJsonFilename;
|
||||
if (AZ::IO::HandleType gemJsonHandle; fileIoBase->Open(gemJsonPath.c_str(), AZ::IO::OpenMode::ModeRead, gemJsonHandle))
|
||||
{
|
||||
AZ::IO::SizeType gemJsonFileSize;
|
||||
if (AZ::IO::Result ioResult = fileIoBase->Size(gemJsonHandle, gemJsonFileSize); !ioResult)
|
||||
{
|
||||
AZ_Error("AzToolsFramework::AssetUtils", false, "Failed to query file size of gem json at path '%s'.\nResult code %u returned",
|
||||
gemJsonPath.c_str(), aznumeric_cast<uint32_t>(ioResult.GetResultCode()));
|
||||
fileIoBase->Close(gemJsonHandle);
|
||||
continue;
|
||||
}
|
||||
|
||||
gemJsonFileData.resize_no_construct(gemJsonFileSize);
|
||||
AZ::IO::SizeType bytesRead{};
|
||||
if (AZ::IO::Result ioResult = fileIoBase->Read(gemJsonHandle, gemJsonFileData.data(), gemJsonFileData.size(), false, &bytesRead); !ioResult)
|
||||
{
|
||||
AZ_Error("AzToolsFramework::AssetUtils", false, "Reading from gem json at path '%s' has failed with result code %u.\n%zu has been read",
|
||||
gemJsonPath.c_str(), aznumeric_cast<uint32_t>(ioResult.GetResultCode()), bytesRead);
|
||||
fileIoBase->Close(gemJsonHandle);
|
||||
continue;
|
||||
}
|
||||
|
||||
rapidjson::Document gemJsonDocument;
|
||||
gemJsonDocument.Parse(gemJsonFileData.data(), gemJsonFileData.size());
|
||||
if (gemJsonDocument.HasParseError())
|
||||
{
|
||||
AZ_Error("AzToolsFramework::AssetUtils", false, "Parsing gem json at path '%s' has json Parse Error: %s",
|
||||
gemJsonPath.c_str(), rapidjson::GetParseError_En(gemJsonDocument.GetParseError()))
|
||||
}
|
||||
|
||||
GemInfo gemInfo = ParseGemInfo(gemJsonDocument);
|
||||
|
||||
|
||||
char gemJsonResolvePath[AZ::IO::MaxPathLength];
|
||||
const bool foundFilename = fileIoBase->GetFilename(gemJsonHandle, AZStd::data(gemJsonResolvePath), AZStd::size(gemJsonResolvePath));
|
||||
if (foundFilename)
|
||||
{
|
||||
// Set the Absolute path to the folder containing the gem.json file
|
||||
gemInfo.m_absoluteFilePath = AZ::IO::PathView(gemJsonResolvePath).ParentPath().Native();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If unable to retrieve the Filename from the File IO handle the gemJsonPath is used instead
|
||||
gemInfo.m_absoluteFilePath = gemJsonPath.ParentPath().Native();
|
||||
}
|
||||
|
||||
// The gemSourcePath is the relative path
|
||||
gemInfo.m_relativeFilePath = static_cast<AZStd::string_view>(gemSourcePath.Native());
|
||||
gemInfoList.push_back(gemInfo);
|
||||
fileIoBase->Close(gemJsonHandle);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
AZ::IO::SystemFile::FindFiles((searchFolder / "*").c_str(), findFiles);
|
||||
searchFolders.pop();
|
||||
}
|
||||
|
||||
bool UpdateFilePathToCorrectCase(const QString& root, QString& relativePathFromRoot)
|
||||
return returnList;
|
||||
}
|
||||
|
||||
void AddGemConfigFiles(const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AZStd::vector<AZ::IO::Path>& configFiles)
|
||||
{
|
||||
const char* AssetProcessorGemConfigIni = "AssetProcessorGemConfig.ini";
|
||||
const char* AssetProcessorGemConfigSetreg = "AssetProcessorGemConfig.setreg";
|
||||
for (const AzFramework::GemInfo& gemElement : gemInfoList)
|
||||
{
|
||||
AZStd::string rootPath(root.toUtf8().data());
|
||||
AZStd::string relPathFromRoot(relativePathFromRoot.toUtf8().data());
|
||||
AZ::StringFunc::Path::Normalize(relPathFromRoot);
|
||||
AZStd::vector<AZStd::string> tokens;
|
||||
AZ::StringFunc::Tokenize(relPathFromRoot.c_str(), tokens, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
|
||||
AZStd::string validatedPath;
|
||||
if (rootPath.empty())
|
||||
for (const AZ::IO::Path& gemAbsoluteSourcePath : gemElement.m_absoluteSourcePaths)
|
||||
{
|
||||
const char* appRoot = nullptr;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot);
|
||||
validatedPath = AZStd::string(appRoot);
|
||||
configFiles.push_back(gemAbsoluteSourcePath / AssetProcessorGemConfigIni);
|
||||
configFiles.push_back(gemAbsoluteSourcePath / AssetProcessorGemConfigSetreg);
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace AzToolsFramework::AssetUtils
|
||||
{
|
||||
// Visitor for reading the "/Amazon/AssetProcessor/Settings/Platforms" entries from the Settings Registry
|
||||
struct EnabledPlatformsVisitor
|
||||
: AZ::SettingsRegistryInterface::Visitor
|
||||
{
|
||||
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value);
|
||||
|
||||
AZStd::vector<AZStd::string> m_enabledPlatforms;
|
||||
};
|
||||
|
||||
void EnabledPlatformsVisitor::Visit([[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value)
|
||||
{
|
||||
if (value == "enabled")
|
||||
{
|
||||
m_enabledPlatforms.emplace_back(valueName);
|
||||
}
|
||||
else if (value == "disabled")
|
||||
{
|
||||
auto platformEntrySearch = [&valueName](AZStd::string_view platformEntry)
|
||||
{
|
||||
validatedPath = rootPath;
|
||||
}
|
||||
return valueName == platformEntry;
|
||||
};
|
||||
auto removeIt = AZStd::remove_if(m_enabledPlatforms.begin(), m_enabledPlatforms.end(), platformEntrySearch);
|
||||
m_enabledPlatforms.erase(removeIt, m_enabledPlatforms.end());
|
||||
}
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
void ReadEnabledPlatformsFromSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry,
|
||||
AZStd::vector<AZStd::string>& enabledPlatforms)
|
||||
{
|
||||
// note that the current host platform is enabled by default.
|
||||
enabledPlatforms.push_back(AzToolsFramework::AssetSystem::GetHostAssetPlatform());
|
||||
|
||||
for (int idx = 0; idx < tokens.size(); idx++)
|
||||
// in the setreg the platform can be missing (commented out)
|
||||
// in which case it is disabled implicitly by not being there
|
||||
// or it can be 'disabled' which means that it is explicitly disabled.
|
||||
// or it can be 'enabled' which means that it is explicitly enabled.
|
||||
EnabledPlatformsVisitor visitor;
|
||||
settingsRegistry.Visit(visitor, AZ::SettingsRegistryInterface::FixedValueString(Internal::AssetProcessorSettingsKey) + "/Platforms");
|
||||
enabledPlatforms.insert(enabledPlatforms.end(), AZStd::make_move_iterator(visitor.m_enabledPlatforms.begin()),
|
||||
AZStd::make_move_iterator(visitor.m_enabledPlatforms.end()));
|
||||
}
|
||||
|
||||
AZStd::vector<AZStd::string> GetEnabledPlatforms(AZ::SettingsRegistryInterface& settingsRegistry,
|
||||
const AZStd::vector<AZ::IO::Path>& configFiles)
|
||||
{
|
||||
AZStd::vector<AZStd::string> enabledPlatforms;
|
||||
|
||||
for (const auto& configFile : configFiles)
|
||||
{
|
||||
if (AZ::IO::SystemFile::Exists(configFile.c_str()))
|
||||
{
|
||||
AZStd::string element = tokens[idx];
|
||||
bool foundAMatch = false;
|
||||
AZ::IO::FileIOBase::GetInstance()->FindFiles(validatedPath.c_str(), "*", [&](const char* file)
|
||||
{
|
||||
if ( idx != tokens.size() - 1 && !AZ::IO::FileIOBase::GetInstance()->IsDirectory(file))
|
||||
{
|
||||
// only the last token is supposed to be a filename, we can skip filenames before that
|
||||
return true;
|
||||
}
|
||||
|
||||
AZStd::string absFilePath(file);
|
||||
AZ::StringFunc::Path::Normalize(absFilePath);
|
||||
auto found = absFilePath.rfind(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
size_t startingPos = found + 1;
|
||||
if (found != AZStd::string::npos && absFilePath.size() > startingPos)
|
||||
{
|
||||
AZStd::string componentName = AZStd::string(absFilePath.begin() + startingPos, absFilePath.end());
|
||||
if (AZ::StringFunc::Equal(componentName.c_str(), tokens[idx].c_str()))
|
||||
{
|
||||
tokens[idx] = componentName;
|
||||
foundAMatch = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!foundAMatch)
|
||||
// If the config file is a settings registry file use the SettingsRegistryInterface MergeSettingsFile function
|
||||
// otherwise use the SettingsRegistryMergeUtils MergeSettingsToRegistry_ConfigFile function to merge an INI-style
|
||||
// file to the settings registry
|
||||
if (configFile.Extension() == ".setreg")
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
settingsRegistry.MergeSettingsFile(configFile.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ::SettingsRegistryMergeUtils::ConfigParserSettings configParserSettings;
|
||||
configParserSettings.m_registryRootPointerPath = Internal::AssetProcessorSettingsKey;
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ConfigFile(settingsRegistry, configFile.Native(), configParserSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
ReadEnabledPlatformsFromSettingsRegistry(settingsRegistry, enabledPlatforms);
|
||||
return enabledPlatforms;
|
||||
}
|
||||
|
||||
constexpr const char* AssetProcessorPlatformConfigFileName = "AssetProcessorPlatformConfig.ini";
|
||||
constexpr const char* AssetProcessorPlatformConfigSetreg = "AssetProcessorPlatformConfig.setreg";
|
||||
|
||||
bool AddPlatformConfigFilePaths(AZStd::string_view engineRoot, AZStd::vector<AZ::IO::Path>& configFilePaths)
|
||||
{
|
||||
auto restrictedRoot = AZ::IO::Path{ engineRoot } / Internal::RestrictedPlatformDir;
|
||||
|
||||
// first collect public platform configs
|
||||
AZStd::vector<AZ::IO::Path> platformDirs{ AZ::IO::Path{ engineRoot } / Internal::AssetConfigPlatformDir };
|
||||
|
||||
// then collect restricted platform configs
|
||||
// Append the AssetConfigPlatformDir value to each directory
|
||||
AZ::IO::SystemFile::FindFileCB findRestrictedAssetConfigs = [&restrictedRoot, &platformDirs](AZStd::string_view fileView, bool isFile) -> bool
|
||||
{
|
||||
if (fileView != "." && fileView != "..")
|
||||
{
|
||||
if (!isFile)
|
||||
{
|
||||
platformDirs.push_back(restrictedRoot / fileView / Internal::AssetConfigPlatformDir);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
AZ::IO::SystemFile::FindFiles((restrictedRoot / "*").c_str(), findRestrictedAssetConfigs);
|
||||
|
||||
// Iterator over all platform directories for platform config files
|
||||
AZStd::vector<AZ::IO::Path> allPlatformConfigs;
|
||||
for (const auto& platformDir : platformDirs)
|
||||
{
|
||||
for (const char* configPath : { AssetProcessorPlatformConfigFileName, AssetProcessorPlatformConfigSetreg })
|
||||
{
|
||||
AZStd::vector<AZ::IO::Path> platformConfigs = Internal::FindWildcardMatches(platformDir.Native(), configPath);
|
||||
allPlatformConfigs.insert(allPlatformConfigs.end(), AZStd::make_move_iterator(platformConfigs.begin()), AZStd::make_move_iterator(platformConfigs.end()));
|
||||
}
|
||||
}
|
||||
|
||||
const bool platformConfigFilePathsAdded = !allPlatformConfigs.empty();
|
||||
configFilePaths.insert(configFilePaths.end(), AZStd::make_move_iterator(allPlatformConfigs.begin()), AZStd::make_move_iterator(allPlatformConfigs.end()));
|
||||
return platformConfigFilePathsAdded;
|
||||
}
|
||||
|
||||
AZStd::vector<AZ::IO::Path> GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view assetRoot, AZStd::string_view projectPath,
|
||||
bool addPlatformConfigs, bool addGemsConfigs, AZ::SettingsRegistryInterface* settingsRegistry)
|
||||
{
|
||||
constexpr const char* AssetProcessorGamePlatformConfigFileName = "AssetProcessorGamePlatformConfig.ini";
|
||||
constexpr const char* AssetProcessorGamePlatformConfigSetreg = "AssetProcessorGamePlatformConfig.setreg";
|
||||
AZStd::vector<AZ::IO::Path> configFiles;
|
||||
AZ::IO::Path configRoot(engineRoot);
|
||||
|
||||
AZ::IO::Path rootConfigFile = configRoot / AssetProcessorPlatformConfigFileName;
|
||||
configFiles.push_back(rootConfigFile);
|
||||
|
||||
// Add a file entry for the Engine Root AssetProcessor setreg file
|
||||
rootConfigFile = configRoot / AssetProcessorPlatformConfigSetreg;
|
||||
configFiles.push_back(rootConfigFile);
|
||||
|
||||
if (addPlatformConfigs)
|
||||
{
|
||||
AddPlatformConfigFilePaths(engineRoot, configFiles);
|
||||
}
|
||||
|
||||
if (addGemsConfigs)
|
||||
{
|
||||
AZStd::vector<AzFramework::GemInfo> gemInfoList;
|
||||
if (!AzFramework::GetGemsInfo(gemInfoList, *settingsRegistry))
|
||||
{
|
||||
AZ_Error("AzToolsFramework::AssetUtils", false, "Failed to read gems from project folder(%s).\n", projectPath);
|
||||
return {};
|
||||
}
|
||||
|
||||
Internal::AddGemConfigFiles(gemInfoList, configFiles);
|
||||
}
|
||||
|
||||
AZ::IO::Path assetRootDir(assetRoot);
|
||||
assetRootDir /= projectPath;
|
||||
|
||||
AZ::IO::Path projectConfigFile = assetRootDir / AssetProcessorGamePlatformConfigFileName;
|
||||
configFiles.push_back(projectConfigFile);
|
||||
|
||||
// Add a file entry for the Project AssetProcessor setreg file
|
||||
projectConfigFile = assetRootDir / AssetProcessorGamePlatformConfigSetreg;
|
||||
configFiles.push_back(projectConfigFile);
|
||||
|
||||
return configFiles;
|
||||
}
|
||||
|
||||
bool UpdateFilePathToCorrectCase(const QString& root, QString& relativePathFromRoot)
|
||||
{
|
||||
AZStd::string rootPath(root.toUtf8().data());
|
||||
AZStd::string relPathFromRoot(relativePathFromRoot.toUtf8().data());
|
||||
AZ::StringFunc::Path::Normalize(relPathFromRoot);
|
||||
AZStd::vector<AZStd::string> tokens;
|
||||
AZ::StringFunc::Tokenize(relPathFromRoot.c_str(), tokens, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
|
||||
AZStd::string validatedPath;
|
||||
if (rootPath.empty())
|
||||
{
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(validatedPath, &AzFramework::ApplicationRequests::GetEngineRoot);
|
||||
}
|
||||
else
|
||||
{
|
||||
validatedPath = rootPath;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
|
||||
for (int idx = 0; idx < tokens.size(); idx++)
|
||||
{
|
||||
AZStd::string element = tokens[idx];
|
||||
bool foundAMatch = false;
|
||||
AZ::IO::FileIOBase::GetInstance()->FindFiles(validatedPath.c_str(), "*", [&](const char* file)
|
||||
{
|
||||
if (idx != tokens.size() - 1 && !AZ::IO::FileIOBase::GetInstance()->IsDirectory(file))
|
||||
{
|
||||
// only the last token is supposed to be a filename, we can skip filenames before that
|
||||
return true;
|
||||
}
|
||||
|
||||
AZStd::string absoluteFilePath;
|
||||
AZ::StringFunc::Path::ConstructFull(validatedPath.c_str(), element.c_str(), absoluteFilePath);
|
||||
AZStd::string absFilePath(file);
|
||||
AZ::StringFunc::Path::Normalize(absFilePath);
|
||||
auto found = absFilePath.rfind(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
size_t startingPos = found + 1;
|
||||
if (found != AZStd::string::npos && absFilePath.size() > startingPos)
|
||||
{
|
||||
AZStd::string componentName = AZStd::string(absFilePath.begin() + startingPos, absFilePath.end());
|
||||
if (AZ::StringFunc::Equal(componentName.c_str(), tokens[idx].c_str()))
|
||||
{
|
||||
tokens[idx] = componentName;
|
||||
foundAMatch = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
validatedPath = absoluteFilePath; // go one step deeper.
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (success)
|
||||
if (!foundAMatch)
|
||||
{
|
||||
relPathFromRoot.clear();
|
||||
AZ::StringFunc::Join(relPathFromRoot, tokens.begin(), tokens.end(), AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
relativePathFromRoot = relPathFromRoot.c_str();
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return success;
|
||||
AZStd::string absoluteFilePath;
|
||||
AZ::StringFunc::Path::ConstructFull(validatedPath.c_str(), element.c_str(), absoluteFilePath);
|
||||
|
||||
validatedPath = absoluteFilePath; // go one step deeper.
|
||||
}
|
||||
} //namespace AssetUtils
|
||||
} //namespace AzToolsFramework
|
||||
|
||||
if (success)
|
||||
{
|
||||
relPathFromRoot.clear();
|
||||
AZ::StringFunc::Join(relPathFromRoot, tokens.begin(), tokens.end(), AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
|
||||
relativePathFromRoot = relPathFromRoot.c_str();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
} //namespace AzToolsFramework::AssetUtils
|
||||
|
||||
@@ -9,64 +9,48 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
AZ_PUSH_DISABLE_WARNING(4127 4251 4800, "-Wunknown-warning-option")
|
||||
#include <QStringList>
|
||||
AZ_POP_DISABLE_WARNING
|
||||
#include <AzFramework/Gem/GemInfo.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
class QString;
|
||||
|
||||
namespace AzToolsFramework::AssetUtils
|
||||
{
|
||||
namespace AssetUtils
|
||||
{
|
||||
extern const char* AssetProcessorPlatformConfigFileName;
|
||||
extern const char* AssetProcessorGamePlatformConfigFileName;
|
||||
//! Reads the "/Amazon/AssetProcessor/Settings/Platforms" entry from the settings registry
|
||||
//! to retrieve all enabled platforms
|
||||
void ReadEnabledPlatformsFromSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry,
|
||||
AZStd::vector<AZStd::string>& enabledPlatforms);
|
||||
|
||||
//! This struct stores gem related information
|
||||
struct GemInfo
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(GemInfo, AZ::SystemAllocator, 0);
|
||||
GemInfo(AZStd::string name, AZStd::string relativeFilePath, AZStd::string absoluteFilePath, AZStd::string identifier, bool isGameGem, bool assetOnlyGem);
|
||||
GemInfo() = default;
|
||||
AZStd::string m_gemName; ///< A friendly display name, not to be used for any pathing stuff.
|
||||
AZStd::string m_relativeFilePath; ///< Where the gem's folder is (relative to the gems search path(s))
|
||||
AZStd::string m_absoluteFilePath; ///< Where the gem's folder is (as an absolute path)
|
||||
AZStd::string m_identifier; ///< The UUID of the gem.
|
||||
//! Returns all the enabledPlatforms by reading the specified config files in order.
|
||||
AZStd::vector<AZStd::string> GetEnabledPlatforms(AZ::SettingsRegistryInterface& settingsRegistry,
|
||||
const AZStd::vector<AZ::IO::Path>& configFiles);
|
||||
|
||||
bool m_isGameGem = false; //< True if its a 'game project' gem. Only one such gem can exist for any game project.
|
||||
bool m_assetOnly = false; ///< True if it is an asset only gems.
|
||||
//! Returns the platform specific AssetProcessorPlatformConfig.ini/setreg files
|
||||
//! Please note that config files are order dependent
|
||||
bool AddPlatformConfigFilePaths(AZStd::string_view root, AZStd::vector<AZ::IO::Path>& configFilePaths);
|
||||
|
||||
static AZStd::string GetGemAssetFolder() { return AZStd::string("Assets"); }
|
||||
//! Returns all the config files including the the platform and gems ones.
|
||||
//! Please note that config files are order dependent
|
||||
//! the root config file has the lowest priority.
|
||||
//! the project configuration file has the absolutely highest priority
|
||||
//! Also note that if the project has any "game project gems", then those will also be inserted last,
|
||||
//! and thus have a higher priority than the root or non - project gems.
|
||||
//! Also note that the game project could be in a different location to the engine therefore we need the assetRoot param.
|
||||
AZStd::vector<AZ::IO::Path> GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view assetRoot, AZStd::string_view projectPath,
|
||||
bool addPlatformConfigs = true, bool addGemsConfigs = true, AZ::SettingsRegistryInterface* settingsRegistry = nullptr);
|
||||
|
||||
};
|
||||
|
||||
//! Returns all the enabledPlatforms by reading the specified config files in order.
|
||||
QStringList GetEnabledPlatforms(QStringList configFiles);
|
||||
|
||||
//! Returns all the config files including the the platform and gems ones.
|
||||
//! Please note that config files are order dependent
|
||||
//! the root config file has the lowest priority.
|
||||
//! the project configuration file has the absolutely highest priority
|
||||
//! Also note that if the project has any "game project gems", then those will also be inserted last,
|
||||
//! and thus have a higher priority than the root or non - project gems.
|
||||
//! Also note that the game project could be in a different location to the engine therefore we need the assetRoot param.
|
||||
QStringList GetConfigFiles(const char* root, const char* assetRoot, const char* gameName, bool addPlatformConfigs = true, bool addGemsConfigs = true);
|
||||
|
||||
//! 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(const char* root, const char* assetRoot, const char* gameName, AZStd::vector<GemInfo>& gemInfoList);
|
||||
|
||||
//! A utility function which checks the given path starting at the root and updates the relative path to be the actual case correct path.
|
||||
//! For example, if you pass it "c:\lumberyard\dev" as the root and "editor\icons\whatever.ico" as the relative path.
|
||||
//! It may update relativePathFromRoot to be "Editor\Icons\Whatever.ico" if such a casing is the actual physical case on disk already.
|
||||
//! @param root a trusted already-case-correct path (will not be case corrected). If empty it will be set to appRoot.
|
||||
//! @param relativePathFromRoot a non-trusted (may be incorrect case) path relative to rootPath,
|
||||
//! which will be normalized and updated to be correct casing.
|
||||
//! @return if such a file does NOT exist, it returns FALSE, else returns TRUE.
|
||||
//! @note A very expensive function! Call sparingly.
|
||||
bool UpdateFilePathToCorrectCase(const QString& root, QString& relativePathFromRoot);
|
||||
} //namespace AssetUtils
|
||||
} //namespace AzToolsFramework
|
||||
//! A utility function which checks the given path starting at the root and updates the relative path to be the actual case correct path.
|
||||
//! For example, if you pass it "c:\lumberyard\dev" as the root and "editor\icons\whatever.ico" as the relative path.
|
||||
//! It may update relativePathFromRoot to be "Editor\Icons\Whatever.ico" if such a casing is the actual physical case on disk already.
|
||||
//! @param root a trusted already-case-correct path (will not be case corrected). If empty it will be set to appRoot.
|
||||
//! @param relativePathFromRoot a non-trusted (may be incorrect case) path relative to rootPath,
|
||||
//! which will be normalized and updated to be correct casing.
|
||||
//! @return if such a file does NOT exist, it returns FALSE, else returns TRUE.
|
||||
//! @note A very expensive function! Call sparingly.
|
||||
bool UpdateFilePathToCorrectCase(const QString& root, QString& relativePathFromRoot);
|
||||
} //namespace AzToolsFramework::AssetUtils
|
||||
|
||||
+15
-26
@@ -13,8 +13,9 @@
|
||||
#include <AzToolsFramework/AssetCatalog/PlatformAddressedAssetCatalog.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
|
||||
|
||||
namespace AzToolsFramework
|
||||
@@ -54,38 +55,26 @@ namespace AzToolsFramework
|
||||
|
||||
AZStd::string PlatformAddressedAssetCatalog::GetAssetRootForPlatform(AzFramework::PlatformId platformId)
|
||||
{
|
||||
const char* assetAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@assets@");
|
||||
if (assetAlias == nullptr)
|
||||
AZ::IO::Path projectCachePath;
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
if (settingsRegistry == nullptr
|
||||
|| !settingsRegistry->Get(projectCachePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
|
||||
{
|
||||
AZ_Error("PlatformAddressedAssetCatalog", false, "Failed to retrieve assetRoot");
|
||||
return {};
|
||||
}
|
||||
AZ::IO::PathView assetRoot{ assetAlias };
|
||||
// Folder structure is Cache/Projectfolder/platform/projectfolder
|
||||
// It should have at least one path separator
|
||||
if (assetRoot.Native().find_first_of(AZ_CORRECT_AND_WRONG_DATABASE_SEPARATOR) == AZStd::string::npos)
|
||||
{
|
||||
AZ_Warning("PlatformAddressedAssetCatalog", false, "Failed to retrieve valid asset root - got %.*s",
|
||||
aznumeric_cast<int>(assetRoot.Native().size()), assetRoot.Native().data());
|
||||
AZ_Warning("PlatformAddressedAssetCatalog", false, "Failed to retrieve valid project cache root from Settings Registry at key %s",
|
||||
AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
|
||||
return {};
|
||||
}
|
||||
|
||||
AZ::IO::PathView projectFolderLower = assetRoot.Filename();
|
||||
assetRoot = assetRoot.ParentPath();
|
||||
// Retrieve the Cache/Projectfolder path
|
||||
AZ::IO::Path platformAssetRoot = assetRoot.ParentPath();
|
||||
platformAssetRoot /= GetPlatformName(platformId);
|
||||
platformAssetRoot /= projectFolderLower;
|
||||
return platformAssetRoot.Native();
|
||||
// Retrieve the ProjectPath/Cache/ path
|
||||
AZ::IO::Path platformCachePath = projectCachePath / GetPlatformName(platformId);
|
||||
return platformCachePath.Native();
|
||||
}
|
||||
|
||||
//! Returns an absolute path to the AssetCatalog for a given platform
|
||||
AZStd::string PlatformAddressedAssetCatalog::GetCatalogRegistryPathForPlatform(AzFramework::PlatformId platformId)
|
||||
{
|
||||
AZStd::string assetRoot = GetAssetRootForPlatform(platformId);
|
||||
AzFramework::StringFunc::Path::Join(assetRoot.c_str(), "assetcatalog.xml", assetRoot);
|
||||
|
||||
return assetRoot;
|
||||
AZ::IO::Path platformCachePath = GetAssetRootForPlatform(platformId);
|
||||
return (platformCachePath / "assetcatalog.xml").Native();
|
||||
}
|
||||
|
||||
AZStd::string PlatformAddressedAssetCatalog::GetCatalogRegistryPath() const
|
||||
@@ -202,7 +191,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
return AssetCatalog::GetDirectProductDependencies(asset);
|
||||
}
|
||||
|
||||
|
||||
AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string> PlatformAddressedAssetCatalog::GetAllProductDependencies(const AZ::Data::AssetId& asset)
|
||||
{
|
||||
return AssetCatalog::GetAllProductDependencies(asset);
|
||||
|
||||
@@ -1,40 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
// 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 AzToolsFramework
|
||||
@@ -1,196 +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 "AzToolsFramework_precompiled.h"
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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 AzToolsFramework
|
||||
@@ -1,239 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzToolsFramework/Process/ProcessCommon_fwd.h>
|
||||
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
#include <AzToolsFramework/Process/internal/ProcessCommon_Win.h>
|
||||
#elif AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
#include <AzToolsFramework/Process/internal/ProcessCommon_OSX.h>
|
||||
#elif defined(AZ_PLATFORM_LINUX)
|
||||
#include <AzToolsFramework/Process/internal/ProcessCommon_Linux.h>
|
||||
#endif
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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(AzToolsFramework::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();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::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;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::StdProcessCommunicator overrides
|
||||
bool CreatePipesForProcess(ProcessData* processData) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected:
|
||||
void CreateHandles();
|
||||
void CloseAllHandles();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::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();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::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;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::StdProcessCommunicatorForChildProcess overrides
|
||||
bool AttachToExistingPipes() override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected:
|
||||
void CreateHandles();
|
||||
void CloseAllHandles();
|
||||
|
||||
StdProcessCommunicatorHandle m_stdInRead;
|
||||
StdProcessCommunicatorHandle m_stdOutWrite;
|
||||
StdProcessCommunicatorHandle m_stdErrWrite;
|
||||
bool m_initialized = false;
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
@@ -1,111 +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 "AzToolsFramework_precompiled.h"
|
||||
#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 <AzToolsFramework/Process/ProcessWatcher.h>
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
bool ProcessWatcher::LaunchProcessAndRetrieveOutput(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType, AzToolsFramework::ProcessOutput& outProcessOutput)
|
||||
{
|
||||
// launch the process
|
||||
|
||||
AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType));
|
||||
// this may fail - but that's okay, it means it cannot find the P4 Process. This is not an error situation, the user may not have P4 installed.
|
||||
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>{};
|
||||
}
|
||||
}
|
||||
@@ -1,109 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#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 <AzToolsFramework/Process/ProcessCommon_fwd.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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 appropraite (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, AzToolsFramework::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 AzToolsFramework
|
||||
-54
@@ -1,54 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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 AzToolsFramework
|
||||
|
||||
@@ -1,57 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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 AzToolsFramework
|
||||
|
||||
#endif // AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
@@ -1,55 +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.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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
|
||||
|
||||
-247
@@ -1,247 +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 "AzToolsFramework_precompiled.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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 AzToolsFramework
|
||||
|
||||
-247
@@ -1,247 +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 "AzToolsFramework_precompiled.h"
|
||||
|
||||
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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 AzToolsFramework
|
||||
|
||||
#endif // AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
-281
@@ -1,281 +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 "AzToolsFramework_precompiled.h"
|
||||
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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
|
||||
|
||||
-416
@@ -1,416 +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 "AzToolsFramework_precompiled.h"
|
||||
|
||||
#include <AzToolsFramework/Process/ProcessWatcher.h>
|
||||
#include <AzToolsFramework/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 AzToolsFramework
|
||||
{
|
||||
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 AzToolsFramework
|
||||
-437
@@ -1,437 +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 "AzToolsFramework_precompiled.h"
|
||||
|
||||
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
|
||||
#include <AzToolsFramework/Process/ProcessWatcher.h>
|
||||
#include <AzToolsFramework/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 AzToolsFramework
|
||||
{
|
||||
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 AzToolsFramework
|
||||
|
||||
#endif // AZ_TRAIT_OS_PLATFORM_APPLE
|
||||
-364
@@ -1,364 +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 "AzToolsFramework_precompiled.h"
|
||||
|
||||
#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 <AzToolsFramework/Process/ProcessWatcher.h>
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
#include <AzToolsFramework/Process/internal/ProcessCommon_Win.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
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, 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 AzToolsFramework::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 AzToolsFramework
|
||||
+3
-3
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
|
||||
#include <AzToolsFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzToolsFramework/SourceControl/PerforceConnection.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
@@ -1251,14 +1251,14 @@ namespace AzToolsFramework
|
||||
return changelistNum;
|
||||
}
|
||||
|
||||
ProcessWatcher* pWatcher = s_perforceConn->m_command.ExecuteNewChangelistInput();
|
||||
AzFramework::ProcessWatcher* pWatcher = s_perforceConn->m_command.ExecuteNewChangelistInput();
|
||||
AZ_Assert(pWatcher, "No process found for p4!");
|
||||
if (!pWatcher)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
|
||||
AzFramework::ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
|
||||
AZ_Assert(pCommunicator, "No communicator found for p4!");
|
||||
if (!pCommunicator)
|
||||
{
|
||||
|
||||
+12
-12
@@ -13,7 +13,7 @@
|
||||
#include "AzToolsFramework_precompiled.h"
|
||||
|
||||
#include <AzToolsFramework/SourceControl/PerforceConnection.h>
|
||||
#include <AzToolsFramework/Process/ProcessWatcher.h>
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
|
||||
#define SCC_WINDOW "Source Control"
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace AzToolsFramework
|
||||
ExecuteCommand();
|
||||
}
|
||||
|
||||
ProcessWatcher* PerforceCommand::ExecuteNewChangelistInput()
|
||||
AzFramework::ProcessWatcher* PerforceCommand::ExecuteNewChangelistInput()
|
||||
{
|
||||
m_commandArgs = "change -i";
|
||||
return ExecuteIOCommand();
|
||||
@@ -370,31 +370,31 @@ namespace AzToolsFramework
|
||||
void PerforceCommand::ExecuteCommand()
|
||||
{
|
||||
m_rawOutput.Clear();
|
||||
ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
processLaunchInfo.m_commandlineParameters = "p4 -ztag " + m_commandArgs;
|
||||
processLaunchInfo.m_showWindow = false;
|
||||
ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, m_rawOutput);
|
||||
m_applicationFound = processLaunchInfo.m_launchResult == ProcessLauncher::PLR_MissingFile ? false : true;
|
||||
AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, m_rawOutput);
|
||||
m_applicationFound = processLaunchInfo.m_launchResult == AzFramework::ProcessLauncher::PLR_MissingFile ? false : true;
|
||||
}
|
||||
|
||||
ProcessWatcher* PerforceCommand::ExecuteIOCommand()
|
||||
AzFramework::ProcessWatcher* PerforceCommand::ExecuteIOCommand()
|
||||
{
|
||||
ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
processLaunchInfo.m_commandlineParameters = "p4 " + m_commandArgs;
|
||||
processLaunchInfo.m_showWindow = false;
|
||||
ProcessWatcher* processWatcher = ProcessWatcher::LaunchProcess(processLaunchInfo, ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT);
|
||||
m_applicationFound = processLaunchInfo.m_launchResult == ProcessLauncher::PLR_MissingFile ? false : true;
|
||||
AzFramework::ProcessWatcher* processWatcher = AzFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT);
|
||||
m_applicationFound = processLaunchInfo.m_launchResult == AzFramework::ProcessLauncher::PLR_MissingFile ? false : true;
|
||||
return processWatcher;
|
||||
}
|
||||
|
||||
void PerforceCommand::ExecuteRawCommand()
|
||||
{
|
||||
m_rawOutput.Clear();
|
||||
ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
processLaunchInfo.m_commandlineParameters = "p4 " + m_commandArgs;
|
||||
processLaunchInfo.m_showWindow = false;
|
||||
ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, m_rawOutput);
|
||||
m_applicationFound = processLaunchInfo.m_launchResult == ProcessLauncher::PLR_MissingFile ? false : true;
|
||||
AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, m_rawOutput);
|
||||
m_applicationFound = processLaunchInfo.m_launchResult == AzFramework::ProcessLauncher::PLR_MissingFile ? false : true;
|
||||
}
|
||||
|
||||
AZStd::string PerforceConnection::GetUser() const
|
||||
|
||||
@@ -13,16 +13,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
|
||||
#include <AzToolsFramework/Process/ProcessCommunicator.h>
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class ProcessWatcher;
|
||||
}
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
class ProcessWatcher;
|
||||
|
||||
class PerforceCommand
|
||||
{
|
||||
public:
|
||||
ProcessOutput m_rawOutput;
|
||||
AzFramework::ProcessOutput m_rawOutput;
|
||||
PerforceMap m_commandOutputMap; // doesn't allow duplicate kvp's
|
||||
AZStd::vector<PerforceMap> m_commandOutputMapList; // allows duplicate kvp's
|
||||
|
||||
@@ -79,7 +82,7 @@ namespace AzToolsFramework
|
||||
void ExecuteTicketStatus();
|
||||
void ExecuteTrust(bool enable, const AZStd::string& fingerprint);
|
||||
|
||||
ProcessWatcher* ExecuteNewChangelistInput();
|
||||
AzFramework::ProcessWatcher* ExecuteNewChangelistInput();
|
||||
void ExecuteNewChangelistOutput();
|
||||
void ExecuteRevert(const AZStd::string& filePath);
|
||||
void ExecuteShowChangelists(const AZStd::string& currentUser, const AZStd::string& currentClient);
|
||||
@@ -91,7 +94,7 @@ namespace AzToolsFramework
|
||||
bool m_applicationFound = false;
|
||||
|
||||
virtual void ExecuteCommand();
|
||||
virtual ProcessWatcher* ExecuteIOCommand();
|
||||
virtual AzFramework::ProcessWatcher* ExecuteIOCommand();
|
||||
virtual void ExecuteRawCommand();
|
||||
};
|
||||
|
||||
|
||||
-12
@@ -193,18 +193,6 @@ namespace LegacyFramework
|
||||
// if we're in console mode, listen for CTRL+C
|
||||
::SetConsoleCtrlHandler(CTRL_BREAK_HandlerRoutine, true);
|
||||
#endif
|
||||
|
||||
m_ptrCommandLineParser = aznew AzFramework::CommandLine();
|
||||
m_ptrCommandLineParser->Parse(m_desc.m_argc, m_desc.m_argv);
|
||||
if (m_ptrCommandLineParser->HasSwitch("app-root"))
|
||||
{
|
||||
auto appRootOverride = m_ptrCommandLineParser->GetSwitchValue("app-root", 0);
|
||||
if (!appRootOverride.empty())
|
||||
{
|
||||
m_appRoot = appRootOverride;
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have one create a serialize context
|
||||
if (GetSerializeContext() == nullptr)
|
||||
{
|
||||
|
||||
+10
-2
@@ -18,8 +18,10 @@
|
||||
#include <AzCore/Component/EntityUtils.h>
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
@@ -147,7 +149,13 @@ namespace AzToolsFramework
|
||||
m_gui = new Ui::EntityOutlinerWidgetUI();
|
||||
m_gui->setupUi(this);
|
||||
|
||||
QDir rootDir(AzQtComponents::FindEngineRootDir(qApp));
|
||||
AZ::IO::FixedMaxPath engineRootPath;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
}
|
||||
|
||||
QDir rootDir = QString::fromUtf8(engineRootPath.c_str(), aznumeric_cast<int>(engineRootPath.Native().size()));
|
||||
const auto pathOnDisk = rootDir.absoluteFilePath(QStringLiteral("Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/"));
|
||||
const QStringView qrcPath = QStringLiteral(":/EntityOutliner");
|
||||
|
||||
@@ -155,7 +163,7 @@ namespace AzToolsFramework
|
||||
// developers. The style will be loaded from a Qt Resource file if Editor is installed, but
|
||||
// developers with the file on disk will be able to modify the style and have it automatically
|
||||
// reloaded.
|
||||
AzQtComponents::StyleManager::addSearchPaths("outliner", pathOnDisk, qrcPath.toString());
|
||||
AzQtComponents::StyleManager::addSearchPaths("outliner", pathOnDisk, qrcPath.toString(), engineRootPath);
|
||||
AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("outliner:EntityOutliner.qss"));
|
||||
|
||||
m_listModel = aznew EntityOutlinerListModel(this);
|
||||
|
||||
@@ -194,11 +194,6 @@ set(FILES
|
||||
Manipulators/TranslationManipulators.cpp
|
||||
Manipulators/TranslationManipulators.h
|
||||
Maths/TransformUtils.h
|
||||
Process/ProcessCommunicator.cpp
|
||||
Process/ProcessCommunicator.h
|
||||
Process/ProcessWatcher.cpp
|
||||
Process/ProcessWatcher.h
|
||||
Process/ProcessCommon_fwd.h
|
||||
Picking/BoundInterface.h
|
||||
Picking/ContextBoundAPI.h
|
||||
Picking/Manipulators/ManipulatorBoundManager.cpp
|
||||
|
||||
@@ -32,8 +32,5 @@ set(FILES
|
||||
UI/UICore/SaveChangesDialog.hxx
|
||||
UI/UICore/SaveChangesDialog.cpp
|
||||
UI/UICore/SaveChangesDialog.ui
|
||||
Process/internal/ProcessWatcher_Linux.cpp
|
||||
Process/internal/ProcessCommon_Linux.h
|
||||
Process/internal/ProcessCommunicator_Linux.cpp
|
||||
ToolsFileUtils/ToolsFileUtils_generic.cpp
|
||||
)
|
||||
|
||||
@@ -32,8 +32,5 @@ set(FILES
|
||||
UI/UICore/SaveChangesDialog.hxx
|
||||
UI/UICore/SaveChangesDialog.cpp
|
||||
UI/UICore/SaveChangesDialog.ui
|
||||
Process/internal/ProcessWatcher_OSX.cpp
|
||||
Process/internal/ProcessCommon_OSX.h
|
||||
Process/internal/ProcessCommunicator_OSX.cpp
|
||||
ToolsFileUtils/ToolsFileUtils_generic.cpp
|
||||
)
|
||||
|
||||
@@ -33,8 +33,5 @@ set(FILES
|
||||
UI/UICore/SaveChangesDialog.hxx
|
||||
UI/UICore/SaveChangesDialog.cpp
|
||||
UI/UICore/SaveChangesDialog.ui
|
||||
Process/internal/ProcessWatcher_Win.cpp
|
||||
Process/internal/ProcessCommon_Win.h
|
||||
Process/internal/ProcessCommunicator_Win.cpp
|
||||
ToolsFileUtils/ToolsFileUtils_win.cpp
|
||||
)
|
||||
|
||||
@@ -33,8 +33,5 @@ set(FILES
|
||||
UI/UICore/SaveChangesDialog.hxx
|
||||
UI/UICore/SaveChangesDialog.cpp
|
||||
UI/UICore/SaveChangesDialog.ui
|
||||
Process/internal/ProcessWatcher_Win.cpp
|
||||
Process/internal/ProcessCommon_Win.h
|
||||
Process/internal/ProcessCommunicator_Win.cpp
|
||||
ToolsFileUtils/ToolsFileUtils_win.cpp
|
||||
)
|
||||
+1
-1
@@ -35,7 +35,7 @@ namespace AzToolsFramework
|
||||
AZStd::string Get7zExePath()
|
||||
{
|
||||
const char* rootPath = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(rootPath, &AZ::ComponentApplicationRequests::GetAppRoot);
|
||||
AZ::ComponentApplicationBus::BroadcastResult(rootPath, &AZ::ComponentApplicationRequests::GetEngineRoot);
|
||||
AZStd::string exePath;
|
||||
AzFramework::StringFunc::Path::ConstructFull(rootPath, "Tools", "7za", ".exe", exePath);
|
||||
return exePath;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <AzFramework/Platform/PlatformDefaults.h>
|
||||
#include <AzToolsFramework/AssetCatalog/PlatformAddressedAssetCatalog.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzTest/Utils.h>
|
||||
namespace // anonymous
|
||||
{
|
||||
@@ -179,7 +180,7 @@ namespace UnitTest
|
||||
const AZStd::string engroot = AZ::Test::GetEngineRootPath();
|
||||
AZ::IO::FileIOBase::GetInstance()->SetAlias("@engroot@", engroot.c_str());
|
||||
|
||||
AZ::IO::Path assetRoot(engroot);
|
||||
AZ::IO::Path assetRoot(AZ::Utils::GetProjectPath());
|
||||
assetRoot /= "Cache";
|
||||
AZ::IO::FileIOBase::GetInstance()->SetAlias("@root@", assetRoot.c_str());
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
*/
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzQtComponents/Components/StyleManager.h>
|
||||
|
||||
@@ -40,7 +43,13 @@ AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv)
|
||||
::testing::InitGoogleMock(&argc, argv);
|
||||
QApplication app(argc, argv);
|
||||
auto styleManager = AZStd::make_unique< AzQtComponents::StyleManager>(&app);
|
||||
styleManager->initialize(&app);
|
||||
AZ::IO::FixedMaxPath engineRootPath;
|
||||
{
|
||||
AZ::ComponentApplication componentApplication(argc, argv);
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
}
|
||||
styleManager->initialize(&app, engineRootPath);
|
||||
AZ::Test::printUnusedParametersWarning(argc, argv);
|
||||
AZ::Test::addTestEnvironments({ new ToolsFrameworkHook });
|
||||
int result = RUN_ALL_TESTS();
|
||||
|
||||
Reference in New Issue
Block a user