Integrating latest from github/staging

Integrating up through commit 5e1bdae
This commit is contained in:
alexpete
2021-03-26 14:31:50 -07:00
parent 9c54341af8
commit 36c4e827bd
764 changed files with 11453 additions and 20251 deletions
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
{
@@ -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();
};
@@ -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)
{
@@ -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
)
@@ -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());
+10 -1
View File
@@ -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();