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
@@ -20,6 +20,8 @@
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/LocalFileEventLogger.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/Memory/OverrunDetectionAllocator.h>
@@ -99,6 +101,18 @@ namespace AZ
return environment ? environment->Get() : nullptr;
}
ComponentApplication::EventLoggerDeleter::EventLoggerDeleter() noexcept= default;
ComponentApplication::EventLoggerDeleter::EventLoggerDeleter(bool skipDelete) noexcept
: m_skipDelete{skipDelete}
{}
void ComponentApplication::EventLoggerDeleter::operator()(AZ::Debug::LocalFileEventLogger* ptr)
{
if (!m_skipDelete)
{
delete ptr;
}
}
//=========================================================================
// ComponentApplication::Descriptor
// [5/30/2012]
@@ -159,6 +173,80 @@ namespace AZ
return true;
};
//! SettingsRegistry notifier handler which updates relevant registry settings based
//! on an update to '/Amazon/AzCore/Bootstrap/project_path' key.
struct UpdateProjectSettingsEventHandler
{
UpdateProjectSettingsEventHandler(AZ::SettingsRegistryInterface& registry)
: m_registry{ registry }
{
}
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
UpdateProjectSpecializationInRegistry(path);
}
//! Add the project name as a specialization underneath the /Amazon/AzCore/Settings/Specializations path
//! and remove the current project name specialization if one exists.
void UpdateProjectSpecializationInRegistry(AZStd::string_view path)
{
auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
+ "/project_path";
if (path == projectPathKey)
{
AZ::SettingsRegistryInterface::FixedValueString newProjectPath;
if (m_registry.Get(newProjectPath, path) && !newProjectPath.empty())
{
// Make the path absolute by appending to app root, in case project path is relative.
// If the project path is already absolute it will remain the same.
// If we turn it from a relative path to an absolute path, write-back the absolute path to the registry.
AZ::IO::FixedMaxPath projectPath = AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath;
if (projectPath.Compare(newProjectPath.c_str()))
{
m_registry.Set(path, projectPath.Native());
}
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
AZ::IO::FixedMaxPath projectMetadataFile{ projectPath };
projectMetadataFile /= "project.json";
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
// Get the 'project_name' value from what was in the 'project.json' file...
auto projectNameKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey)
+ "/project_name";
AZ::SettingsRegistryInterface::FixedValueString projectSpecialization;
if (m_registry.Get(projectSpecialization, projectNameKey))
{
auto specializationKey = AZ::SettingsRegistryInterface::FixedValueString::format(
"%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, projectSpecialization.c_str());
if (m_currentSpecialization != specializationKey)
{
m_registry.Set(specializationKey, true);
if (!m_currentSpecialization.empty())
{
// Remove the previous Project Name from the specialization path if it was set.
m_registry.Remove(m_currentSpecialization);
}
m_currentSpecialization = specializationKey;
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
}
}
}
}
}
private:
AZ::SettingsRegistryInterface::FixedValueString m_currentSpecialization;
AZ::SettingsRegistryInterface& m_registry;
};
void ComponentApplication::Descriptor::AllocatorRemapping::Reflect(ReflectContext* context, ComponentApplication* app)
{
(void)app;
@@ -276,6 +364,7 @@ namespace AZ
}
ComponentApplication::ComponentApplication(int argC, char** argV)
: m_eventLogger{}
{
if (argV)
{
@@ -284,11 +373,23 @@ namespace AZ
}
else
{
azstrcpy(m_commandLineBuffer, AZ_ARRAY_SIZE(m_commandLineBuffer), "no_argv_supplied");
azstrcpy(m_commandLineBuffer, AZ_ARRAY_SIZE(m_commandLineBuffer), "no_argv_supplied");
// use a "valid" value here. This is because Qt and potentially other third party libraries require
// that ArgC be 'at least 1' and that (*argV)[0] be a valid pointer to a real null terminated string.
m_argC = 1;
m_argV = &m_commandLineBufferAddress;
m_argC = 1;
m_argV = &m_commandLineBufferAddress;
}
// Create the Event logger if it doesn't exist, otherwise reuse the one registered
// with the AZ::Interface
if (AZ::Interface<AZ::Debug::IEventLogger>::Get() == nullptr)
{
m_eventLogger.reset(new AZ::Debug::LocalFileEventLogger);
}
else
{
m_eventLogger = EventLoggerPtr(static_cast<AZ::Debug::LocalFileEventLogger*>(AZ::Interface<AZ::Debug::IEventLogger>::Get()),
EventLoggerDeleter{ true });
}
// Initializes the OSAllocator and SystemAllocator as soon as possible
@@ -297,6 +398,7 @@ namespace AZ
// Now that the Allocators are initialized, the Command Line parameters can be parsed
m_commandLine.Parse(m_argC, m_argV);
ParseCommandLine(m_commandLine);
// Create the settings registry and register it with the AZ interface system
// This is done after the AppRoot has been calculated so that the Bootstrap.cfg
@@ -304,62 +406,34 @@ namespace AZ
m_settingsRegistry = AZStd::make_unique<SettingsRegistryImpl>();
// Register the Settings Registry with the AZ Interface if there isn't one registered already
if (AZ::SettingsRegistry::Get() == nullptr)
if (SettingsRegistry::Get() == nullptr)
{
SettingsRegistry::Register(m_settingsRegistry.get());
}
// Add the Command Line arguments into the SettingsRegistry
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_StoreCommandLine(*m_settingsRegistry, m_commandLine);
SettingsRegistryMergeUtils::StoreCommandLineToRegistry(*m_settingsRegistry, m_commandLine);
// Merge Command Line arguments
constexpr bool executeRegDumpCommands = false;
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
// Query for the Executable Path using OS specific functions
CalculateExecutablePath();
// Determine the path to the engine
CalculateEngineRoot();
// If the current platform returns an engaged optional from Utils::GetDefaultAppRootPath(), that is used
// for the application root other the application root is found by scanning upwards from the Executable Directory
// for a bootstrap.cfg file
CalculateAppRoot(nullptr);
// for the application root.
CalculateAppRoot();
// Add a notifier to update the /Amazon/AzCore/Settings/Specializations
// when the sys_game_folder property changes within the SettingsRegistry
// currentGameName is bound by value in order to allow the lambda to have a member variable that
// can track the current game specialization before it changes
AZ::SettingsRegistryInterface::FixedValueString currentGameSpecialization;
auto GameProjectChanged = [currentGameSpecialization](AZStd::string_view path, AZ::SettingsRegistryInterface::Type type) mutable
{
constexpr auto projectKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
+ "/sys_game_folder";
if (projectKey == path && type == AZ::SettingsRegistryInterface::Type::String)
{
auto registry = AZ::SettingsRegistry::Get();
AZ::SettingsRegistryInterface::FixedValueString newGameName;
if (registry && registry->Get(newGameName, path) && !newGameName.empty())
{
auto specializationKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, newGameName.c_str());
if (currentGameSpecialization != specializationKey)
{
registry->Set(specializationKey, true);
if (!currentGameSpecialization.empty())
{
// Remove the previous Game Name from the specialization path if it was set
registry->Remove(currentGameSpecialization);
}
// Update the currentGameSpecialization
currentGameSpecialization = specializationKey;
// Update all the runtime filepaths based on the new "sys_game_folder" value
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
}
}
}
};
m_gameProjectChangedHandler = m_settingsRegistry->RegisterNotifier(AZStd::move(GameProjectChanged));
// when the 'project_path' property changes within the SettingsRegistry
m_projectChangedHandler = m_settingsRegistry->RegisterNotifier(UpdateProjectSettingsEventHandler{ *m_settingsRegistry });
// Merge the bootstrap.cfg file into the Settings Registry as soon as the OSAllocator has been created.
SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(*m_settingsRegistry);
constexpr bool executeRegDumpCommands = false;
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
@@ -391,11 +465,11 @@ namespace AZ
Destroy();
}
// The m_gameProjectChangedHandler stores an AZStd::function internally
// The m_projectChangedHandler stores an AZStd::function internally
// which allocates using the AZ SystemAllocator
// m_gameProjectChangedHandler is being default value initialized
// m_projectChangedHandler is being default value initialized
// to clear out the AZStd::function
m_gameProjectChangedHandler = {};
m_projectChangedHandler = {};
// Delete the AZ::IConsole if it was created by this application instance
if (m_ownsConsole)
@@ -412,6 +486,10 @@ namespace AZ
}
m_settingsRegistry.reset();
// Set AZ::CommandLine to an empty object to clear out allocated memory before the allocators
// are destroyed
m_commandLine = {};
DestroyAllocator();
}
@@ -421,17 +499,6 @@ namespace AZ
AZ_Assert(!m_isStarted, "Component application already started!");
m_startupParameters = startupParameters;
// Invokes CalculateAppRoot() again this time with the appRootOverride startup parameter
// supplied in order to allow overriding the AppRoot calculated in the constructor
if (m_startupParameters.m_appRootOverride)
{
CalculateAppRoot(m_startupParameters.m_appRootOverride);
// Re-check for the bootstrap.cfg file again using the appRoot override and update the file paths
SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(*m_settingsRegistry);
constexpr bool executeRegDumpCommands = false;
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
}
m_descriptor = descriptor;
@@ -461,20 +528,14 @@ namespace AZ
void ComponentApplication::CreateCommon()
{
{
AZ::SettingsRegistryInterface::FixedValueString registryValue;
m_settingsRegistry->Get(registryValue, AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage);
AZ::IO::FixedMaxPath outputPath{ registryValue };
AZ::IO::FixedMaxPath outputPath;
m_settingsRegistry->Get(outputPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage);
outputPath /= "eventlogger";
registryValue.clear();
AZ::IO::FixedMaxPathString baseFileName{ "EventLog" }; // default name
if (m_settingsRegistry->Get(registryValue, AZ::SettingsRegistryMergeUtils::BuildTargetNameKey))
{
baseFileName = registryValue;
}
m_settingsRegistry->Get(baseFileName, AZ::SettingsRegistryMergeUtils::BuildTargetNameKey);
m_eventLogger.Start(outputPath.c_str(), baseFileName.c_str());
m_eventLogger->Start(outputPath.Native(), baseFileName);
}
CreateDrillers();
@@ -515,7 +576,9 @@ namespace AZ
LoadModules();
// Execute user.cfg after modules have been loaded but before processing any command-line overrides
m_console->ExecuteConfigFile("@root@/user.cfg");
AZ::IO::FixedMaxPath platformCachePath;
m_settingsRegistry->Get(platformCachePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
m_console->ExecuteConfigFile((platformCachePath / "user.cfg").Native());
// Parse the command line parameters for console commands after modules have loaded
m_console->ExecuteCommandLine(m_commandLine);
@@ -601,7 +664,7 @@ namespace AZ
m_drillerManager = nullptr;
}
m_eventLogger.Stop();
m_eventLogger->Stop();
// Clear the descriptor to deallocate all strings (owned by ModuleDescriptor)
m_descriptor = Descriptor();
@@ -775,6 +838,46 @@ namespace AZ
}
}
void ComponentApplication::ParseCommandLine(const AZ::CommandLine& commandLine)
{
struct OptionKeyToRegsetKey
{
AZStd::string_view m_optionKey;
AZStd::string m_regsetKey;
};
// Provide overrides for the engine root, the project root and the project cache root
AZStd::array commandOptions = {
OptionKeyToRegsetKey{ "engine-path", AZStd::string::format("%s/engine_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) },
OptionKeyToRegsetKey{ "project-path", AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) },
OptionKeyToRegsetKey{ "project-cache-path", AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) }
};
AZStd::fixed_vector<AZStd::string, commandOptions.size()> overrideArgs;
for (auto&& [optionKey, regsetKey] : commandOptions)
{
if (size_t optionCount = commandLine.GetNumSwitchValues(optionKey); optionCount > 0)
{
// Use the last supplied command option value to override previous values
auto overrideArg = AZStd::string::format(R"(--regset="%s=%s")", regsetKey.c_str(),
commandLine.GetSwitchValue(optionKey, optionCount - 1).c_str());
overrideArgs.emplace_back(AZStd::move(overrideArg));
}
}
if (!overrideArgs.empty())
{
// Dump the input command line, add the additional option overrides
// and Parse the new command line into the Component Application command line
AZ::CommandLine::ParamContainer commandLineArgs;
commandLine.Dump(commandLineArgs);
commandLineArgs.insert(commandLineArgs.end(), AZStd::make_move_iterator(overrideArgs.begin()),
AZStd::make_move_iterator(overrideArgs.end()));
m_commandLine.Parse(commandLineArgs);
}
}
void ComponentApplication::MergeSettingsToRegistry(SettingsRegistryInterface& registry)
{
SettingsRegistryInterface::Specializations specializations;
@@ -788,14 +891,14 @@ namespace AZ
// In development builds apply the developer registry and the command line to allow early overrides. This will
// allow developers to override things like default paths or Asset Processor connection settings. Any additional
// values will be replaced by later loads, so this step will happen again at the end of loading.
SettingsRegistryMergeUtils::MergeSettingsToRegistry_DevRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_UserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
#endif
SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD)
SettingsRegistryMergeUtils::MergeSettingsToRegistry_DevRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_UserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
#endif
}
@@ -1013,7 +1116,7 @@ namespace AZ
struct GemModuleLoadData
{
AZ::OSString m_gemName;
AZ::OSString m_dynamicLibraryPath;
AZStd::vector<AZ::OSString> m_dynamicLibraryPaths;
bool m_autoLoad{ true };
};
@@ -1069,19 +1172,18 @@ namespace AZ
}
}
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value) override
void Visit(AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type, AZStd::string_view value) override
{
if (valueName == "Module" && !value.empty())
// Remove last path segment and check if the key corresponds to the Modules array
AZStd::optional<AZStd::string_view> moduleIndex = AZ::StringFunc::TokenizeLast(path, "/");
if (path.ends_with("/Modules"))
{
// Strip off the Module entry from the path
auto moduleKey = AZ::StringFunc::TokenizeLast(path, "/");
if (!moduleKey)
{
return;
}
// Remove the "Modules" path segment to be at the GemName key
AZ::StringFunc::TokenizeLast(path, "/");
if (auto moduleLoadData = FindGemModuleEntry(path); moduleLoadData != nullptr)
{
moduleLoadData->m_dynamicLibraryPath = value;
// Just use Json Serialization to load all the array elements
moduleLoadData->m_dynamicLibraryPaths.emplace_back(value);
}
}
}
@@ -1111,30 +1213,41 @@ namespace AZ
}
};
constexpr size_t RegistryKeySize = 64;
auto gemModuleKey = AZStd::fixed_string<RegistryKeySize>::format("%s/Gems", AZ::SettingsRegistryMergeUtils::OrganizationRootKey);
auto gemModuleKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/Gems", AZ::SettingsRegistryMergeUtils::OrganizationRootKey);
ModuleDescriptorList gemModules;
{
GemModuleVisitor moduleVisitor;
m_settingsRegistry->Visit(moduleVisitor, gemModuleKey);
m_settingsRegistry->Visit(moduleVisitor, gemModuleKey);
for (GemModuleLoadData& moduleLoadData : moduleVisitor.m_modulesLoadData)
{
// Add all auto loadable non-asset gems to the list of gem modules to load
if (moduleLoadData.m_autoLoad && !moduleLoadData.m_dynamicLibraryPath.empty())
if (!moduleLoadData.m_autoLoad)
{
gemModules.emplace_back(DynamicModuleDescriptor{ AZStd::move(moduleLoadData.m_dynamicLibraryPath) });
break;
}
for (AZ::OSString& dynamicLibraryPath : moduleLoadData.m_dynamicLibraryPaths)
{
auto CompareDynamicModuleDescriptor = [&dynamicLibraryPath](const DynamicModuleDescriptor& entry)
{
return entry.m_dynamicLibraryPath.contains(dynamicLibraryPath);
};
if (auto moduleIter = AZStd::find_if(gemModules.begin(), gemModules.end(), CompareDynamicModuleDescriptor);
moduleIter == gemModules.end())
{
gemModules.emplace_back(DynamicModuleDescriptor{ AZStd::move(dynamicLibraryPath) });
}
}
}
}
// The settings registry in the settings registry are prioritized to load before the modules in the ComponetnApplication descriptor
// The modules in the settings registry are prioritized to load before the modules in the ComponentApplication descriptor
// in the order in which they were found
for (auto&& moduleDescriptor : m_descriptor.m_modules)
{
// Append new dynamic library modules to the descriptor array
auto CompareDynamicModuleDescriptor = [&moduleDescriptor](const DynamicModuleDescriptor& entry)
{
return entry.m_dynamicLibraryPath.find(moduleDescriptor.m_dynamicLibraryPath) != AZStd::string_view::npos;
return entry.m_dynamicLibraryPath.contains(moduleDescriptor.m_dynamicLibraryPath);
};
if (auto foundModuleIter = AZStd::find_if(gemModules.begin(), gemModules.end(), CompareDynamicModuleDescriptor);
foundModuleIter == gemModules.end())
@@ -1260,17 +1373,8 @@ namespace AZ
m_exeDirectory.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
void ComponentApplication::CalculateAppRoot(const char* appRootOverride)
void ComponentApplication::CalculateAppRoot()
{
if (appRootOverride)
{
m_appRoot = appRootOverride;
if (!m_appRoot.empty() && !m_appRoot.ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR))
{
m_appRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
return;
}
if (AZStd::optional<AZ::StringFunc::Path::FixedString> appRootPath = Utils::GetDefaultAppRootPath(); appRootPath)
{
m_appRoot = AZStd::move(*appRootPath);
@@ -1279,26 +1383,14 @@ namespace AZ
m_appRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
else
{
m_appRoot = AZ::SettingsRegistryMergeUtils::GetAppRoot(m_settingsRegistry.get()).Native();
m_appRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
//=========================================================================
// CheckEngineMarkerFile
//=========================================================================
bool ComponentApplication::CheckPathForEngineMarker(const char* fullPath) const
void ComponentApplication::CalculateEngineRoot()
{
static const char* engineMarkerFileName = "engine.json";
char engineMarkerFullPathToCheck[AZ_MAX_PATH_LEN] = "";
azstrcpy(engineMarkerFullPathToCheck, AZ_ARRAY_SIZE(engineMarkerFullPathToCheck), fullPath);
azstrcat(engineMarkerFullPathToCheck, AZ_ARRAY_SIZE(engineMarkerFullPathToCheck), "/");
azstrcat(engineMarkerFullPathToCheck, AZ_ARRAY_SIZE(engineMarkerFullPathToCheck), engineMarkerFileName);
return AZ::IO::SystemFile::Exists(engineMarkerFullPathToCheck);
if (m_engineRoot = AZ::SettingsRegistryMergeUtils::FindEngineRoot(*m_settingsRegistry).Native(); !m_engineRoot.empty())
{
m_engineRoot.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR);
}
}
void ComponentApplication::ResolveModulePath([[maybe_unused]] AZ::OSString& modulePath)
@@ -9,15 +9,13 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#ifndef AZCORE_COMPONENT_APPLICATION_H
#define AZCORE_COMPONENT_APPLICATION_H
#pragma once
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/ProfileModuleInit.h>
#include <AzCore/Debug/LocalFileEventLogger.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Module/DynamicModuleHandle.h>
@@ -40,12 +38,15 @@ namespace AZ
class IConsole;
class Module;
class ModuleManager;
}
namespace AZ::Debug
{
class DrillerManager;
class LocalFileEventLogger;
}
namespace Debug
{
class DrillerManager;
}
namespace AZ
{
class ReflectionEnvironment
{
public:
@@ -167,15 +168,6 @@ namespace AZ
//! \note Dynamic AZ::Modules are specified in the ComponentApplication::Descriptor.
CreateStaticModulesCallback m_createStaticModulesCallback = nullptr;
//! If set, this is used as the app root folder instead of it being calculated.
const char* m_appRootOverride = nullptr;
//! The path to root of the asset cache folder. For instance: ./cache/<project>/pc
const char* m_cacheRootPath = nullptr;
//! The path to the project in the asset cache folder. For instance: ./cache/<project>/pc/<project>
const char* m_cacheProjectPath = nullptr;
//! Specifies which system components to create & activate. If no tags specified, all system components are used. Specify as comma separated list.
const char* m_systemComponentTags = nullptr;
@@ -226,6 +218,8 @@ namespace AZ
/// Returns the working root folder that has been registered with the app, if there is one.
/// It's expected that derived applications will implement an application root.
const char* GetAppRoot() const override { return m_appRoot.c_str(); }
/// Returns the path to the engine.
const char* GetEngineRoot() const override { return m_engineRoot.c_str(); }
/// Returns the path to the folder the executable is in.
const char* GetExecutableFolder() const override { return m_exeDirectory.c_str(); }
@@ -331,6 +325,9 @@ namespace AZ
/// Create the drillers
void CreateDrillers();
/// Parse ComponentApplication specific command line arguments
void ParseCommandLine(const AZ::CommandLine& commandLine);
virtual void MergeSettingsToRegistry(SettingsRegistryInterface& registry);
//! Sets the specializations that will be used when loading the Settings Registry. Extend this in derived
@@ -363,17 +360,11 @@ namespace AZ
/// Calculates the directory the application executable comes from.
void CalculateExecutablePath();
/// Calculates the directory where the bootstrap.cfg file resides.
void CalculateAppRoot(const char* appRootOverride = {});
/// Calculates the root directory of the engine.
void CalculateEngineRoot();
/**
* Check/verify a given path for the engine marker (file) so that we can identify that
* a given path is the engine root. This is only valid for target platforms that are built
* for the host platform and not deployable (ie windows, mac).
* @param fullPath The full path to look for the engine marker
* @return true if the input path contains the engine marker file, false if not
*/
virtual bool CheckPathForEngineMarker(const char* fullPath) const;
/// Calculates the directory where the bootstrap.cfg file resides.
void CalculateAppRoot();
template<typename Iterator>
static void NormalizePath(Iterator begin, Iterator end, bool doLowercase = true)
@@ -398,10 +389,11 @@ namespace AZ
void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy.
IAllocatorAllocate* m_osAllocator{ nullptr };
EntitySetType m_entities;
AZ::StringFunc::Path::FixedString m_exeDirectory;
AZ::StringFunc::Path::FixedString m_appRoot;
AZ::IO::FixedMaxPathString m_exeDirectory;
AZ::IO::FixedMaxPathString m_engineRoot;
AZ::IO::FixedMaxPathString m_appRoot;
AZ::SettingsRegistryInterface::NotifyEventHandler m_gameProjectChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectChangedHandler;
// ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console
// from the m_console member when it goes out of scope
@@ -427,9 +419,15 @@ namespace AZ
// Created early to allow events to be logged before anything else. These will be kept in memory until
// a file is associated with the logger. The internal buffer is limited to 64kb and once full unexpected
// behavior may happen. The LocalFileEventLogger will register itself automatically with AZ::Interface<IEventLogger>.
AZ::Debug::LocalFileEventLogger m_eventLogger;
struct EventLoggerDeleter
{
EventLoggerDeleter() noexcept;
EventLoggerDeleter(bool skipDelete) noexcept;
void operator()(AZ::Debug::LocalFileEventLogger* ptr);
bool m_skipDelete{};
};
using EventLoggerPtr = AZStd::unique_ptr<AZ::Debug::LocalFileEventLogger, EventLoggerDeleter>;
EventLoggerPtr m_eventLogger;
};
}
#endif // AZCORE_COMPONENT_APPLICATION_H
#pragma once
@@ -183,6 +183,11 @@ namespace AZ
* @return A pointer to the name of the app's root folder, if a root folder was registered.
*/
virtual const char* GetAppRoot() const = 0;
/**
* Gets the path of the working engine folder that the app is a part of.
* @return A pointer to the engine path.
*/
virtual const char* GetEngineRoot() const = 0;
/**
* Gets the path to the directory that contains the application's executable.
* @return A pointer to the name of the path that contains the application's executable.
@@ -161,9 +161,11 @@ namespace AZ
void Console::ExecuteCommandLine(const AZ::CommandLine& commandLine)
{
for (const auto& [switchKey, switchValues] : commandLine.GetSwitchList())
for (auto&& commandArgument : commandLine)
{
ConsoleCommandContainer commandArgs(switchValues.begin(), switchValues.end());
const auto& switchKey = commandArgument.m_option;
const auto& switchValue = commandArgument.m_value;
ConsoleCommandContainer commandArgs{ switchValue };
PerformCommand(switchKey, commandArgs, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null);
}
}
+14 -25
View File
@@ -67,25 +67,7 @@ namespace AZ
return a != OpenMode::Invalid;
}
inline OpenMode operator | (OpenMode a, OpenMode b)
{
return static_cast<OpenMode>(static_cast<AZ::u32>(a) | static_cast<AZ::u32>(b));
}
inline OpenMode operator & (OpenMode a, OpenMode b)
{
return static_cast<OpenMode>(static_cast<AZ::u32>(a) & static_cast<AZ::u32>(b));
}
inline OpenMode& operator |= (OpenMode& a, OpenMode b)
{
return a = a | b;
}
inline OpenMode& operator &= (OpenMode& a, OpenMode b)
{
return a = a & b;
}
AZ_DEFINE_ENUM_BITWISE_OPERATORS(OpenMode)
OpenMode GetOpenModeFromStringMode(const char* mode);
@@ -250,12 +232,14 @@ namespace AZ
virtual bool ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& path) const = 0;
AZStd::optional<AZ::IO::FixedMaxPath> ConvertToAlias(const AZ::IO::PathView& path) const;
/// ResolvePath - Replaces any aliases in path with their values and stores the result in resolvedPath,
/// also ensures that the path is absolute
/// returns true if path was resolved, false otherwise
/// note that all of the above file-finding and opening functions automatically resolve the path before operating
/// so you should not need to call this except in very exceptional circumstances where you absolutely need to
/// hit a physical file and don't want to use SystemFile
//! ResolvePath - Replaces any aliases in path with their values and stores the result in resolvedPath,
//! also ensures that the path is absolute
//! NOTE: If the path does not start with an alias then the resolved value of the @assets@ is used
//! which has the effect of making the path relative to the @assets@/ folder
//! returns true if path was resolved, false otherwise
//! note that all of the above file-finding and opening functions automatically resolve the path before operating
//! so you should not need to call this except in very exceptional circumstances where you absolutely need to
//! hit a physical file and don't want to use SystemFile
virtual bool ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const = 0;
//! ResolvePath - Replaces any @ aliases in the supplied path with their the resolved alias values
@@ -265,6 +249,11 @@ namespace AZ
virtual bool ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const = 0;
AZStd::optional<AZ::IO::FixedMaxPath> ResolvePath(const AZ::IO::PathView& path) const;
//! ReplaceAliases - If the path starts with an @...@ alias it is substituted with the alias value
//! otherwise the path is copied as is to the resolvedAlias path value
//! returns true if the resulting path can fit within AZ::IO::FixedMaxPath buffer
virtual bool ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const = 0;
/// Divulge the filename used to originally open that handle.
virtual bool GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const = 0;
+4 -4
View File
@@ -170,7 +170,7 @@ namespace AZ::IO
//! Check whether the path is not absolute
[[nodiscard]] constexpr bool IsRelative() const;
//! Check whether the path is relative to the base path
[[nodiscard]] constexpr bool IsRelativeTo(const PathView & base) const;
[[nodiscard]] constexpr bool IsRelativeTo(const PathView& base) const;
//! Normalizes a path in a purely lexical manner.
//! # Path separators are converted to their preferred path separator
@@ -517,7 +517,7 @@ namespace AZ::IO
//! Checks if the path has a root directory
[[nodiscard]] constexpr bool HasRootDirectory() const;
//! Checks whether the entire root path portion of the path is empty
//! The root portion ofthe path is made up of root_name() / root_directory()
//! The root portion of the path is made up of root_name() / root_directory()
[[nodiscard]] constexpr bool HasRootPath() const;
//! checks whether the relative part of path is empty
//! (C:\\ lumberyard\dev\)
@@ -539,8 +539,8 @@ namespace AZ::IO
[[nodiscard]] constexpr bool IsAbsolute() const;
//! Check whether the path is not absolute
[[nodiscard]] constexpr bool IsRelative() const;
//! Check whether the path is relative to the input path
[[nodiscard]] constexpr bool IsRelativeTo() const;
//! Check whether the path is relative to the base path
[[nodiscard]] constexpr bool IsRelativeTo(const PathView& base) const;
// decomposition
//! Given a windows path of "C:\lumberyard\foo\bar\name.txt" and a posix path of
@@ -1035,7 +1035,7 @@ namespace AZ::IO
// move the parser from the end to a valid filename by decrementing
for(--pathParserEnd, --patternParserEnd; pathParserEnd && patternParserEnd; --pathParserEnd, --patternParserEnd)
{
if (!AZStd::wildcard_match(*patternParserEnd, *pathParserEnd))
if (!AZStd::wildcard_match_case(*patternParserEnd, *pathParserEnd))
{
return false;
}
@@ -1830,9 +1830,9 @@ namespace AZ::IO
}
template <typename StringType>
[[nodiscard]] constexpr bool BasicPath<StringType>::IsRelativeTo() const
[[nodiscard]] constexpr bool BasicPath<StringType>::IsRelativeTo(const PathView& base) const
{
return static_cast<PathView>(*this).IsRelative();
return static_cast<PathView>(*this).IsRelativeTo(base);
}
template <typename StringType>
@@ -369,6 +369,7 @@ namespace AZ
context.Serializer<JsonVector2Serializer>()->HandlesType<Vector2>();
context.Serializer<JsonVector3Serializer>()->HandlesType<Vector3>();
context.Serializer<JsonVector4Serializer>()->HandlesType<Vector4>();
context.Serializer<JsonQuaternionSerializer>()->HandlesType<Quaternion>();
}
void MathReflect(ReflectContext* context)
@@ -14,6 +14,7 @@
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Math/MathVectorSerializer.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
@@ -238,4 +239,56 @@ namespace AZ
{
return JsonMathVectorSerializerInternal::Store<Vector4, 4>(outputValue, inputValue, defaultValue, valueTypeId, context);
}
// Quaternion
AZ_CLASS_ALLOCATOR_IMPL(JsonQuaternionSerializer, SystemAllocator, 0);
JsonSerializationResult::Result JsonQuaternionSerializer::Load(void* outputValue, const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
// check for "yaw, pitch, roll" object
if (inputValue.IsObject())
{
if (inputValue.GetObject().ObjectEmpty())
{
Quaternion* outQuaternion = reinterpret_cast<Quaternion*>(outputValue);
*outQuaternion = Quaternion::CreateIdentity();
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, "Using identity quaternion for empty object.");
}
AZ::BaseJsonSerializer* floatSerializer = context.GetRegistrationContext()->GetSerializerForType(azrtti_typeid<float>());
if (!floatSerializer)
{
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Catastrophic, "Failed to find the json float serializer.");
}
constexpr const char* names[3] = {"yaw", "pitch", "roll"};
float values[3];
int i = 0;
for (auto itr = inputValue.MemberBegin(); itr != inputValue.MemberEnd(); ++i, ++itr)
{
ScopedContextPath subPath(context, names[i]);
JSR::Result intermediate = floatSerializer->Load(values + i, azrtti_typeid<float>(), itr->value, context);
if (intermediate.GetResultCode().GetProcessing() != JSR::Processing::Completed)
{
return intermediate;
}
}
auto eulerAnglesDegrees = Vector3::CreateFromFloat3(values);
reinterpret_cast<Quaternion*>(outputValue)->SetFromEulerDegrees(eulerAnglesDegrees);
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Success, "Successfully read quaternion.");
}
return JsonMathVectorSerializerInternal::Load<Quaternion, 4>(outputValue, outputValueTypeId, inputValue, context);
}
JsonSerializationResult::Result JsonQuaternionSerializer::Store(rapidjson::Value& outputValue, const void* inputValue,
const void* defaultValue, const Uuid& valueTypeId, JsonSerializerContext& context)
{
return JsonMathVectorSerializerInternal::Store<Quaternion, 4>(outputValue, inputValue, defaultValue, valueTypeId, context);
}
}
@@ -51,4 +51,16 @@ namespace AZ
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
class JsonQuaternionSerializer
: public BaseJsonSerializer
{
public:
AZ_RTTI(JsonQuaternionSerializer, "{18604375-3606-49AC-B366-0F6DF9149FF3}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue,
const Uuid& valueTypeId, JsonSerializerContext& context) override;
};
}
@@ -10,11 +10,11 @@
*
*/
#include <AzCore/Settings/CommandLine.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/AzCore_Traits_Platform.h>
#include <AzCore/std/tuple.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/Settings/CommandLine.h>
#include <AzCore/StringFunc/StringFunc.h>
namespace AZ
{
@@ -29,6 +29,21 @@ namespace AZ
return lowerStr;
}
AZStd::string_view UnquoteArgument(AZStd::string_view arg)
{
if (arg.size() < 2)
{
return arg;
}
return arg.front() == '"' && arg.back() == '"' ? AZStd::string_view{ arg.begin() + 1, arg.end() - 1 } : arg;
}
AZStd::string QuoteArgument(AZStd::string_view arg)
{
return !arg.empty() ? AZStd::string::format(R"("%.*s")", aznumeric_cast<int>(arg.size()), arg.data()) : AZStd::string{ arg };
}
}
CommandLine::CommandLine()
@@ -41,15 +56,62 @@ namespace AZ
{
}
void CommandLine::AddArgument(AZStd::string currentArg, AZStd::string& currentSwitch)
void CommandLine::ParseOptionArgument(AZStd::string_view newOption, AZStd::string_view newValue,
CommandArgument* inProgressArgument)
{
StringFunc::Strip(currentArg, " ", false, true, true);
// Allow argument values wrapped in quotes at both ends to become the value within the quotes
AZStd::string_view unquotedValue = UnquoteArgument(newValue);
if (unquotedValue != newValue)
{
// Update the inProgressArgument before adding new arguments
if(inProgressArgument)
{
inProgressArgument->m_value = unquotedValue;
// Set the inProgressArgument to nullptr to indicate that the inProgressArgument has been fulfilled
inProgressArgument = nullptr;
}
else
{
m_allValues.push_back({ newOption, unquotedValue });
}
}
else
{
AZStd::vector<AZStd::string_view> tokens;
auto splitArgument = [&tokens](AZStd::string_view token)
{
tokens.emplace_back(AZ::StringFunc::StripEnds(token));
};
AZ::StringFunc::TokenizeVisitor(newValue, splitArgument, ",;");
// we do it this way because you are allowed to do odd things like
// -root=abc -root=hij,klm -root whee -root fun;days
// and roots value should be { abc, hij, klm, whee, fun, days }
for (AZStd::string_view optionValue : tokens)
{
if (inProgressArgument)
{
inProgressArgument->m_value = optionValue;
// Set the inProgressArgument to nullptr to indicate that the inProgressArgument has been fulfilled
inProgressArgument = nullptr;
}
else
{
m_allValues.push_back({ newOption, optionValue });
}
}
}
}
void CommandLine::AddArgument(AZStd::string_view currentArg, AZStd::string& currentSwitch)
{
currentArg = AZ::StringFunc::StripEnds(currentArg);
if (!currentArg.empty())
{
if (AZStd::string_view(currentArg.begin(), currentArg.begin() + 1).find_first_of(m_commandLineOptionPrefix) != AZStd::string_view::npos)
if (m_commandLineOptionPrefix.contains(currentArg.front()))
{
// its possible that its a key-value-pair like /blah=whatever
// we support this too, for compatibilty.
// its possible that its a key-value-pair like -blah=whatever
// we support this too, for compatibility.
currentArg = currentArg.substr(1);
if (currentArg[0] == '-') // for -- extra
@@ -57,93 +119,41 @@ namespace AZ
currentArg = currentArg.substr(1);
}
AZStd::size_t foundPos = StringFunc::Find(currentArg.c_str(), '=');
AZStd::size_t foundPos = AZ::StringFunc::Find(currentArg, "=");
if (foundPos != AZStd::string::npos)
{
// Allow argument values wrapped in quotes at both ends to become the value within the quotes
if (currentArg.length() > (foundPos + 2) && currentArg[foundPos + 1] == '"' && currentArg[currentArg.length() - 1] == '"')
{
AZStd::string argName = currentArg.substr(0, foundPos);
argName = ToLower(argName);
StringFunc::Strip(argName, " ", false, true, true);
m_switches[argName].emplace_back(currentArg.substr(foundPos + 2, currentArg.length() - (foundPos + 2) - 1));
currentSwitch.clear();
return;
}
// its in '=' format
AZStd::vector<AZStd::string> tokens;
StringFunc::Tokenize(currentArg.substr(foundPos + 1).c_str(), tokens, ",;");
currentArg.resize(foundPos);
currentArg = ToLower(currentArg);
StringFunc::Strip(currentArg, " ", false, true, true);
m_switches.insert_key(currentArg); // returns pair<iter, bool>
// we do it this way because you are allowed to do odd things like
// /root=abc /root=hij,klm /root whee /root fun;days
// and roots value should be { abc, hij, klm, whee, fun, days }
for (AZStd::string& switchValue : tokens)
{
StringFunc::Strip(switchValue, " ", false, true, true);
m_switches[currentArg].push_back(switchValue);
}
AZStd::string_view argumentView{ currentArg };
AZStd::string_view option = AZ::StringFunc::StripEnds(argumentView.substr(0, foundPos));
AZStd::string_view value = AZ::StringFunc::StripEnds(argumentView.substr(foundPos + 1));
ParseOptionArgument(ToLower(option), value, nullptr);
currentSwitch.clear();
}
else
{
// its in this format /switchName switchvalue
// its in this format -switchName switchvalue
// (no equals)
currentSwitch = currentArg;
currentSwitch = ToLower(currentSwitch);
m_switches.insert_key(currentSwitch);
currentSwitch = ToLower(currentArg);
m_allValues.push_back({ currentSwitch, "" });
}
}
else
{
if (currentSwitch.empty())
{
m_miscValues.push_back(currentArg);
m_allValues.push_back({ "", UnquoteArgument(currentArg) });
}
else
{
// Allow argument values wrapped in quotes at both ends to become the value within the quotes
if (currentArg[0] == '"' && currentArg.length() >= 2 && currentArg[currentArg.length() - 1] == '"')
{
m_switches[currentSwitch].emplace_back(currentArg.substr(1, currentArg.length() - 2));
currentSwitch.clear();
return;
}
AZStd::vector<AZStd::string> tokens;
StringFunc::Tokenize(currentArg.c_str(), tokens, ",;");
for (AZStd::string& switchValue : tokens)
{
StringFunc::Strip(switchValue, " ", false, true, true);
m_switches[currentSwitch].push_back(switchValue);
}
ParseOptionArgument(currentSwitch, currentArg, &m_allValues.back());
currentSwitch.clear();
}
currentSwitch.clear();
}
}
}
void CommandLine::Parse(const ParamContainer& commandLine)
{
m_switches.clear();
m_miscValues.clear();
AZStd::string currentSwitch;
for (int i = 1; i < commandLine.size(); ++i)
{
AddArgument(commandLine[i], currentSwitch);
}
}
void CommandLine::Parse(int argc, char** argv)
{
m_switches.clear();
m_miscValues.clear();
m_allValues.clear();
AZStd::string currentSwitch;
// Start on 1 because 0 is the executable name
@@ -157,91 +167,145 @@ namespace AZ
}
}
void CommandLine::Dump(ParamContainer& commandLineDumpOutput)
void CommandLine::Parse(const ParamContainer& commandLine)
{
// Push back an empty argument as the Parse function always skips parsing the first argument
commandLineDumpOutput.emplace_back(AZStd::string());
for (const AZStd::string& miscValue : m_miscValues)
{
commandLineDumpOutput.push_back(miscValue);
}
m_allValues.clear();
if (!m_commandLineOptionPrefix.empty())
// This version of Parse does not skip over 0th index
AZStd::string currentSwitch;
for (int i = 0; i < commandLine.size(); ++i)
{
for (const auto& [switchKey, switchValues] : m_switches)
{
AZStd::string prefixSwitchKey = m_commandLineOptionPrefix.front() + switchKey;
if (switchValues.empty())
{
// There are no value for the switch so just push it back of the command line dump output
commandLineDumpOutput.emplace_back(prefixSwitchKey);
}
else
{
for (const auto& switchValue : switchValues)
{
commandLineDumpOutput.emplace_back(prefixSwitchKey);
commandLineDumpOutput.emplace_back(switchValue);
}
}
}
AddArgument(commandLine[i], currentSwitch);
}
else
}
void CommandLine::Dump(ParamContainer& commandLineDumpOutput) const
{
AZ_Error("CommandLine", !m_commandLineOptionPrefix.empty(),
"Cannot dump command line switches from a command line with an empty option prefix");
for (const CommandArgument& argument : m_allValues)
{
AZ_Error("CommandLine", false, "Cannot dump command line switches from a command line with an empty option prefix");
if (!argument.m_option.empty())
{
commandLineDumpOutput.emplace_back(m_commandLineOptionPrefix.front() + argument.m_option);
}
if (!argument.m_value.empty())
{
commandLineDumpOutput.emplace_back(QuoteArgument(argument.m_value));
}
}
}
bool CommandLine::HasSwitch(AZStd::string_view switchName) const
{
return m_switches.find(ToLower(switchName)) != m_switches.end();
auto commandArgumentIter = AZStd::find_if(m_allValues.begin(), m_allValues.end(),
[optionName = ToLower(switchName)](const CommandArgument& argument) { return argument.m_option == optionName; });
return commandArgumentIter != m_allValues.end();
}
AZStd::size_t CommandLine::GetNumSwitchValues(AZStd::string_view switchName) const
{
ParamMap::const_iterator switchFound = m_switches.find(ToLower(switchName));
if (switchFound == m_switches.end())
{
return 0;
}
return switchFound->second.size();
return AZStd::count_if(m_allValues.begin(), m_allValues.end(),
[optionName = ToLower(switchName)](const CommandArgument& argument) { return argument.m_option == optionName; });
}
const AZStd::string& CommandLine::GetSwitchValue(AZStd::string_view switchName, AZStd::size_t index) const
{
ParamMap::const_iterator switchFound = m_switches.find(ToLower(switchName));
if (switchFound == m_switches.end())
AZStd::string optionName = ToLower(switchName);
size_t currentPosIndex{};
auto findArgumentAt = [&optionName, index, &currentPosIndex](const CommandArgument& argument)
{
if (argument.m_option == optionName)
{
return currentPosIndex++ == index;
}
return false;
};
auto commandArgumentIter = AZStd::find_if(m_allValues.begin(), m_allValues.end(), findArgumentAt);
if (!optionName.empty())
{
AZ_Assert(index < currentPosIndex, R"(Invalid Command line optional argument lookup of "%s" at index %zu)",
optionName.c_str(), index);
}
else
{
AZ_Assert(index < currentPosIndex, R"(Invalid Command line positional argument lookup at index %zu)", index);
}
if (commandArgumentIter == m_allValues.end())
{
return m_emptyValue;
}
AZ_Assert(index < switchFound->second.size(), "Invalid Command line switch lookup");
if (index >= switchFound->second.size())
{
return m_emptyValue;
}
return switchFound->second.at(index);
return commandArgumentIter->m_value;
}
AZStd::size_t CommandLine::GetNumMiscValues() const
{
return m_miscValues.size();
return GetNumSwitchValues("");
}
const AZStd::string& CommandLine::GetMiscValue(AZStd::size_t index) const
{
AZ_Assert(index < m_miscValues.size(), "Invalid Command line lookup");
if (index >= m_miscValues.size())
{
return m_emptyValue;
}
return m_miscValues[index];
// Positional arguments option value is an empty string
return GetSwitchValue("", index);
}
const CommandLine::ParamMap& CommandLine::GetSwitchList() const
[[nodiscard]] bool CommandLine::empty() const
{
return m_switches;
return m_allValues.empty();
}
auto CommandLine::size() const -> ArgumentVector::size_type
{
return m_allValues.size();
}
auto CommandLine::begin() -> ArgumentVector::iterator
{
return m_allValues.begin();
}
auto CommandLine::begin() const -> ArgumentVector::const_iterator
{
return m_allValues.begin();
}
auto CommandLine::cbegin() const -> ArgumentVector::const_iterator
{
return m_allValues.cbegin();
}
auto CommandLine::end() -> ArgumentVector::iterator
{
return m_allValues.end();
}
auto CommandLine::end() const -> ArgumentVector::const_iterator
{
return m_allValues.end();
}
auto CommandLine::cend() const -> ArgumentVector::const_iterator
{
return m_allValues.cend();
}
auto CommandLine::rbegin() -> ArgumentVector::reverse_iterator
{
return m_allValues.rbegin();
}
auto CommandLine::rbegin() const -> ArgumentVector::const_reverse_iterator
{
return m_allValues.rbegin();
}
auto CommandLine::crbegin() const -> ArgumentVector::const_reverse_iterator
{
return m_allValues.crbegin();
}
auto CommandLine::rend() -> ArgumentVector::reverse_iterator
{
return m_allValues.rend();
}
auto CommandLine::rend() const -> ArgumentVector::const_reverse_iterator
{
return m_allValues.rend();
}
auto CommandLine::crend() const -> ArgumentVector::const_reverse_iterator
{
return m_allValues.crend();
}
}
@@ -36,36 +36,47 @@ namespace AZ
public:
AZ_CLASS_ALLOCATOR(CommandLine, AZ::SystemAllocator, 0);
using ParamContainer = AZStd::vector<AZStd::string>;
struct CommandArgument
{
AZStd::string m_option;
AZStd::string m_value;
};
using ArgumentVector = AZStd::vector<CommandArgument>;
CommandLine();
/**
* Initializes a CommandLine instance which uses the provided commandLineOptionPreix for parsing switches
*/
CommandLine(AZStd::string_view commandLineOptionPrefix);
using ParamContainer = AZStd::vector<AZStd::string>;
using ParamMap = AZStd::unordered_map<AZStd::string, ParamContainer>;
/**
* Construct a command line parser.
* It will load parameters from the given ARGC/ARGV parameters instead of process command line.
* Skips over the first parameter as it assumes it is the executable name
*/
void Parse(int argc, char** argv);
/**
* Parses each element of the command line as parameter.
* Unlike the ARGC/ARGV version above, this function doesn't skip over the first parameter
* It allows for round trip conversion with the Dump() method
*/
void Parse(const ParamContainer& commandLine);
/**
* Will dump command line parameters from the CommandLine in a format such that switches
* are prefixed with the option prefix followed by their value(s) which are comma separated
* The result of this function can be supplied to Parse() to re-create an eqiuvlanet command line obect
* The result of this function can be supplied to Parse() to re-create an equivalent command line object
* Ex, If the command line has the current list of parsed miscellaneous values and switches of
* MiscValue = ["Foo", "Bat"]
* Switches = ["GameFolder" : [], "RemoteIp" : ["10.0.0.1"], "ScanFolders" : ["\a\b\c", "\d\e\f"]
* CommandLineOptionPrefix = "-/"
*
* Then the resulting dumped value would be
* Dump = ["", "Foo", "Bat", "-GameFolder", "-RemoteIp", "10.0.0.1", "-ScanFolders", "\a\b\c", "-ScanFolders", "\d\e\f"]
* NOTE: The first parameter is always empty string as the Parse function skips over it
* Dump = ["Foo", "Bat", "-GameFolder", "-RemoteIp", "10.0.0.1", "-ScanFolders", "\a\b\c", "-ScanFolders", "\d\e\f"]
*/
void Dump(ParamContainer& commandLineDumpOutput);
void Dump(ParamContainer& commandLineDumpOutput) const;
/**
* Determines whether a switch is present in the command line
@@ -97,16 +108,28 @@ namespace AZ
*/
const AZStd::string& GetMiscValue(AZStd::size_t index) const;
/*
* Return the list of parsed switches
*/
const ParamMap& GetSwitchList() const;
// Range accessors
[[nodiscard]] bool empty() const;
auto size() const -> ArgumentVector::size_type;
auto begin() -> ArgumentVector::iterator;
auto begin() const -> ArgumentVector::const_iterator;
auto cbegin() const -> ArgumentVector::const_iterator;
auto end() -> ArgumentVector::iterator;
auto end() const -> ArgumentVector::const_iterator;
auto cend() const -> ArgumentVector::const_iterator;
auto rbegin() -> ArgumentVector::reverse_iterator;
auto rbegin() const -> ArgumentVector::const_reverse_iterator;
auto crbegin() const -> ArgumentVector::const_reverse_iterator;
auto rend() -> ArgumentVector::reverse_iterator;
auto rend() const -> ArgumentVector::const_reverse_iterator;
auto crend() const -> ArgumentVector::const_reverse_iterator;
private:
void AddArgument(AZStd::string currentArg, AZStd::string& currentSwitch);
void AddArgument(AZStd::string_view currentArg, AZStd::string& currentSwitch);
void ParseOptionArgument(AZStd::string_view newOption, AZStd::string_view newValue, CommandArgument* inProgressArgument);
ParamMap m_switches;
ParamContainer m_miscValues;
ArgumentVector m_allValues;
AZStd::string m_emptyValue;
inline static constexpr size_t MaxCommandOptionPrefixes = 8;
@@ -173,7 +173,15 @@ namespace AZ
path.remove_prefix(1); // Remove the leading slash as the StackedString will add this back in.
jsonPath.Push(path);
}
Visit(visitor, jsonPath, "", *value);
// Extract the last token of the JSON pointer to use as the valueName
AZStd::string_view valueName;
size_t pointerTokenCount = pointer.GetTokenCount();
if (pointerTokenCount > 0)
{
const rapidjson::Pointer::Token& lastToken = pointer.GetTokens()[pointerTokenCount - 1];
valueName = AZStd::string_view(lastToken.name, lastToken.length);
}
Visit(visitor, jsonPath, valueName, *value);
return true;
}
}
@@ -41,68 +41,197 @@ namespace AZ::Internal
return value;
}
}
AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject(
SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
{
// projectPath needs to be an absolute path here.
using namespace AZ::SettingsRegistryMergeUtils;
bool projectJsonMerged = false;
auto projectJsonPath = projectPath / "project.json";
if (AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
{
projectJsonMerged = settingsRegistry.MergeSettingsFile(
projectJsonPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ProjectSettingsRootKey);
}
AZ::SettingsRegistryInterface::FixedValueString engineMoniker;
if (projectJsonMerged)
{
// In project.json look for the "engine" key.
auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine", ProjectSettingsRootKey);
settingsRegistry.Get(engineMoniker, engineMonikerKey);
}
return engineMoniker;
}
AZ::IO::FixedMaxPath ReconcileEngineRootFromProjectPath(SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
{
// Find the engine root via the engine manifest file and project.json
// Locate the engine manifest file and merge it to settings registry.
// Visit over the engine paths list and merge the engine.json files to settings registry.
// Merge project.json to settings registry. That will give us an "engine" key.
// When we find a match for "engine_name" value against the "engine" value from before, we can stop and use that engine root.
// Finally set the BootstrapSettingsRootKey/engine_path setting so that subsequent calls to GetEngineRoot will use that
// and avoid all this logic.
using namespace AZ::SettingsRegistryMergeUtils;
AZ::IO::FixedMaxPath engineRoot;
if (auto engineManifestPath = AZ::Utils::GetEngineManifestPath(); !engineManifestPath.empty())
{
bool manifestLoaded{false};
if (AZ::IO::SystemFile::Exists(engineManifestPath.c_str()))
{
manifestLoaded = settingsRegistry.MergeSettingsFile(
engineManifestPath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineManifestRootKey);
}
struct EngineInfo
{
AZ::IO::FixedMaxPath m_path;
AZ::SettingsRegistryInterface::FixedValueString m_moniker;
};
struct EnginePathsVisitor : public AZ::SettingsRegistryInterface::Visitor
{
void Visit(
[[maybe_unused]] AZStd::string_view path, [[maybe_unused]] AZStd::string_view valueName,
[[maybe_unused]] AZ::SettingsRegistryInterface::Type type, AZStd::string_view value) override
{
m_enginePaths.emplace_back(EngineInfo{AZ::IO::FixedMaxPath{value}.LexicallyNormal(), {}});
}
AZStd::vector<EngineInfo> m_enginePaths{};
};
EnginePathsVisitor pathVisitor;
if (manifestLoaded)
{
auto enginePathsKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engines", EngineManifestRootKey);
settingsRegistry.Visit(pathVisitor, enginePathsKey);
}
const auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
for (EngineInfo& engineInfo : pathVisitor.m_enginePaths)
{
AZ::IO::FixedMaxPath engineSettingsPath{engineInfo.m_path};
engineSettingsPath /= "engine.json";
if (AZ::IO::SystemFile::Exists(engineSettingsPath.c_str()))
{
if (settingsRegistry.MergeSettingsFile(
engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey))
{
settingsRegistry.Get(engineInfo.m_moniker, engineMonikerKey);
}
}
auto engineMoniker = Internal::GetEngineMonikerForProject(settingsRegistry, engineInfo.m_path / projectPath);
if (!engineMoniker.empty() && engineMoniker == engineInfo.m_moniker)
{
engineRoot = engineInfo.m_path;
break;
}
}
}
return engineRoot;
}
AZ::IO::FixedMaxPath ScanUpRootLocator(AZStd::string_view rootFileToLocate)
{
AZStd::fixed_string<AZ::IO::MaxPathLength> executableDir;
if (Utils::GetExecutableDirectory(executableDir.data(), executableDir.capacity()) == Utils::ExecutablePathResult::Success)
{
// Update the size value of the executable directory fixed string to correctly be the length of the null-terminated string
// stored within it
executableDir.resize_no_construct(AZStd::char_traits<char>::length(executableDir.data()));
}
AZ::IO::FixedMaxPath engineRootCandidate{ executableDir };
bool rootPathVisited = false;
do
{
if (AZ::IO::SystemFile::Exists((engineRootCandidate / rootFileToLocate).c_str()))
{
return engineRootCandidate;
}
// Note for posix filesystems the parent directory of '/' is '/' and for windows
// the parent directory of 'C:\\' is 'C:\\'
// Validate that the parent directory isn't itself, that would imply
// that it is the filesystem root path
AZ::IO::PathView parentPath = engineRootCandidate.ParentPath();
rootPathVisited = (engineRootCandidate == parentPath);
// Recurse upwards one directory
engineRootCandidate = AZStd::move(parentPath);
} while (!rootPathVisited);
return {};
}
} // namespace AZ::Internal
namespace AZ::SettingsRegistryMergeUtils
{
AZ::IO::FixedMaxPath GetAppRoot(SettingsRegistryInterface* settingsRegistry)
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry)
{
constexpr size_t MaxAppRootPathsToScan = 16;
AZStd::fixed_vector<AZStd::string_view, MaxAppRootPathsToScan> appRootScanPaths;
AZ::IO::FixedMaxPath engineRoot;
// Check the commandLine for --app-root parameter
// If it exist use that instead
auto appRootOverrideKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/app-root", AZ::SettingsRegistryMergeUtils::CommandLineSwitchRootKey);
if (settingsRegistry)
// This is the 'external' engine root key, as in passed from command-line or .setreg files.
auto engineRootKey = SettingsRegistryInterface::FixedValueString::format("%s/engine_path", BootstrapSettingsRootKey);
if (settingsRegistry.Get(engineRoot.Native(), engineRootKey); !engineRoot.empty())
{
AZ::IO::FixedMaxPath appRootOverridePath;
if (settingsRegistry->Get(appRootOverridePath.Native(), appRootOverrideKey))
{
return appRootOverridePath;
}
return engineRoot;
}
AZStd::optional<AZStd::fixed_string<AZ::IO::MaxPathLength>> appRootPath = Utils::GetDefaultAppRootPath();
if (appRootPath.has_value())
// We can scan up from exe directory to find engine.json, use that for engine root if it exists.
if (engineRoot = Internal::ScanUpRootLocator("engine.json"); !engineRoot.empty())
{
appRootScanPaths.push_back(*appRootPath);
settingsRegistry.Set(engineRootKey, engineRoot.c_str());
return engineRoot;
}
AZStd::fixed_string<AZ::IO::MaxPathLength> executableDir;
AZ::Utils::ExecutablePathResult pathResult = Utils::GetExecutableDirectory(executableDir.data(), executableDir.capacity());
if (pathResult == Utils::ExecutablePathResult::Success)
AZ::IO::FixedMaxPath projectRoot = FindProjectRoot(settingsRegistry);
if (projectRoot.empty())
{
// Update the size value of the executable directory fixed string to correctly be the length of the null-terminated string stored within it
executableDir.resize_no_construct(AZStd::char_traits<char>::length(executableDir.data()));
if (auto foundIt = AZStd::find(appRootScanPaths.begin(), appRootScanPaths.end(), executableDir);
foundIt == appRootScanPaths.end())
{
appRootScanPaths.push_back(executableDir);
}
return {};
}
for (AZStd::string_view appRootScanPath : appRootScanPaths)
// Use the project.json and engine manifest to locate the engine root.
if (engineRoot = Internal::ReconcileEngineRootFromProjectPath(settingsRegistry, projectRoot); !engineRoot.empty())
{
AZ::IO::FixedMaxPath appRootCandidate{ appRootScanPath };
// Search for the application root
bool rootPathVisited = false;
do
{
if (AZ::IO::SystemFile::Exists((appRootCandidate / "bootstrap.cfg").c_str()))
{
return appRootCandidate;
}
// Validate that the parent directory isn't itself, that would imply
// that it is the root path
AZ::IO::PathView parentPath = appRootCandidate.ParentPath();
rootPathVisited = appRootCandidate == parentPath;
// Recurse upwards one directory
appRootCandidate = AZStd::move(parentPath);
} while (!rootPathVisited);
// Note for posix filesystems the parent directory of '/' is '/' and for windows
// the parent directory of 'C:\\' is 'C:\\'
settingsRegistry.Set(engineRootKey, engineRoot.c_str());
return engineRoot;
}
return {};
}
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry)
{
AZ::IO::FixedMaxPath projectRoot;
// This is the 'external' project root key, as in passed from command-line or .setreg files.
auto projectRootKey = SettingsRegistryInterface::FixedValueString::format("%s/project_path", BootstrapSettingsRootKey);
if (settingsRegistry.Get(projectRoot.Native(), projectRootKey))
{
return projectRoot;
}
if (projectRoot = Internal::ScanUpRootLocator("project.json"); !projectRoot.empty())
{
settingsRegistry.Set(projectRootKey, projectRoot.c_str());
return projectRoot;
}
return {};
}
@@ -111,9 +240,9 @@ namespace AZ::SettingsRegistryMergeUtils
constexpr AZStd::string_view commentPrefixes = ";#";
for (char commentPrefix : commentPrefixes)
{
if (line.starts_with(commentPrefix))
if (size_t commentOffset = line.find(commentPrefix); commentOffset != AZStd::string_view::npos)
{
return {};
return line.substr(0, commentOffset);
}
}
@@ -123,7 +252,7 @@ namespace AZ::SettingsRegistryMergeUtils
AZStd::string_view ConfigParserSettings::DefaultSectionHeaderFilter(AZStd::string_view line)
{
AZStd::string_view sectionName;
constexpr char sectionHeaderStart= '[';
constexpr char sectionHeaderStart = '[';
constexpr char sectionHeaderEnd = ']';
if (line.starts_with(sectionHeaderStart) && line.ends_with(sectionHeaderEnd))
{
@@ -184,7 +313,7 @@ namespace AZ::SettingsRegistryMergeUtils
void QuerySpecializationsFromRegistry(SettingsRegistryInterface& registry, SettingsRegistryInterface::Specializations& specializations)
{
// Append any specializations stored in the registry
// Append any specializations stored in the registry
struct SpecializationsVisitor
: AZ::SettingsRegistryInterface::Visitor
{
@@ -222,7 +351,7 @@ namespace AZ::SettingsRegistryMergeUtils
bool MergeSettingsToRegistry_ConfigFile(SettingsRegistryInterface& registry, AZStd::string_view filePath,
const ConfigParserSettings& configParserSettings)
{
auto configPath = GetAppRoot(&registry) / filePath;
auto configPath = FindEngineRoot(registry) / filePath;
IO::SystemFile configFile;
if (!configFile.Open(configPath.c_str(), IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY))
{
@@ -348,9 +477,13 @@ namespace AZ::SettingsRegistryMergeUtils
ConfigParserSettings parserSettings;
parserSettings.m_commentPrefixFunc = [](AZStd::string_view line) -> AZStd::string_view
{
if (line.starts_with("--") || line.starts_with(';') || line.starts_with('#'))
constexpr AZStd::string_view commentPrefixes[]{ "--", ";","#" };
for (AZStd::string_view commentPrefix : commentPrefixes)
{
return {};
if (size_t commentOffset = line.find(commentPrefix); commentOffset != AZStd::string_view::npos)
{
return line.substr(0, commentOffset);
}
}
return line;
};
@@ -365,71 +498,104 @@ namespace AZ::SettingsRegistryMergeUtils
registry.Set(FilePathKey_BinaryFolder, path.LexicallyNormal().Native());
// Engine root folder - corresponds to the @engroot@ and @devroot@ aliases
AZ::IO::FixedMaxPath appRoot = GetAppRoot(&registry);
registry.Set(FilePathKey_EngineRootFolder, appRoot.LexicallyNormal().Native());
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
registry.Set(FilePathKey_EngineRootFolder, engineRoot.LexicallyNormal().Native());
constexpr size_t bufferSize = 64;
auto buffer = AZStd::fixed_string<bufferSize>::format("%s/sys_game_folder", BootstrapSettingsRootKey);
auto buffer = AZStd::fixed_string<bufferSize>::format("%s/project_path", BootstrapSettingsRootKey);
AZStd::string_view projectPath(buffer);
AZ::SettingsRegistryInterface::FixedValueString projectPathKey(buffer);
SettingsRegistryInterface::FixedValueString projectPathValue;
if (registry.Get(projectPathValue, projectPath))
if (registry.Get(projectPathValue, projectPathKey))
{
// Cache folder
// Get the name of the asset platform assigned by the bootstrap. First check for platform version such as "windows_assets"
// and if that's missing just get "asset".
// and if that's missing just get "assets".
constexpr char platformName[] = AZ_TRAIT_OS_PLATFORM_CODENAME_LOWER;
SettingsRegistryInterface::FixedValueString assetPlatform;
buffer = AZStd::fixed_string<bufferSize>::format("%s/%s_assets", BootstrapSettingsRootKey, platformName);
AZStd::string_view assetPlatformatPath(buffer);
if (!registry.Get(assetPlatform, assetPlatformatPath))
AZStd::string_view assetPlatformKey(buffer);
if (!registry.Get(assetPlatform, assetPlatformKey))
{
buffer = AZStd::fixed_string<bufferSize>::format("%s/assets", BootstrapSettingsRootKey);
assetPlatformatPath = AZStd::string_view(buffer);
if (!registry.Get(assetPlatform, assetPlatformatPath))
{
return;
}
assetPlatformKey = AZStd::string_view(buffer);
registry.Get(assetPlatform, assetPlatformKey);
}
// Source game folder - corresponds to the @devassets@ alias
path = appRoot / projectPathValue;
// Project path - corresponds to the @devassets@ alias
// NOTE: Here we append to engineRoot, but if projectPathValue is absolute then engineRoot is discarded.
path = engineRoot / projectPathValue;
AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(path.c_str()),
R"(Project Path "%s" does not exist. Is the "sys_game_folder" entry set to valid project folder?)"
, path.c_str());
registry.Set(FilePathKey_SourceGameFolder, path.LexicallyNormal().Native());
R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to valid absolute path?)"
, path.c_str(), aznumeric_cast<int>(projectPathKey.size()), projectPathKey.data());
// Source game name - The filename of the project directory and the name of the project
registry.Set(FilePathKey_SourceGameName, path.Filename().Native());
AZ::IO::FixedMaxPath normalizedProjectPath = path.LexicallyNormal();
registry.Set(FilePathKey_ProjectPath, normalizedProjectPath.Native());
// Cache root folder - corresponds to the @root@ alias
#if AZ_TRAIT_USE_ASSET_CACHE_FOLDER
path = appRoot / "Cache" / projectPathValue / assetPlatform;
#else
// Use the Engine Root/App Root as the Asset root directory in this case
path = appRoot;
#endif
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
// check for a default write storage path, fall back to the cache root if not
// Add an alias to the project "user" directory
AZ::IO::FixedMaxPath projectUserPath = (normalizedProjectPath / "user").LexicallyNormal();
registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
// check for a default write storage path, fall back to the project's user/ directory if not
AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
registry.Set(FilePathKey_DevWriteStorage,
devWriteStorage.has_value() ?
devWriteStorage.value() :
path.LexicallyNormal().Native());
registry.Set(FilePathKey_DevWriteStorage, devWriteStorage.has_value()
? devWriteStorage.value()
: projectUserPath.Native());
// Cache game folder - corresponds to the @assets@ alias
AZStd::to_lower(projectPathValue.begin(), projectPathValue.end());
path /= projectPathValue;
registry.Set(FilePathKey_CacheGameFolder, path.LexicallyNormal().Native());
// Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
auto projectNameKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey)
+ "/project_name";
AZ::SettingsRegistryInterface::FixedValueString projectName;
if (!registry.Get(projectName, projectNameKey))
{
projectName = path.Filename().Native();
registry.Set(projectNameKey, projectName);
}
// Cache folders - sets up various paths in registry for the cache.
// Make sure the asset platform is set before setting these cache paths.
if (!assetPlatform.empty())
{
// Cache: project root - no corresponding fileIO alias, but this is where the asset database lives.
// A registry override is accepted using the "project_cache_path" key.
buffer = AZStd::fixed_string<bufferSize>::format("%s/project_cache_path", BootstrapSettingsRootKey);
AZStd::string_view projectCacheRootOverrideKey(buffer);
// Clear path to make sure that the `project_cache_path` value isn't concatenated to the project path
path.clear();
if (registry.Get(path.Native(), projectCacheRootOverrideKey))
{
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
path /= assetPlatform;
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
}
else
{
// Cache: root - same as the @root@ alias, this is the starting path for cache files.
path = normalizedProjectPath / "Cache";
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
path /= assetPlatform;
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
}
}
}
else
{
AZ_TracePrintf("SettingsRegistryMergeUtils",
R"(Current Project game folder "%.*s" isn't set in the Settings Registry. Project-related filepaths will not be set)" "\n",
aznumeric_cast<int>(projectPath.size()), projectPath.data());
R"(Project path isn't set in the Settings Registry at "%.*s". Project-related filepaths will not be set)" "\n",
aznumeric_cast<int>(projectPathKey.size()), projectPathKey.data());
}
#if !AZ_TRAIT_USE_ASSET_CACHE_FOLDER
// Setup the cache and user paths for Platforms where the Asset Cache Folder isn't used
path = engineRoot;
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
registry.Set(FilePathKey_DevWriteStorage, path.LexicallyNormal().Native());
registry.Set(FilePathKey_ProjectUserPath, (path / "user").LexicallyNormal().Native());
#endif // AZ_TRAIT_USE_ASSET_CACHE_FOLDER
}
void MergeSettingsToRegistry_TargetBuildDependencyRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
@@ -535,7 +701,7 @@ namespace AZ::SettingsRegistryMergeUtils
const SettingsRegistryInterface::Specializations& specializations, AZStd::vector<char>* scratchBuffer)
{
AZ::SettingsRegistryInterface::FixedValueString sourceGamePath;
if (registry.Get(sourceGamePath, FilePathKey_SourceGameFolder))
if (registry.Get(sourceGamePath, FilePathKey_ProjectPath))
{
AZ::IO::FixedMaxPath mergePath{ sourceGamePath };
mergePath /= SettingsRegistryInterface::RegistryFolder;
@@ -543,26 +709,41 @@ namespace AZ::SettingsRegistryMergeUtils
}
}
void MergeSettingsToRegistry_DevRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
void MergeSettingsToRegistry_UserRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
const SettingsRegistryInterface::Specializations& specializations, AZStd::vector<char>* scratchBuffer)
{
// Unlike other paths, the path can't be overwritten by the dev settings because that would create a circular dependency.
auto mergePath = GetAppRoot(&registry);
mergePath /= SettingsRegistryInterface::DevUserRegistryFolder;
registry.MergeSettingsFolder(mergePath.Native(), specializations, platform, "", scratchBuffer);
AZ::IO::FixedMaxPath projectUserPath;
if (registry.Get(projectUserPath.Native(), FilePathKey_ProjectPath))
{
projectUserPath /= SettingsRegistryInterface::DevUserRegistryFolder;
registry.MergeSettingsFolder(projectUserPath.Native(), specializations, platform, "", scratchBuffer);
}
}
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, const AZ::CommandLine& commandLine, bool executeCommands)
{
const size_t regsetSwitchValues = commandLine.GetNumSwitchValues("regset");
for (size_t regsetIndex = 0; regsetIndex < regsetSwitchValues; ++regsetIndex)
// Iterate over all the command line options in order to parse the --regset and --regremove
// arguments in the order they were supplied
for (const CommandLine::CommandArgument& commandArgument : commandLine)
{
AZStd::string_view regsetValue = commandLine.GetSwitchValue("regset", regsetIndex);
if (!registry.MergeCommandLineArgument(regsetValue, AZStd::string_view{}))
if (commandArgument.m_option == "regset")
{
AZ_Warning("SettingsRegistryMergeUtils", false, "Unable to parse argument for --regset with value of %.*s.",
aznumeric_cast<int>(regsetValue.size()), regsetValue.data());
continue;
if (!registry.MergeCommandLineArgument(commandArgument.m_value, AZStd::string_view{}))
{
AZ_Warning("SettingsRegistryMergeUtils", false, "Unable to parse argument for --regset with value of %s.",
commandArgument.m_value.c_str());
continue;
}
}
if (commandArgument.m_option == "regremove")
{
if (!registry.Remove(commandArgument.m_value))
{
AZ_Warning("SettingsRegistryMergeUtils", false, "Unable to remove value at JSON Pointer %s for --regremove.",
commandArgument.m_value.data());
continue;
}
}
}
@@ -585,16 +766,8 @@ namespace AZ::SettingsRegistryMergeUtils
DumpSettingsRegistryToStream(registry, regdumpValue, outputStream, dumperSettings);
}
const size_t regdumpallSwitchValues = commandLine.GetNumSwitchValues("regdumpall");
if (regdumpallSwitchValues > 0)
if (commandLine.HasSwitch("regdumpall"))
{
AZStd::string_view regdumpallValue = commandLine.GetSwitchValue("regdumpall", 0);
if (regdumpallValue.empty())
{
AZ_Warning("SettingsRegistryMergeUtils", false, "Unable to parse argument for --regdumpall with value of %.*s.",
aznumeric_cast<int>(regdumpallValue.size()), regdumpallValue.data());
}
DumperSettings dumperSettings{ prettifyOutput };
AZ::IO::StdoutStream outputStream;
DumpSettingsRegistryToStream(registry, "", outputStream, dumperSettings);
@@ -602,85 +775,77 @@ namespace AZ::SettingsRegistryMergeUtils
}
}
void MergeSettingsToRegistry_StoreCommandLine(SettingsRegistryInterface& registry, const AZ::CommandLine& commandLine)
void StoreCommandLineToRegistry(SettingsRegistryInterface& registry, const AZ::CommandLine& commandLine)
{
// Clear out any existing CommandLine settings
registry.Remove(CommandLineRootKey);
// Add the positional arguments into the Settings Registry
AZ::SettingsRegistryInterface::FixedValueString miscValueKey{ CommandLineMiscValuesRootKey };
size_t miscKeyRootSize = miscValueKey.size();
for (size_t miscIndex = 0; miscIndex < commandLine.GetNumMiscValues(); ++miscIndex)
{
// Push an array for each positional entry
miscValueKey += AZ::SettingsRegistryInterface::FixedValueString::format("/%zu", miscIndex);
registry.Set(miscValueKey, commandLine.GetMiscValue(miscIndex));
miscValueKey.resize(miscKeyRootSize);
}
// Add the option arguments into the SettingsRegistry
for (const auto& [commandOption, value] : commandLine.GetSwitchList())
{
AZ::SettingsRegistryInterface::FixedValueString switchKey{ CommandLineSwitchRootKey };
switchKey += '/';
switchKey += commandOption;
size_t switchKeyRootSize = switchKey.size();
// Associate an empty array with the commandOption by default
rapidjson::Document commandSwitchDocument;
rapidjson::Pointer pointer(switchKey.c_str(), switchKey.length());
pointer.Set(commandSwitchDocument, rapidjson::Value(rapidjson::kArrayType));;
rapidjson::StringBuffer documentBuffer;
rapidjson::Writer documentWriter(documentBuffer);
commandSwitchDocument.Accept(documentWriter);
registry.MergeSettings(AZStd::string_view{ documentBuffer.GetString(), documentBuffer.GetSize() },
AZ::SettingsRegistryInterface::Format::JsonMergePatch);
for (size_t switchIndex = 0; switchIndex < value.size(); ++switchIndex)
{
// Push an array for each positional entry
switchKey += AZ::SettingsRegistryInterface::FixedValueString::format("/%zu", switchIndex);
registry.Set(switchKey, value[switchIndex]);
switchKey.resize(switchKeyRootSize);
}
AZ::SettingsRegistryInterface::FixedValueString commandLinePath{ CommandLineRootKey };
const size_t commandLineRootSize = commandLinePath.size();
size_t argumentIndex{};
for (const CommandLine::CommandArgument& commandArgument : commandLine)
{
commandLinePath += AZ::SettingsRegistryInterface::FixedValueString::format("/%zu", argumentIndex);
registry.Set(commandLinePath + "/Option", commandArgument.m_option);
registry.Set(commandLinePath + "/Value", commandArgument.m_value);
++argumentIndex;
commandLinePath.resize(commandLineRootSize);
}
}
bool GetCommandLineFromRegistry(SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
{
struct CommandLineVisitor
: AZ::SettingsRegistryInterface::Visitor
{
void Visit(AZStd::string_view, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type
, AZStd::string_view value) override
{
if (valueName == "Option" && !value.empty())
{
m_arguments.push_back(AZStd::string::format("--%.*s", aznumeric_cast<int>(value.size()), value.data()));
}
else if (valueName == "Value" && !value.empty())
{
m_arguments.push_back(value);
}
}
// The first parameter is skipped by the ComamndLine::Parse function so initialize
// the container with one empty element
AZ::CommandLine::ParamContainer m_arguments{ 1 };
};
CommandLineVisitor commandLineVisitor;
if (!registry.Visit(commandLineVisitor, AZ::SettingsRegistryMergeUtils::CommandLineRootKey))
{
return false;
}
commandLine.Parse(commandLineVisitor.m_arguments);
return true;
}
bool DumpSettingsRegistryToStream(SettingsRegistryInterface& registry, AZStd::string_view key,
AZ::IO::GenericStream& stream, const DumperSettings& dumperSettings)
{
struct SettingsExportVisitor
: SettingsRegistryInterface::Visitor
{
using SettingsWriter = AZStd::variant<
rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter>,
rapidjson::Writer<AZ::IO::RapidJSONStreamWriter>>;
SettingsWriter m_writer;
AZ::IO::RapidJSONStreamWriter m_rapidJsonStream;
DumperSettings m_dumperSettings;
AZStd::stack<bool> m_includeNameStack;
bool m_includeName{};
bool m_result{ true };
SettingsExportVisitor(AZ::IO::GenericStream& stream, const DumperSettings& dumperSettings)
: m_rapidJsonStream{ &stream }
, m_dumperSettings{ dumperSettings }
, m_writer{ m_stringBuffer }
{
if (m_dumperSettings.m_prettifyOutput)
{
m_writer.emplace<rapidjson::PrettyWriter<AZ::IO::RapidJSONStreamWriter>>(m_rapidJsonStream);
}
else
{
m_writer.emplace<rapidjson::Writer<AZ::IO::RapidJSONStreamWriter>>(m_rapidJsonStream);
}
}
void WriteName(AZStd::string_view name)
bool WriteName(AZStd::string_view name)
{
if (m_includeName)
// Write the Key if the include name stack the top element is true
if (!m_includeNameStack.empty() && m_includeNameStack.top())
{
AZStd::visit([&name](auto&& writer)
{
writer.Key(name.data(), aznumeric_caster(name.size()));
}, m_writer);
return m_writer.Key(name.data(), aznumeric_caster(name.size()), false);
}
return true;
}
AZ::SettingsRegistryInterface::VisitResponse Traverse(
AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::VisitAction action,
@@ -694,60 +859,43 @@ namespace AZ::SettingsRegistryMergeUtils
if (action == AZ::SettingsRegistryInterface::VisitAction::Begin)
{
AZ_Assert(type == AZ::SettingsRegistryInterface::Type::Object || type == AZ::SettingsRegistryInterface::Type::Array,
"Unexpected type visited: %i.", type);
WriteName(valueName);
m_result = m_result && WriteName(valueName);
if (type == AZ::SettingsRegistryInterface::Type::Object)
{
auto StartObject = [](auto&& writer)
m_result = m_result && m_writer.StartObject();
if (m_result)
{
return writer.StartObject();
};
m_result = m_result && AZStd::visit(StartObject, m_writer);
m_includeNameStack.push(true);
m_includeName = true;
m_includeNameStack.push(true);
}
}
else
{
auto StartArray = [](auto&& writer)
m_result = m_result && m_writer.StartArray();
if (m_result)
{
return writer.StartArray();
};
m_result = m_result && AZStd::visit(StartArray, m_writer);
m_includeNameStack.push(false);
m_includeName = false;
m_includeNameStack.push(false);
}
}
}
else if (action == AZ::SettingsRegistryInterface::VisitAction::End)
{
if (type == AZ::SettingsRegistryInterface::Type::Object)
{
auto EndObject = [](auto&& writer)
{
return writer.EndObject();
};
m_result = m_result && AZStd::visit(EndObject, m_writer);
m_result = m_result && m_writer.EndObject();
}
else
{
auto EndArray = [](auto&& writer)
{
return writer.EndArray();
};
m_result = m_result && AZStd::visit(EndArray, m_writer);
m_result = m_result && m_writer.EndArray();
}
AZ_Assert(!m_includeNameStack.empty(), "Attempting to close a json array or object that wasn't started.");
m_includeNameStack.pop();
m_includeName = !m_includeNameStack.empty() ? m_includeNameStack.top() : true;
}
else if (type == AZ::SettingsRegistryInterface::Type::Null)
else
{
WriteName(valueName);
auto WriteNull = [](auto&& writer)
if (type == AZ::SettingsRegistryInterface::Type::Null)
{
return writer.Null();
};
m_result = m_result && AZStd::visit(WriteNull, m_writer);
m_result = m_result && WriteName(valueName) && m_writer.Null();
}
}
return m_result ?
@@ -757,42 +905,27 @@ namespace AZ::SettingsRegistryMergeUtils
void Visit(AZStd::string_view, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, bool value)
{
WriteName(valueName);
auto WriteBool = [value](auto&& writer)
{
return writer.Bool(value);
};
m_result = m_result && AZStd::visit(WriteBool, m_writer);
m_result = m_result && WriteName(valueName) && m_writer.Bool(value);
}
void Visit(AZStd::string_view, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZ::s64 value)
void Visit(AZStd::string_view, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZ::s64 value) override
{
WriteName(valueName);
auto WriteInt64 = [value](auto&& writer)
{
return writer.Int64(value);
};
m_result = m_result && AZStd::visit(WriteInt64, m_writer);
m_result = m_result && WriteName(valueName) && m_writer.Int64(value);
}
void Visit(AZStd::string_view, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZ::u64 value) override
{
m_result = m_result && WriteName(valueName) && m_writer.Uint64(value);
}
void Visit(AZStd::string_view, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, double value)
{
WriteName(valueName);
auto WriteDouble = [value](auto&& writer)
{
return writer.Double(value);
};
m_result = m_result && AZStd::visit(WriteDouble, m_writer);
m_result = m_result && WriteName(valueName) && m_writer.Double(value);
}
void Visit(AZStd::string_view, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value)
{
WriteName(valueName);
auto WriteString = [&value](auto&& writer)
{
return writer.String(value.data(), aznumeric_caster(value.size()));
};
m_result = m_result && AZStd::visit(WriteString, m_writer);
m_result = m_result && WriteName(valueName) && m_writer.String(value.data(), aznumeric_caster(value.size()));
}
bool Finalize()
@@ -802,8 +935,46 @@ namespace AZ::SettingsRegistryMergeUtils
AZ_Assert(false, "m_includeNameStack is expected to be empty. This means that there was an object or array what wasn't closed.");
return false;
}
return m_result;
// Root the JSON document underneath the JSON pointer prefix if non-empty
// Parse non-anchored JSON data into a document and then re-root
// that document under the prefix value
rapidjson::Document document;
document.Parse(m_stringBuffer.GetString(), m_stringBuffer.GetSize());
rapidjson::Document rootDocument;
AZStd::string_view jsonPrefix{ m_dumperSettings.m_jsonPointerPrefix };
if (!jsonPrefix.empty())
{
rapidjson::Pointer rootPointer(jsonPrefix.data(), jsonPrefix.size());
rapidjson::SetValueByPointer(rootDocument, rootPointer, document);
}
else
{
rootDocument = AZStd::move(document);
}
if (m_dumperSettings.m_prettifyOutput)
{
rapidjson::PrettyWriter settingsWriter(m_rapidJsonStream);
rootDocument.Accept(settingsWriter);
}
else
{
rapidjson::Writer settingsWriter(m_rapidJsonStream);
rootDocument.Accept(settingsWriter);
}
return true;
}
rapidjson::StringBuffer m_stringBuffer;
rapidjson::Writer<rapidjson::StringBuffer> m_writer;
AZ::IO::RapidJSONStreamWriter m_rapidJsonStream;
DumperSettings m_dumperSettings;
AZStd::stack<bool> m_includeNameStack;
bool m_result{ true };
};
SettingsExportVisitor visitor(stream, dumperSettings);
@@ -36,27 +36,45 @@ namespace AZ::SettingsRegistryMergeUtils
inline static constexpr char FilePathsRootKey[] = "/Amazon/AzCore/Runtime/FilePaths";
inline static constexpr char FilePathKey_BinaryFolder[] = "/Amazon/AzCore/Runtime/FilePaths/BinaryFolder";
inline static constexpr char FilePathKey_EngineRootFolder[] = "/Amazon/AzCore/Runtime/FilePaths/EngineRootFolder";
//! Stores the absolute path to root of a project's cache. No asset platform in this path, this is where the asset database file lives.
//! i.e. <ProjectPath>/Cache
inline static constexpr char FilePathKey_CacheProjectRootFolder[] = "/Amazon/AzCore/Runtime/FilePaths/CacheProjectRootFolder";
//! Stores the absolute path to the cache root for an asset platform. This is the @root@ alias.
//! i.e. <ProjectPath>/Cache/<assetplatform>
inline static constexpr char FilePathKey_CacheRootFolder[] = "/Amazon/AzCore/Runtime/FilePaths/CacheRootFolder";
inline static constexpr char FilePathKey_CacheGameFolder[] = "/Amazon/AzCore/Runtime/FilePaths/CacheGameFolder";
inline static constexpr char FilePathKey_SourceGameFolder[] = "/Amazon/AzCore/Runtime/FilePaths/SourceGameFolder";
//! Stores the filename of the Game Project Directory which is equivalent to the project name
inline static constexpr char FilePathKey_SourceGameName[] = "/Amazon/AzCore/Runtime/FilePaths/SourceGameName";
//! Stores the absolute path of the Game Project Directory
inline static constexpr char FilePathKey_ProjectPath[] = "/Amazon/AzCore/Runtime/FilePaths/SourceProjectPath";
//! Store the absolute path to the Projects "user" directory, which is a transient directory where per user
//! project settings can be stored
inline static constexpr char FilePathKey_ProjectUserPath[] = "/Amazon/AzCore/Runtime/FilePaths/SourceProjectUserPath";
//! Development write storage path may be considered temporary or cache storage on some platforms
inline static constexpr char FilePathKey_DevWriteStorage[] = "/Amazon/AzCore/Runtime/FilePaths/DevWriteStorage";
//! Root key for where command line are stored at witin the settings registry
//! Root key for where command line are stored at within the settings registry
inline static constexpr char CommandLineRootKey[] = "/Amazon/AzCore/Runtime/CommandLine";
//! Root key for command line switches(arguments that start with "-" or "--")
inline static constexpr char CommandLineSwitchRootKey[] = "/Amazon/AzCore/Runtime/CommandLine/Switches";
//! Root key for command line positional arguments
inline static constexpr char CommandLineMiscValuesRootKey[] = "/Amazon/AzCore/Runtime/CommandLine/MiscValues";
//! Examines the Settings Registry for a "/Amazon/CommandLine/Switches/app-root" key
//! to use as an override for the Application Root.
//! If that key is not found, it then checks the AZ::Utils::GetDefaultAppRootPath and returns that if it is value
//! Root key where raw project settings (project.json) file is merged to settings registry
inline static constexpr char ProjectSettingsRootKey[] = "/Amazon/Project/Settings";
//! Root key where raw engine manifest (o3de_manifest.json) file is merged to settings registry
inline static constexpr char EngineManifestRootKey[] = "/Amazon/Engine/Manifest";
//! Root key where raw engine settings (engine.json) file is merged to settings registry
inline static constexpr char EngineSettingsRootKey[] = "/Amazon/Engine/Settings";
//! Examines the Settings Registry for a "${BootstrapSettingsRootKey}/engine_path" key
//! to use as an override for the Engine Root.
//! Otherwise a directory walk upwards from the executable directory is performed
//! to find the boostrap.cfg file which will be used as the app root
AZ::IO::FixedMaxPath GetAppRoot(SettingsRegistryInterface* settingsRegistry = nullptr);
//! to find the engine.json file which will be used as the engine root
//! If it's still not found, attempt to find the project (by similar means) then reconcile the
//! engine root by inspecting project.json and the engine manifest file.
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry);
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry);
//! Query the specializations that will be used when loading the Settings Registry.
//! The SpecializationsRootKey is visited to retrieve any specializations stored within that section of that registry
@@ -151,16 +169,18 @@ namespace AZ::SettingsRegistryMergeUtils
void MergeSettingsToRegistry_ProjectRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
const SettingsRegistryInterface::Specializations& specializations, AZStd::vector<char>* scratchBuffer = nullptr);
//! Adds the development settings added by individual users to the Settings Registry.
//! Adds the development settings added by individual users of the project to the Settings Registry.
//! Note that this function is only called in development builds and is compiled out in release builds.
void MergeSettingsToRegistry_DevRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
void MergeSettingsToRegistry_UserRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
const SettingsRegistryInterface::Specializations& specializations, AZStd::vector<char>* scratchBuffer = nullptr);
//! Adds the settings set through the command line to the Settings Registry. This will also execute any Settings
//! Registry related arguments. Note that --set will be run first and all other commands are run afterwards and only
//! if executeCommands is true. The following options are supported:
//! Registry related arguments. Note that --regset and -regremove will run in the order in which they are parsed
//! --regset <arg> Sets a value in the registry. See MergeCommandLineArgument for options for <arg>
//! example: --regset "/My/String/Value=String value set"
//! --regremove <arg> Removes a value in the registry
//! example: --regremove "/My/String/Value"
//! only when executeCommands is true are the following options supported:
//! --regdump <path> Dumps the content of the key at path and all it's content/children to output.
//! example: --regdump /My/Array/With/Objects
//! --regdumpall Dumps the entire settings registry to output.
@@ -169,7 +189,11 @@ namespace AZ::SettingsRegistryMergeUtils
//! Stores the command line settings into the Setting Registry
//! The arguments can be used later anywhere the command line is needed
void MergeSettingsToRegistry_StoreCommandLine(SettingsRegistryInterface& registry, const AZ::CommandLine& commandLine);
void StoreCommandLineToRegistry(SettingsRegistryInterface& registry, const AZ::CommandLine& commandLine);
//! Query the command line settings from the Setting Registry and stores them
//! into the AZ::CommandLine instance
bool GetCommandLineFromRegistry(SettingsRegistryInterface& registry, AZ::CommandLine& commandLine);
//! Structure for configuring how values should be dumped from the Settings Registry
struct DumperSettings
@@ -179,14 +203,25 @@ namespace AZ::SettingsRegistryMergeUtils
//! Include filter which is used to indicate which paths of the Settings Registry
//! should be traversed.
//! If the include filter is empty then all paths underneath the JSON pointer path are included
//! otherwise the include filter invoked and if it returns true does it proceed with traversal continues down the path
//! otherwise the include filter invoked and if it returns true does it proceed with traversal down the path
//! The supplied JSON pointer will be a complete path from the root of the registry
AZStd::function<bool(AZStd::string_view path)> m_includeFilter;
//! JSON pointer prefix to dump all settings underneath
//! For example if the prefix is "/Amazon/Settings", then the dumped settings will be placed underneath
//! an object at that path
//! """
//! {
//! "Amazon":{
//! "Settings":{ <Dumped values> }
//! }
//! }
AZStd::string_view m_jsonPointerPrefix;
};
//! Dumps supplied settings registry from the path specified by key if it exist the the AZ::IO::GenericStream
//! key is a JSON pointer path to dumping settings recursively from
//! stream is an AZ::IO::GenericStream that supports writing
//! dumperSettings are used to determine how to format the dumped output
//! @param key is a JSON pointer to recursively dump settings from
//! @param stream is an AZ::IO::GenericStream that supports writing
//! @param dumperSettings are used to determine how to format the dumped output
bool DumpSettingsRegistryToStream(SettingsRegistryInterface& registry, AZStd::string_view key,
AZ::IO::GenericStream& stream, const DumperSettings& dumperSettings);
@@ -418,36 +418,37 @@ namespace AZ::StringFunc::Internal
return Strip(inout, { &stripCharacter, 1 }, bCaseSensitive, bStripBeginning, bStripEnding);
}
AZStd::string_view LStrip(AZStd::string_view in, AZStd::string_view stripCharacters)
{
if (size_t pos = in.find_first_not_of(stripCharacters); pos != AZStd::string_view::npos)
{
return in.substr(pos);
}
return {};
};
AZStd::string_view RStrip(AZStd::string_view in, AZStd::string_view stripCharacters)
{
if (size_t pos = in.find_last_not_of(stripCharacters); pos != AZStd::string_view::npos)
{
return in.substr(0, pos < in.size() ? pos + 1 : pos);
}
return {};
};
AZStd::string_view StripEnds(AZStd::string_view in, AZStd::string_view stripCharacters)
{
return LStrip(RStrip(in, stripCharacters), stripCharacters);
};
}
namespace AZ
{
namespace StringFunc
{
AZStd::string_view LStrip(AZStd::string_view in, AZStd::string_view stripCharacters)
{
if (size_t pos = in.find_first_not_of(stripCharacters); pos != AZStd::string_view::npos)
{
return in.substr(pos);
}
return {};
};
AZStd::string_view RStrip(AZStd::string_view in, AZStd::string_view stripCharacters)
{
if (size_t pos = in.find_last_not_of(stripCharacters); pos != AZStd::string_view::npos)
{
return in.substr(0, pos < in.size() ? pos + 1 : pos);
}
return {};
};
AZStd::string_view StripEnds(AZStd::string_view in, AZStd::string_view stripCharacters)
{
return LStrip(RStrip(in, stripCharacters), stripCharacters);
};
bool Equal(const char* inA, const char* inB, bool bCaseSensitive /*= false*/, size_t n /*= 0*/)
{
if (!inA || !inB)
@@ -483,6 +484,14 @@ namespace AZ
}
}
}
bool Equal(AZStd::string_view inA, AZStd::string_view inB, bool bCaseSensitive)
{
const size_t maxCharsToCompare = inA.size();
return inA.size() == inB.size() && (bCaseSensitive
? strncmp(inA.data(), inB.data(), maxCharsToCompare) == 0
: azstrnicmp(inA.data(), inB.data(), maxCharsToCompare) == 0);
}
bool StartsWith(AZStd::string_view sourceValue, AZStd::string_view prefixValue, bool bCaseSensitive)
{
@@ -496,9 +505,18 @@ namespace AZ
&& Equal(sourceValue.substr(sourceValue.size() - suffixValue.size(), AZStd::string_view::npos).data(), suffixValue.data(), bCaseSensitive, suffixValue.size());
}
size_t Find(const char* in, char c, size_t pos /*= 0*/, bool bReverse /*= false*/, bool bCaseSensitive /*= false*/)
bool Contains(AZStd::string_view in, char ch, bool bCaseSensitive)
{
if (!in)
return Find(in, ch, 0, false, bCaseSensitive) != AZStd::string_view::npos;
}
bool Contains(AZStd::string_view in, AZStd::string_view sv, bool bCaseSensitive)
{
return Find(in, sv, 0, false, bCaseSensitive) != AZStd::string_view::npos;
}
size_t Find(AZStd::string_view in, char c, size_t pos /*= 0*/, bool bReverse /*= false*/, bool bCaseSensitive /*= false*/)
{
if (in.empty())
{
return AZStd::string::npos;
}
@@ -508,7 +526,7 @@ namespace AZ
pos = 0;
}
size_t inLen = strlen(in);
size_t inLen = in.size();
if (inLen < pos)
{
return AZStd::string::npos;
@@ -557,7 +575,13 @@ namespace AZ
size_t Find(AZStd::string_view in, AZStd::string_view s, size_t offset /*= 0*/, bool bReverse /*= false*/, bool bCaseSensitive /*= false*/)
{
if (in.empty() || s.empty())
// Formally an empty string matches at the offset if it is <= to the size of the input string
if (s.empty() && offset <= in.size())
{
return offset;
}
if (in.empty())
{
return AZStd::string::npos;
}
@@ -773,7 +797,7 @@ namespace AZ
bool bIsSpaces = false;
if (!bIsEmpty)
{
AZStd::string_view strippedNextToken = Internal::StripEnds(*nextToken, " ");
AZStd::string_view strippedNextToken = StripEnds(*nextToken, " ");
bIsSpaces = strippedNextToken.empty();
}
@@ -805,7 +829,7 @@ namespace AZ
bool bIsSpaces = false;
if (!bIsEmpty)
{
AZStd::string_view strippedNextToken = Internal::StripEnds(*nextToken, " ");
AZStd::string_view strippedNextToken = StripEnds(*nextToken, " ");
bIsSpaces = strippedNextToken.empty();
}
@@ -12,6 +12,7 @@
#pragma once
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/std/function/function_fwd.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/string.h>
@@ -107,6 +108,22 @@ namespace AZ
StringFunc::Equal("Hello World", "Hello", true, 3) = true
*/
bool Equal(const char* inA, const char* inB, bool bCaseSensitive = false, size_t n = 0);
bool Equal(AZStd::string_view inA, AZStd::string_view inB, bool bCaseSensitive = false);
//! Contains
/*! Checks if the supplied character or string is contained within the @in parameter
*
Example: Case Insensitive contains finds character
StringFunc::Contains("Hello", 'L') == true
Example: Case Sensitive contains finds character
StringFunc::Contains("Hello", 'l', true) = true
Example: Case Insensitive contains does not find string
StringFunc::Contains("Well Hello", "Mello") == false
Example: Case Sensitive contains does not find character
StringFunc::Contains("HeLlo", 'h', true) == false
*/
bool Contains(AZStd::string_view in, char ch, bool bCaseSensitive = false);
bool Contains(AZStd::string_view in, AZStd::string_view sv, bool bCaseSensitive = false);
//! Find
/*! Find for non AZStd::strings. Ease of use to find the first or last occurrence of a character or substring in a c-string with case sensitivity.
@@ -119,7 +136,7 @@ namespace AZ
Example: Case Sensitive find first occurrence of substring "Hello" a in a c-string
StringFunc::Find("Well Hello", "Hello", false, true) == 5
*/
size_t Find(const char* in, char c, size_t pos = 0, bool bReverse = false, bool bCaseSensitive = false);
size_t Find(AZStd::string_view in, char c, size_t pos = 0, bool bReverse = false, bool bCaseSensitive = false);
size_t Find(AZStd::string_view in, AZStd::string_view str, size_t pos = 0, bool bReverse = false, bool bCaseSensitive = false);
//! First and Last Character
@@ -196,6 +213,33 @@ namespace AZ
*/
AZStd::string& TrimWhiteSpace(AZStd::string& value, bool leading, bool trailing);
//! LStrip
/*! Strips leading characters in the stripCharacters set
* Example
* Example: Case Insensitive Strip leading 'a' characters
* StringFunc::LStrip(s = "Abracadabra", 'a'); s == "bracadabra"
* Example: Case Sensitive Strip leading 'a' characters
* StringFunc::LStrip(s = "Abracadabra", 'a'); s == "Abracadabra"
*/
//! RStrip
/*! Strips trailing characters in the stripCharacters set
* Example
* Example: Case Insensitive Strip trailing 'a' characters
* StringFunc::RStrip(s = "AbracadabrA", 'a'); s == "Abracadabr"
* Example: Case Sensitive Strip trailing 'a' characters
* StringFunc::RStrip(s = "AbracadabrA", 'a'); s == "AbracadabrA"
*/
//! StripEnds
/*! Strips leading and trailing characters in the stripCharacters set
Example: Case Insensitive Strip all 'a' characters
StringFunc::StripEnds(s = "Abracadabra", 'a'); s == "bracadabr"
Example: Case Sensitive Strip all 'a' characters
StringFunc::StripEnds(s = "Abracadabra", 'a'); s == "Abracadabr"
*/
AZStd::string_view LStrip(AZStd::string_view in, AZStd::string_view stripCharacters = " ");
AZStd::string_view RStrip(AZStd::string_view in, AZStd::string_view stripCharacters = " ");
AZStd::string_view StripEnds(AZStd::string_view in, AZStd::string_view stripCharacters = " ");
//! Strip
/*! Strip away the leading, trailing or all character(s) or substring(s) in a AZStd::string with
*! case sensitivity.
@@ -669,8 +713,7 @@ namespace AZ
*/
namespace Path
{
inline constexpr size_t MaxPathLength = 1024;
using FixedString = AZStd::fixed_string<MaxPathLength>;
using FixedString = AZ::IO::FixedMaxPathString;
//! Normalize
/*! Normalizes a path and returns returns StringFunc::Path::IsValid()
*! strips all AZ_FILESYSTEM_INVALID_CHARACTERS
@@ -791,7 +834,7 @@ namespace AZ
//! StripFullName
/*! gets rid of the full file name if it has one, returns if it removed one or not
*! EX: StringFunc::Path::StripFullName(a="C:\\p4\\game\\info\\some.file") == true; a=="C:\\p4\\game\\info\\"
*! EX: StringFunc::Path::StripFullName(a="C:\\p4\\game\\info\\some.file") == true; a=="C:\\p4\\game\\info"
*/
void StripFullName(AZStd::string& out);
@@ -39,6 +39,7 @@ namespace UnitTest
MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ());
MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ());
MOCK_CONST_METHOD0(GetAppRoot, const char* ());
MOCK_CONST_METHOD0(GetEngineRoot, const char* ());
MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ());
MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&));
@@ -60,6 +60,7 @@ namespace AZ
MOCK_CONST_METHOD2(ConvertToAlias, bool(AZ::IO::FixedMaxPath& aliasPath, const AZ::IO::PathView& path));
MOCK_CONST_METHOD3(ResolvePath, bool(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize));
MOCK_CONST_METHOD2(ResolvePath, bool(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path));
MOCK_CONST_METHOD2(ReplaceAlias, bool(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path));
MOCK_CONST_METHOD3(GetFilename, bool(HandleType fileHandle, char* filename, AZ::u64 filenameSize));
using FileIOBase::ConvertToAlias;
using FileIOBase::ResolvePath;
@@ -16,6 +16,8 @@
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/StringFunc/StringFunc.h>
namespace AZ::Utils
@@ -40,6 +42,47 @@ namespace AZ::Utils
return result.m_pathStored;
}
AZ::IO::FixedMaxPathString GetEnginePath()
{
if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr)
{
AZ::SettingsRegistryInterface::FixedValueString settingsValue;
if (registry->Get(settingsValue, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
return AZ::IO::FixedMaxPathString{settingsValue};
}
}
return {};
}
AZ::IO::FixedMaxPathString GetProjectPath()
{
if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr)
{
AZ::SettingsRegistryInterface::FixedValueString settingsValue;
if (registry->Get(settingsValue, AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath))
{
return AZ::IO::FixedMaxPathString{settingsValue};
}
}
return {};
}
AZ::SettingsRegistryInterface::FixedValueString GetProjectName()
{
if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr)
{
AZ::SettingsRegistryInterface::FixedValueString projectNameKey{ AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey };
projectNameKey += "/project_name";
AZ::SettingsRegistryInterface::FixedValueString settingsValue;
if (registry->Get(settingsValue, projectNameKey))
{
return settingsValue;
}
}
return {};
}
AZ::Outcome<void, AZStd::string> WriteFile(AZStd::string_view content, AZStd::string_view filePath)
{
AZ::IO::FixedMaxPath filePathFixed = filePath; // Because FileIOStream requires a null-terminated string
+16 -5
View File
@@ -16,6 +16,8 @@
#include <AzCore/base.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/fixed_string.h>
@@ -24,9 +26,6 @@ namespace AZ
{
namespace Utils
{
// Common cross platform Utils go here
inline constexpr size_t MaxPathLength = 1024;
//! Protects from allocating too much memory. The choice of a 1MB threshold is arbitrary.
//! If you need to work with larger files, please use AZ::IO directly instead of these utility functions.
inline constexpr size_t DefaultMaxFileSize = 1024 * 1024;
@@ -77,10 +76,22 @@ namespace AZ
//! @returns a result object that indicates if the executable directory was able to be stored within the buffer
ExecutablePathResult GetExecutableDirectory(char* exeStorageBuffer, size_t exeStorageSize);
//! Retrieves the full path to the engine from settings registry
AZ::IO::FixedMaxPathString GetEnginePath();
//! Retrieves the full path to the project from settings registry
AZ::IO::FixedMaxPathString GetProjectPath();
//! Retrieves the project name from the settings registry
AZ::SettingsRegistryInterface::FixedValueString GetProjectName();
//! Retrieves the full path where the manifest file lives, i.e. "<userhome>/.o3de/o3de_manifest.json"
AZ::IO::FixedMaxPathString GetEngineManifestPath();
//! Retrieves the App root path to use on the current platform
//! If the optional is not engaged the AppRootPath should be calculated based
//! on the location of the bootstrap.cfg file
AZStd::optional<AZStd::fixed_string<MaxPathLength>> GetDefaultAppRootPath();
AZStd::optional<AZ::IO::FixedMaxPathString> GetDefaultAppRootPath();
//! Retrieves the development write storage path to use on the current platform, may be considered
//! temporary or cache storage
@@ -88,7 +99,7 @@ namespace AZ
// Attempts the supplied path to an absolute path.
//! Returns nullopt if path cannot be converted to an absolute path
AZStd::optional<AZStd::fixed_string<MaxPathLength>> ConvertToAbsolutePath(AZStd::string_view path);
AZStd::optional<AZ::IO::FixedMaxPathString> ConvertToAbsolutePath(AZStd::string_view path);
//! Save a string to a file. Otherwise returns a failure with error message.
AZ::Outcome<void, AZStd::string> WriteFile(AZStd::string_view content, AZStd::string_view filePath);
@@ -249,6 +249,11 @@ namespace AZStd
constexpr auto swap(basic_fixed_string& rhs) -> void;
// C++23 contains
constexpr auto contains(const basic_fixed_string& other) const -> bool;
constexpr auto contains(Element ch) const -> bool;
constexpr auto contains(const_pointer s) const -> bool;
constexpr auto find(const basic_fixed_string& rhs, size_type offset = 0) const -> size_type;
constexpr auto find(const_pointer ptr, size_type offset, size_type count) const -> size_type;
constexpr auto find(const_pointer ptr, size_type offset = 0) const -> size_type;
@@ -1072,6 +1072,23 @@ namespace AZStd
Traits::assign(rhs.m_buffer[rhs.m_size], Element{ 0 });
}
// C++23 contains
template<class Element, size_t MaxElementCount, class Traits>
inline constexpr auto basic_fixed_string<Element, MaxElementCount, Traits>::contains(const basic_fixed_string& other) const -> bool
{
return find(other) != npos;
}
template<class Element, size_t MaxElementCount, class Traits>
inline constexpr auto basic_fixed_string<Element, MaxElementCount, Traits>::contains(value_type c) const -> bool
{
return find(c) != npos;
}
template<class Element, size_t MaxElementCount, class Traits>
inline constexpr auto basic_fixed_string<Element, MaxElementCount, Traits>::contains(const_pointer s) const -> bool
{
return find(s) != npos;
}
template<class Element, size_t MaxElementCount, class Traits>
inline constexpr auto basic_fixed_string<Element, MaxElementCount, Traits>::find(const basic_fixed_string& rhs, size_type offset) const -> size_type
{
@@ -961,6 +961,22 @@ namespace AZStd
}
}
// C++23 contains
bool contains(const basic_string& other) const
{
return find(other) != npos;
}
bool contains(value_type c) const
{
return find(c) != npos;
}
bool contains(const_pointer s) const
{
return find(s) != npos;
}
inline size_type find(const this_type& rhs, size_type offset = 0) const
{
const_pointer rhsData = SSO_BUF_SIZE <= rhs.m_capacity ? rhs.m_data : rhs.m_buffer;
@@ -669,6 +669,22 @@ namespace AZStd
return ends_with(basic_string_view(suffix));
}
// C++23 contains
constexpr bool contains(basic_string_view other) const
{
return find(other) != npos;
}
constexpr bool contains(value_type c) const
{
return find(c) != npos;
}
constexpr bool contains(const_pointer s) const
{
return find(s) != npos;
}
// find
constexpr size_type find(basic_string_view other, size_type pos = 0) const
{
@@ -52,10 +52,10 @@ namespace AZ
// in non-release builds.
// If a bootstrap.cfg file is not found in the public storage, it is then searched for
// within the APK itself
AZStd::optional<AZStd::fixed_string<MaxPathLength>> GetDefaultAppRootPath()
AZStd::optional<AZ::IO::FixedMaxPathString> GetDefaultAppRootPath()
{
const char* appRoot = AZ::Android::Utils::FindAssetsDirectory();
return appRoot ? AZStd::make_optional<AZStd::fixed_string<MaxPathLength>>(appRoot) : AZStd::nullopt;
return appRoot ? AZStd::make_optional<AZ::IO::FixedMaxPathString>(appRoot) : AZStd::nullopt;
}
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath()
@@ -64,10 +64,10 @@ namespace AZ
return writeStorage ? AZStd::make_optional<AZ::IO::FixedMaxPathString>(writeStorage) : AZStd::nullopt;
}
AZStd::optional<AZStd::fixed_string<MaxPathLength>> ConvertToAbsolutePath(AZStd::string_view path)
AZStd::optional<AZ::IO::FixedMaxPathString> ConvertToAbsolutePath(AZStd::string_view path)
{
AZStd::fixed_string<MaxPathLength> absolutePath;
AZStd::fixed_string<MaxPathLength> srcPath{ path };
AZ::IO::FixedMaxPathString absolutePath;
AZ::IO::FixedMaxPathString srcPath{ path };
if (AZ::Android::Utils::IsApkPath(srcPath.c_str()))
{
return srcPath;
@@ -64,6 +64,7 @@ set(FILES
AzCore/Socket/AzSocket_Platform.h
../Common/UnixLike/AzCore/std/time_UnixLike.cpp
AzCore/Utils/Utils_Android.cpp
../Common/Unimplemented/AzCore/Utils/Utils_Unimplemented.cpp
../../AzCore/Android/AndroidEnv.cpp
../../AzCore/Android/AndroidEnv.h
../../AzCore/Android/APKFileHandler.cpp
@@ -0,0 +1,22 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Utils/Utils.h>
namespace AZ::Utils
{
AZ::IO::FixedMaxPathString GetEngineManifestPath()
{
return {};
}
} // namespace AZ::Utils
@@ -12,7 +12,7 @@
#include <AzCore/Utils/Utils.h>
#include <stdlib.h>
#include <cstdlib>
namespace AZ
{
@@ -25,10 +25,25 @@ namespace AZ
void NativeErrorMessageBox(const char*, const char*) {}
AZStd::optional<AZStd::fixed_string<MaxPathLength>> ConvertToAbsolutePath(AZStd::string_view path)
AZ::IO::FixedMaxPathString GetEngineManifestPath()
{
AZStd::fixed_string<MaxPathLength> absolutePath;
AZStd::fixed_string<MaxPathLength> srcPath{ path };
if (const char* homePath = std::getenv("HOME"); homePath != nullptr)
{
AZ::IO::FixedMaxPath path{homePath};
if (!path.empty())
{
path /= ".o3de";
path /= "o3de_manifest.json";
}
return path.Native();
}
return {};
}
AZStd::optional<AZ::IO::FixedMaxPathString> ConvertToAbsolutePath(AZStd::string_view path)
{
AZ::IO::FixedMaxPathString absolutePath;
AZ::IO::FixedMaxPathString srcPath{ path };
if (char* result = realpath(srcPath.c_str(), absolutePath.data()); result)
{
@@ -47,7 +47,7 @@ namespace AZ
return result;
}
AZStd::optional<AZStd::fixed_string<MaxPathLength>> GetDefaultAppRootPath()
AZStd::optional<AZ::IO::FixedMaxPathString> GetDefaultAppRootPath()
{
return AZStd::nullopt;
}
@@ -57,10 +57,10 @@ namespace AZ
return AZStd::nullopt;
}
AZStd::optional<AZStd::fixed_string<MaxPathLength>> ConvertToAbsolutePath(AZStd::string_view path)
AZStd::optional<AZ::IO::FixedMaxPathString> ConvertToAbsolutePath(AZStd::string_view path)
{
AZStd::fixed_string<MaxPathLength> absolutePath;
AZStd::fixed_string<MaxPathLength> srcPath{ path };
AZ::IO::FixedMaxPathString absolutePath;
AZ::IO::FixedMaxPathString srcPath{ path };
char* result = _fullpath(absolutePath.data(), srcPath.c_str(), absolutePath.capacity());
// Force update of the fixed_string size() value
absolutePath.resize_no_construct(AZStd::char_traits<char>::length(absolutePath.data()));
@@ -45,7 +45,7 @@ namespace AZ
return result;
}
AZStd::optional<AZStd::fixed_string<MaxPathLength>> GetDefaultAppRootPath()
AZStd::optional<AZ::IO::FixedMaxPathString> GetDefaultAppRootPath()
{
return AZStd::nullopt;
}
@@ -16,7 +16,7 @@
namespace AZ::Utils
{
AZStd::optional<AZStd::fixed_string<MaxPathLength>> GetDefaultAppRootPath()
AZStd::optional<AZ::IO::FixedMaxPathString> GetDefaultAppRootPath()
{
return AZStd::nullopt;
}
@@ -376,7 +376,7 @@ namespace AZ::IO
hardwareInfo.m_profile = driveList.size() == 1 ? driveList.front().m_profile : "Generic";
hardwareInfo.m_platformData = AZStd::make_any<DriveList>(AZStd::move(driveList));
return true;
return !driveList.empty();
}
else
{
@@ -15,13 +15,27 @@
#include <stdlib.h>
namespace AZ
namespace AZ::Utils
{
namespace Utils
void NativeErrorMessageBox(const char* title, const char* message)
{
void NativeErrorMessageBox(const char* title, const char* message)
::MessageBox(0, message, title, MB_OK | MB_ICONERROR);
}
AZ::IO::FixedMaxPathString GetEngineManifestPath()
{
char userProfileBuffer[AZ::IO::MaxPathLength] = {0};
size_t variableSize = 0;
auto err = getenv_s(&variableSize, userProfileBuffer, AZ::IO::MaxPathLength, "USERPROFILE");
if (!err)
{
::MessageBox(0, message, title, MB_OK | MB_ICONERROR);
AZ::IO::FixedMaxPath path{userProfileBuffer};
path /= ".o3de";
path /= "o3de_manifest.json";
return path.Native();
}
} // namespace Utils
} // namespace AZ
return {};
}
} // namespace AZ::Utils
@@ -19,10 +19,10 @@
namespace AZ::Utils
{
AZStd::optional<AZStd::fixed_string<MaxPathLength>> GetDefaultAppRootPath()
AZStd::optional<AZ::IO::FixedMaxPathString> GetDefaultAppRootPath()
{
const char* pathToResources = [[[NSBundle mainBundle] resourcePath] UTF8String];
return AZStd::fixed_string<MaxPathLength>::format("%s/assets", pathToResources);
return AZ::IO::FixedMaxPathString::format("%s/assets", pathToResources);
}
AZStd::optional<AZ::IO::FixedMaxPathString> GetDevWriteStoragePath()
@@ -70,4 +70,5 @@ set(FILES
AzCore/Utils/Utils_iOS.mm
../Common/Apple/AzCore/Utils/Utils_Apple.cpp
../Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp
../Common/Unimplemented/AzCore/Utils/Utils_Unimplemented.cpp
)
@@ -2391,6 +2391,27 @@ namespace UnitTest
EXPECT_STREQ("AB BA", eraseIfTest.c_str());
}
template <typename StringType>
class ImmutableStringFunctionsFixture
: public ScopedAllocatorSetupFixture
{};
using StringTypesToTest = ::testing::Types<AZStd::string_view, AZStd::string, AZStd::fixed_string<1024>>;
TYPED_TEST_CASE(ImmutableStringFunctionsFixture, StringTypesToTest);
TYPED_TEST(ImmutableStringFunctionsFixture, Contains_Succeeds)
{
TypeParam testStrValue{ R"(C:\o3de\Assets\Materials\texture\image.png)" };
EXPECT_TRUE(testStrValue.contains("Materials"));
EXPECT_FALSE(testStrValue.contains("Animations"));
EXPECT_TRUE(testStrValue.contains('o'));
EXPECT_FALSE(testStrValue.contains('Q'));
TypeParam typeParamEntry{ R"(texture)" };
TypeParam typeParamNoEntry{ R"(physics)" };
EXPECT_TRUE(testStrValue.contains(typeParamEntry));
EXPECT_FALSE(testStrValue.contains(typeParamNoEntry));
}
template<typename T>
const T* GetFormatString()
{
@@ -54,8 +54,9 @@ namespace UnitTest
AZ::SerializeContext* GetSerializeContext() override { return nullptr; }
AZ::BehaviorContext* GetBehaviorContext() override { return m_behaviorContext; }
AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; }
const char* GetExecutableFolder() const override { return nullptr; }
const char* GetAppRoot() const override { return nullptr; }
const char* GetEngineRoot() const override { return nullptr; }
const char* GetExecutableFolder() const override { return nullptr; }
AZ::Debug::DrillerManager* GetDrillerManager() override { return nullptr; }
void EnumerateEntities(const EntityCallback& /*callback*/) override {}
void QueryApplicationType(AZ::ApplicationTypeQuery& /*appType*/) const override {}
@@ -416,6 +416,11 @@ public:
return false;
}
bool ReplaceAlias(AZ::IO::FixedMaxPath&, const AZ::IO::PathView&) const override
{
return false;
}
void SetAlias(const char*, const char*) override
{
}
@@ -58,22 +58,22 @@ namespace UnitTest
TEST_F(UtilsTestFixture, ConvertToAbsolutePath_OnRelativePath_Succeeds)
{
AZStd::optional<AZStd::fixed_string<AZ::Utils::MaxPathLength>> absolutePath = AZ::Utils::ConvertToAbsolutePath("./");
AZStd::optional<AZ::IO::FixedMaxPathString> absolutePath = AZ::Utils::ConvertToAbsolutePath("./");
EXPECT_TRUE(absolutePath);
}
TEST_F(UtilsTestFixture, ConvertToAbsolutePath_OnAbsolutePath_Succeeds)
{
char executableDirectory[AZ::Utils::MaxPathLength];
AZ::Utils::ExecutablePathResult result = AZ::Utils::GetExecutableDirectory(executableDirectory, AZ_ARRAY_SIZE(executableDirectory));
char executableDirectory[AZ::IO::MaxPathLength];
AZ::Utils::ExecutablePathResult result = AZ::Utils::GetExecutableDirectory(executableDirectory, AZStd::size(executableDirectory));
EXPECT_EQ(AZ::Utils::ExecutablePathResult::Success, result);
AZStd::optional<AZStd::fixed_string<AZ::Utils::MaxPathLength>> absolutePath = AZ::Utils::ConvertToAbsolutePath(executableDirectory);
AZStd::optional<AZ::IO::FixedMaxPathString> absolutePath = AZ::Utils::ConvertToAbsolutePath(executableDirectory);
ASSERT_TRUE(absolutePath);
// Note that ConvertToAbsolutePath will perform a realpath on the result. The result of AZ::Utils::GetExecutableDirectory
// uses AZ::Android::AndroidEnv::Get()->GetAppPrivateStoragePath() which will retrieve the storage path, but that path could
// be symlinked, so we need to perform a real path on it before comparison
char realExecutableDirectory[AZ::Utils::MaxPathLength];
char realExecutableDirectory[AZ::IO::MaxPathLength];
ASSERT_TRUE(realpath(executableDirectory, realExecutableDirectory));
EXPECT_STRCASEEQ(realExecutableDirectory, absolutePath->c_str());
@@ -27,16 +27,16 @@ namespace UnitTest
TEST_F(UtilsUnixLikeTestFixture, ConvertToAbsolutePath_OnRelativePath_Succeeds)
{
AZStd::optional<AZStd::fixed_string<AZ::Utils::MaxPathLength>> absolutePath = AZ::Utils::ConvertToAbsolutePath("./");
AZStd::optional<AZ::IO::FixedMaxPathString> absolutePath = AZ::Utils::ConvertToAbsolutePath("./");
EXPECT_TRUE(absolutePath);
}
TEST_F(UtilsUnixLikeTestFixture, ConvertToAbsolutePath_OnAbsolutePath_Succeeds)
{
char executableDirectory[AZ::Utils::MaxPathLength];
char executableDirectory[AZ::IO::MaxPathLength];
AZ::Utils::ExecutablePathResult result = AZ::Utils::GetExecutableDirectory(executableDirectory, AZ_ARRAY_SIZE(executableDirectory));
EXPECT_EQ(AZ::Utils::ExecutablePathResult::Success, result);
AZStd::optional<AZStd::fixed_string<AZ::Utils::MaxPathLength>> absolutePath = AZ::Utils::ConvertToAbsolutePath(executableDirectory);
AZStd::optional<AZ::IO::FixedMaxPathString> absolutePath = AZ::Utils::ConvertToAbsolutePath(executableDirectory);
ASSERT_TRUE(absolutePath);
EXPECT_STRCASEEQ(executableDirectory, absolutePath->c_str());
}
@@ -53,23 +53,23 @@ namespace UnitTest
TEST_F(UtilsTestFixture, ConvertToAbsolutePath_OnInvalidPath_Fails)
{
AZStd::fixed_string<AZ::Utils::MaxPathLength> invalidPath{ "Z:\\" };
invalidPath.append(AZ::Utils::MaxPathLength - invalidPath.size(), '@');
AZ::IO::FixedMaxPathString invalidPath{ "Z:\\" };
invalidPath.append(invalidPath.max_size() - invalidPath.size(), '@');
EXPECT_FALSE(AZ::Utils::ConvertToAbsolutePath(invalidPath));
}
TEST_F(UtilsTestFixture, ConvertToAbsolutePath_OnRelativePath_Succeeds)
{
AZStd::optional<AZStd::fixed_string<AZ::Utils::MaxPathLength>> absolutePath = AZ::Utils::ConvertToAbsolutePath("./");
AZStd::optional<AZ::IO::FixedMaxPathString> absolutePath = AZ::Utils::ConvertToAbsolutePath("./");
EXPECT_TRUE(absolutePath);
}
TEST_F(UtilsTestFixture, ConvertToAbsolutePath_OnAbsolutePath_Succeeds)
{
char executableDirectory[AZ::Utils::MaxPathLength];
char executableDirectory[AZ::IO::MaxPathLength];
AZ::Utils::ExecutablePathResult result = AZ::Utils::GetExecutableDirectory(executableDirectory, AZ_ARRAY_SIZE(executableDirectory));
EXPECT_EQ(AZ::Utils::ExecutablePathResult::Success, result);
AZStd::optional<AZStd::fixed_string<AZ::Utils::MaxPathLength>> absolutePath = AZ::Utils::ConvertToAbsolutePath(executableDirectory);
AZStd::optional<AZ::IO::FixedMaxPathString> absolutePath = AZ::Utils::ConvertToAbsolutePath(executableDirectory);
ASSERT_TRUE(absolutePath);
EXPECT_STRCASEEQ(executableDirectory, absolutePath->c_str());
}
@@ -1237,8 +1237,9 @@ namespace UnitTest
SerializeContext* GetSerializeContext() override { return m_serializeContext.get(); }
BehaviorContext* GetBehaviorContext() override { return nullptr; }
JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; }
const char* GetExecutableFolder() const override { return nullptr; }
const char* GetAppRoot() const override { return nullptr; }
const char* GetEngineRoot() const override { return nullptr; }
const char* GetExecutableFolder() const override { return nullptr; }
Debug::DrillerManager* GetDrillerManager() override { return nullptr; }
void EnumerateEntities(const EntityCallback& /*callback*/) override {}
void QueryApplicationType(AZ::ApplicationTypeQuery& /*appType*/) const override {}
@@ -15,6 +15,7 @@
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Serialization/Json/DoubleSerializer.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <Tests/Serialization/Json/BaseJsonSerializerFixture.h>
@@ -142,6 +143,13 @@ namespace JsonSerializationTests
constexpr static size_t ElementCount = 4;
};
struct QuaternionDescriptor
{
using VectorType = AZ::Quaternion;
using Serializer = AZ::JsonQuaternionSerializer;
constexpr static size_t ElementCount = 4;
};
using JsonMathVectorSerializerTypes = ::testing::Types <
Vector2Descriptor, Vector3Descriptor, Vector4Descriptor>;
TYPED_TEST_CASE(JsonMathVectorSerializerTests, JsonMathVectorSerializerTypes);
@@ -25,9 +25,11 @@ namespace UnitTest
AZ::CommandLine cmd;
EXPECT_FALSE(cmd.HasSwitch(""));
EXPECT_EQ(cmd.GetNumSwitchValues("haha"), 0);
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_EQ(cmd.GetSwitchValue("haha", 0), AZStd::string());
EXPECT_EQ(cmd.GetNumMiscValues(), 0);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
EXPECT_EQ(cmd.GetNumMiscValues(), 0);
AZ_TEST_START_TRACE_SUPPRESSION;
EXPECT_EQ(cmd.GetMiscValue(1), AZStd::string());
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
@@ -379,7 +381,6 @@ namespace UnitTest
{
AZ::CommandLine origCommandLine{ "-/" };
const char* argValues[] =
{
"programname.exe", "/gamefolder", "/RemoteIp", "10.0.0.1", "-ScanFolders", R"(\a\b\c,\d\e\f)", "Foo", "Bat"
@@ -396,20 +397,21 @@ namespace UnitTest
for (const auto& commandLine : commandLines)
{
EXPECT_TRUE(commandLine.HasSwitch("gamefolder"));
EXPECT_EQ(0, commandLine.GetNumSwitchValues("gamefolder"));
EXPECT_EQ(1, commandLine.GetNumSwitchValues("gamefolder"));
EXPECT_STREQ("", commandLine.GetSwitchValue("gamefolder", 0).c_str());
EXPECT_TRUE(commandLine.HasSwitch("remoteip"));
EXPECT_EQ(1, commandLine.GetNumSwitchValues("remoteip"));
EXPECT_EQ("10.0.0.1", commandLine.GetSwitchValue("remoteip", 0));
EXPECT_STREQ("10.0.0.1", commandLine.GetSwitchValue("remoteip", 0).c_str());
EXPECT_TRUE(commandLine.HasSwitch("scanfolders"));
EXPECT_EQ(2, commandLine.GetNumSwitchValues("scanfolders"));
EXPECT_EQ(R"(\a\b\c)", commandLine.GetSwitchValue("scanfolders", 0));
EXPECT_EQ(R"(\d\e\f)", commandLine.GetSwitchValue("scanfolders", 1));
EXPECT_STREQ(R"(\a\b\c)", commandLine.GetSwitchValue("scanfolders", 0).c_str());
EXPECT_STREQ(R"(\d\e\f)", commandLine.GetSwitchValue("scanfolders", 1).c_str());
EXPECT_EQ(2, commandLine.GetNumMiscValues());
EXPECT_EQ("Foo", commandLine.GetMiscValue(0));
EXPECT_EQ("Bat", commandLine.GetMiscValue(1));
EXPECT_STREQ("Foo", commandLine.GetMiscValue(0).c_str());
EXPECT_STREQ("Bat", commandLine.GetMiscValue(1).c_str());
}
}
} // namespace UnitTest
@@ -82,23 +82,25 @@ namespace SettingsRegistryMergeUtilsTests
DumpSettings,
SettingsRegistryMergeUtilsParamFixture,
::testing::Values(
DumpSettingsRegistryParams{ AZ::SettingsRegistryInterface::Format::JsonPatch,
R"( [
{ "op": "add", "path": "/Test", "value": { "Object": {} } },
{ "op": "add", "path": "/Test/Object/NullType", "value": null },
{ "op": "add", "path": "/Test/Object/TrueType", "value": true },
{ "op": "add", "path": "/Test/Object/FalseType", "value": false },
{ "op": "add", "path": "/Test/Object/IntType", "value": -42 },
{ "op": "add", "path": "/Test/Object/UIntType", "value": 42 },
{ "op": "add", "path": "/Test/Object/DoubleType", "value": 42.0 },
{ "op": "add", "path": "/Test/Object/StringType", "value": "Hello world" },
{ "op": "add", "path": "/Test/Array", "value": [ null, true, false, -42, 42, 42.0, "Hello world" ] }
])",
DumpSettingsRegistryParams
{
AZ::SettingsRegistryInterface::Format::JsonPatch,
R"([)" "\n"
R"( { "op": "add", "path": "/Test", "value": { "Object": {} } },)" "\n"
R"( { "op": "add", "path": "/Test/Object/NullType", "value": null },)" "\n"
R"( { "op": "add", "path": "/Test/Object/TrueType", "value": true },)" "\n"
R"( { "op": "add", "path": "/Test/Object/FalseType", "value": false },)" "\n"
R"( { "op": "add", "path": "/Test/Object/IntType", "value": -42 },)" "\n"
R"( { "op": "add", "path": "/Test/Object/UIntType", "value": 42 },)" "\n"
R"( { "op": "add", "path": "/Test/Object/DoubleType", "value": 42.0 },)" "\n"
R"( { "op": "add", "path": "/Test/Object/StringType", "value": "Hello world" },)" "\n"
R"( { "op": "add", "path": "/Test/Array", "value": [ null, true, false, -42, 42, 42.0, "Hello world" ] })" "\n"
R"(])" "\n",
R"({"Test":{"Object":{"NullType":null,"TrueType":true,"FalseType":false,"IntType":-42,"UIntType":42)"
R"(,"DoubleType":42.0,"StringType":"Hello world"},"Array":[null,true,false,-42,42,42.0,"Hello world"]}})",
{ false,
AZ::SettingsRegistryMergeUtils::DumperSettings
{
false,
[](AZStd::string_view path)
{
AZStd::string_view prefixPath("/Test");
@@ -106,26 +108,30 @@ namespace SettingsRegistryMergeUtilsTests
}
}
},
DumpSettingsRegistryParams{ AZ::SettingsRegistryInterface::Format::JsonMergePatch,
R"({
"Test":
{
"Array0": [ 142, 188 ],
"Array1": [ 242, 288 ],
"Array2": [ 342, 388 ]
}
})",
DumpSettingsRegistryParams
{
AZ::SettingsRegistryInterface::Format::JsonMergePatch,
R"({)" "\n"
R"( "Test":)" "\n"
R"( {)" "\n"
R"( "Array0": [ 142, 188 ], )" "\n"
R"( "Array1": [ 242, 288 ], )" "\n"
R"( "Array2": [ 342, 388 ] )" "\n"
R"( })" "\n"
R"(})" "\n",
R"({"Test":{"Array0":[142,188],"Array1":[242,288],"Array2":[342,388]}})",
{ false,
AZ::SettingsRegistryMergeUtils::DumperSettings{ false,
[](AZStd::string_view path)
{
AZStd::string_view prefixPath("/Test");
return prefixPath.starts_with(path.substr(0, prefixPath.size()));
}
}
},
DumpSettingsRegistryParams{ AZ::SettingsRegistryInterface::Format::JsonMergePatch,
R"({
},
DumpSettingsRegistryParams
{
AZ::SettingsRegistryInterface::Format::JsonMergePatch,
R"({
"Test":
{
"Array0": [ 142, 188 ],
@@ -149,23 +155,27 @@ namespace SettingsRegistryMergeUtilsTests
R"( ])""\n"
R"( })""\n"
R"(})",
{ true,
AZ::SettingsRegistryMergeUtils::DumperSettings
{
true,
[](AZStd::string_view path)
{
AZStd::string_view prefixPath("/Test");
return prefixPath.starts_with(path.substr(0, prefixPath.size()));
}
}
},
DumpSettingsRegistryParams{ AZ::SettingsRegistryInterface::Format::JsonMergePatch,
R"({
},
DumpSettingsRegistryParams
{
AZ::SettingsRegistryInterface::Format::JsonMergePatch,
R"({
"Test":
{
"Array0": [ 142, 188 ],
"Array1": [ 242, 288 ],
"Array2": [ 342, 388 ]
}
})",
})",
R"({)""\n"
R"( "Array0": [)""\n"
R"( 142,)""\n"
@@ -180,7 +190,9 @@ namespace SettingsRegistryMergeUtilsTests
R"( 388)""\n"
R"( ])""\n"
R"(})",
{ true,
AZ::SettingsRegistryMergeUtils::DumperSettings
{
true,
[](AZStd::string_view path)
{
AZStd::string_view prefixPath("/Test");
@@ -188,8 +200,19 @@ namespace SettingsRegistryMergeUtilsTests
}
},
"/Test"
}
)
},
DumpSettingsRegistryParams{
AZ::SettingsRegistryInterface::Format::JsonMergePatch,
R"({)" "\n"
R"( "Test":)" "\n"
R"( {)" "\n"
R"( "Array0": [ 142, 188 ])" "\n"
R"( })" "\n"
R"(})",
R"({"Root":{"Path":{"Test":{"Array0":[142,188]}}}})",
AZ::SettingsRegistryMergeUtils::DumperSettings{ false, {}, "/Root/Path/Test" },
"/Test"
})
);
//! ConfigFile MergeUtils Test
@@ -281,9 +304,13 @@ namespace SettingsRegistryMergeUtilsTests
AZ::SettingsRegistryMergeUtils::ConfigParserSettings parserSettings;
parserSettings.m_commentPrefixFunc = [](AZStd::string_view line) -> AZStd::string_view
{
if (line.starts_with("--") || line.starts_with(';') || line.starts_with('#'))
constexpr AZStd::string_view commentPrefixes[]{ "--", ";","#" };
for (AZStd::string_view commentPrefix : commentPrefixes)
{
return {};
if (size_t commentOffset = line.find(commentPrefix); commentOffset != AZStd::string_view::npos)
{
return line.substr(0, commentOffset);
}
}
return line;
};
@@ -330,7 +357,7 @@ INSTANTIATE_TEST_CASE_P(
-- android, ios, mac, linux, windows, etc...
-- or left unprefixed, to set all platforms not specified. The rules apply in the order they're declared
sys_game_folder=TestProject
project_path=TestProject
-- remote_filesystem - enable Virtual File System (VFS)
-- This feature allows a remote instance of the game to run off assets
@@ -363,7 +390,7 @@ mac_assets = osx_gl
-- Example: 192.168.1.0/24 will allow any address starting with 192.168.1.
-- Example: 192.168.0.0/16 will allow any address starting with 192.168.
-- Example: 192.168.0.0/8 will allow any address starting with 192.
-- white_list =
-- allowed_list =
-- IP address and optionally port of the asset processor.
-- Set your PC IP here: (and uncomment the next line)
@@ -405,7 +432,7 @@ mac_wait_for_connect=0
)"
, AZStd::fixed_vector<ConfigFileParams::SettingsKeyValuePair, 20>{
ConfigFileParams::SettingsKeyValuePair{"/sys_game_folder", AZStd::string_view{"TestProject"}},
ConfigFileParams::SettingsKeyValuePair{"/project_path", AZStd::string_view{"TestProject"}},
ConfigFileParams::SettingsKeyValuePair{"/remote_filesystem", AZ::s64{0}},
ConfigFileParams::SettingsKeyValuePair{"/android_remote_filesystem", AZ::s64{0}},
ConfigFileParams::SettingsKeyValuePair{"/ios_remote_filesystem", AZ::s64{0}},
@@ -428,7 +455,7 @@ mac_wait_for_connect=0
// Parses a fake AssetProcessorPlatformConfig file which contains sections headers
// and does not end with a newline
ConfigFileParams{ "fake_AssetProcessorPlatformConfig.ini", R"(
; ---- Enable/Disable platforms for the entire project. AssetProcessor will automatically add the current platform by default.
; ---- Enable/Disable platforms for the entire project. AssetProcessor will automatically add the current platform by default.
; PLATFORM DEFINITIONS
; [Platform (unique identifier)]
@@ -452,7 +479,7 @@ test_asset_processor_tag = test_value
tags=tools,renderer,dx12,vulkan
[Platform es3]
tags=android,mobile,renderer,vulkan
tags=android,mobile,renderer,vulkan ; With Comments at the end
[Platform ios]
tags=mobile,renderer,metal
@@ -489,76 +516,30 @@ tags=tools,renderer,metal)"
TEST_F(SettingsRegistryMergeUtilsCommandLineFixture, CommandLineArguments_MergeToSettingsRegistry_Success)
{
AZ::CommandLine commandLine;
commandLine.Parse({ "programname.exe", "--gamefolder", "--RemoteIp", "10.0.0.1", "--ScanFolders", R"(\a\b\c,\d\e\f)", "Foo", "Bat" });
commandLine.Parse({ "programname.exe", "--project-path", "--RemoteIp", "10.0.0.1", "--ScanFolders", R"(\a\b\c,\d\e\f)", "Foo", "Bat" });
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_StoreCommandLine(*m_registry, commandLine);
AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(*m_registry, commandLine);
// Clear the CommandLine instance
commandLine = {};
struct CommandLineVisitor
: AZ::SettingsRegistryInterface::Visitor
{
AZ::SettingsRegistryInterface::VisitResponse Traverse(AZStd::string_view jsonPointer, AZStd::string_view,
AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type) override
{
if (action == AZ::SettingsRegistryInterface::VisitAction::Begin)
{
// Strip off the last key of the jsonPointer and check if the path is the command line root switch key
// i.e "/../CommandLine/Switches
AZStd::optional<AZStd::string_view> optionName = AZ::StringFunc::TokenizeLast(jsonPointer, '/');
if (jsonPointer == AZ::SettingsRegistryMergeUtils::CommandLineSwitchRootKey && optionName && !optionName->empty())
{
// Add a empty mapping of option name to values if it is not in the map already
m_optionArguments[*optionName];
}
}
EXPECT_TRUE(AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(*m_registry, commandLine));
return AZ::SettingsRegistryInterface::VisitResponse::Continue;
}
ASSERT_TRUE(commandLine.HasSwitch("project-path"));
EXPECT_EQ(1, commandLine.GetNumSwitchValues("project-path"));
EXPECT_STREQ("", commandLine.GetSwitchValue("project-path", 0).c_str());
void Visit(AZStd::string_view jsonPointer, AZStd::string_view, AZ::SettingsRegistryInterface::Type
, AZStd::string_view value) override
{
// Strip off the last key from the jsonPointer and return that in the option name parameter
AZStd::optional<AZStd::string_view> optionName = AZ::StringFunc::TokenizeLast(jsonPointer, '/');
if (jsonPointer == AZ::SettingsRegistryMergeUtils::CommandLineMiscValuesRootKey)
{
m_positionalArguments.push_back(value);
}
else if (jsonPointer.starts_with(AZ::SettingsRegistryMergeUtils::CommandLineSwitchRootKey) && optionName)
{
// Option arguments are stored in array indices, underneath the option key
// Therefore remove another token from the jsonPointer to retrieve the actual option name
if (optionName = AZ::StringFunc::TokenizeLast(jsonPointer, '/'); optionName && !optionName->empty())
{
// Add an entry for the option with the specified value
m_optionArguments[*optionName].push_back(value);
}
}
}
ASSERT_TRUE(commandLine.HasSwitch("remoteip"));
ASSERT_EQ(1, commandLine.GetNumSwitchValues("remoteip"));
EXPECT_STREQ("10.0.0.1", commandLine.GetSwitchValue("remoteip", 0).c_str());
AZ::CommandLine::ParamMap m_optionArguments;
AZ::CommandLine::ParamContainer m_positionalArguments;
};
ASSERT_TRUE(commandLine.HasSwitch("scanfolders"));
ASSERT_EQ(2, commandLine.GetNumSwitchValues("scanfolders"));
EXPECT_STREQ(R"(\a\b\c)", commandLine.GetSwitchValue("scanfolders", 0).c_str());
EXPECT_STREQ(R"(\d\e\f)", commandLine.GetSwitchValue("scanfolders", 1).c_str());
CommandLineVisitor commandLineVisitor;
EXPECT_TRUE(m_registry->Visit(commandLineVisitor, AZ::SettingsRegistryMergeUtils::CommandLineRootKey));
EXPECT_EQ(3, commandLineVisitor.m_optionArguments.size());
auto optionIter = commandLineVisitor.m_optionArguments.find("gamefolder");
ASSERT_NE(commandLineVisitor.m_optionArguments.end(), optionIter);
EXPECT_EQ(0, optionIter->second.size());
optionIter = commandLineVisitor.m_optionArguments.find("remoteip");
ASSERT_NE(commandLineVisitor.m_optionArguments.end(), optionIter);
ASSERT_EQ(1, optionIter->second.size());
EXPECT_STREQ("10.0.0.1", optionIter->second[0].c_str());
optionIter = commandLineVisitor.m_optionArguments.find("scanfolders");
ASSERT_NE(commandLineVisitor.m_optionArguments.end(), optionIter);
ASSERT_EQ(2, optionIter->second.size());
EXPECT_STREQ(R"(\a\b\c)", optionIter->second[0].c_str());
EXPECT_STREQ(R"(\d\e\f)", optionIter->second[1].c_str());
ASSERT_EQ(2, commandLineVisitor.m_positionalArguments.size());
EXPECT_STREQ("Foo", commandLineVisitor.m_positionalArguments[0].c_str());
EXPECT_STREQ("Bat", commandLineVisitor.m_positionalArguments[1].c_str());
ASSERT_EQ(3, commandLine.GetNumMiscValues());
EXPECT_STREQ("programname.exe", commandLine.GetMiscValue(0).c_str());
EXPECT_STREQ("Foo", commandLine.GetMiscValue(1).c_str());
EXPECT_STREQ("Bat", commandLine.GetMiscValue(2).c_str());
}
}
}
@@ -499,6 +499,38 @@ namespace SettingsRegistryTests
EXPECT_TRUE(this->m_registry->Visit(visitor, "/Test/Object/Type"));
}
TEST_F(SettingsRegistryTest, VisitWithVisitor_EndOfPathArguments_MatchesValueNameArgument_ForAllIterations)
{
// Validate that every invocation of the Traverse and Visit command supplies a 'path' parameter
// whose end matches that of the 'valueName' parameter
AZStd::string json{
R"({
"Test":
{
"Object":{ "Type": "TestString" }
}
})"
};
ASSERT_TRUE(this->m_registry->MergeSettings(json.c_str(), AZ::SettingsRegistryInterface::Format::JsonMergePatch));
struct : public AZ::SettingsRegistryInterface::Visitor
{
AZ::SettingsRegistryInterface::VisitResponse Traverse(AZStd::string_view path, AZStd::string_view valueName,
AZ::SettingsRegistryInterface::VisitAction, AZ::SettingsRegistryInterface::Type) override
{
EXPECT_TRUE(path.ends_with(valueName));
return AZ::SettingsRegistryInterface::VisitResponse::Continue;
}
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type , AZStd::string_view)override
{
EXPECT_TRUE(path.ends_with(valueName));
}
} visitor;
EXPECT_TRUE(this->m_registry->Visit(visitor, ""));
EXPECT_TRUE(this->m_registry->Visit(visitor, "/Test"));
}
//
// Object
//
@@ -730,7 +762,7 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object", "Object", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object/NullType", "NullType", SRI::Type::Null, SRI::VisitAction::Value),
@@ -752,7 +784,7 @@ namespace SettingsRegistryTests
RegistryEntry("/Test/Array/6", "6", SRI::Type::String, SRI::VisitAction::Value),
RegistryEntry("/Test/Array", "Array", SRI::Type::Array, SRI::VisitAction::End),
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::End)
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::End)
};
Visit(expected, "/Test");
@@ -778,11 +810,11 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Layer1/Layer2", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Layer1/Layer2", "Layer2", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Layer1/Layer2/Layer3", "Layer3", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Layer1/Layer2/Layer3/StringType", "StringType", SRI::Type::String, SRI::VisitAction::Value),
RegistryEntry("/Layer1/Layer2/Layer3", "Layer3", SRI::Type::Object, SRI::VisitAction::End),
RegistryEntry("/Layer1/Layer2", "", SRI::Type::Object, SRI::VisitAction::End)
RegistryEntry("/Layer1/Layer2", "Layer2", SRI::Type::Object, SRI::VisitAction::End)
};
Visit(expected, "/Layer1/Layer2");
@@ -806,9 +838,9 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Object/0/0", "", SRI::Type::Array, SRI::VisitAction::Begin),
RegistryEntry("/Object/0/0", "0", SRI::Type::Array, SRI::VisitAction::Begin),
RegistryEntry("/Object/0/0/0", "0", SRI::Type::String, SRI::VisitAction::Value),
RegistryEntry("/Object/0/0", "", SRI::Type::Array, SRI::VisitAction::End)
RegistryEntry("/Object/0/0", "0", SRI::Type::Array, SRI::VisitAction::End)
};
Visit(expected, "/Object/0/0");
@@ -830,7 +862,7 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object0", "Object0", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object0/Field", "Field", SRI::Type::Integer, SRI::VisitAction::Value),
@@ -842,7 +874,7 @@ namespace SettingsRegistryTests
RegistryEntry("/Test/Object2/Field", "Field", SRI::Type::Integer, SRI::VisitAction::Value),
RegistryEntry("/Test/Object2", "Object2", SRI::Type::Object, SRI::VisitAction::End),
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::End)
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::End)
};
Visit(expected, "/Test");
@@ -864,7 +896,7 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Array0", "Array0", SRI::Type::Array, SRI::VisitAction::Begin),
RegistryEntry("/Test/Array0/0", "0", SRI::Type::Integer, SRI::VisitAction::Value),
@@ -878,7 +910,7 @@ namespace SettingsRegistryTests
RegistryEntry("/Test/Array2/1", "1", SRI::Type::Integer, SRI::VisitAction::Value),
RegistryEntry("/Test/Array2", "Array2", SRI::Type::Array, SRI::VisitAction::End),
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::End)
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::End)
};
Visit(expected, "/Test");
@@ -900,7 +932,7 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object0", "Object0", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object0/Field", "Field", SRI::Type::Integer, SRI::VisitAction::Value),
@@ -928,7 +960,7 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object0", "Object0", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Object0/Field", "Field", SRI::Type::Integer, SRI::VisitAction::Value),
@@ -954,7 +986,7 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Array0", "Array0", SRI::Type::Array, SRI::VisitAction::Begin),
RegistryEntry("/Test/Array0/0", "0", SRI::Type::Integer, SRI::VisitAction::Value),
@@ -984,7 +1016,7 @@ namespace SettingsRegistryTests
AZStd::vector<RegistryEntry> expected =
{
RegistryEntry("/Test", "", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test", "Test", SRI::Type::Object, SRI::VisitAction::Begin),
RegistryEntry("/Test/Array0", "Array0", SRI::Type::Array, SRI::VisitAction::Begin),
RegistryEntry("/Test/Array0/0", "0", SRI::Type::Integer, SRI::VisitAction::Value),
@@ -1700,11 +1732,7 @@ namespace SettingsRegistryTests
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/0/Path"));
}
#if AZ_TRAIT_DISABLE_FAILED_MERGESETTINGSFOLDER_CONFLICTINGSPECIALIZATIONS
TEST_F(SettingsRegistryTest, DISABLED_MergeSettingsFolder_ConflictingSpecializations_ReportsErrorAndReturnsFalse)
#else
TEST_F(SettingsRegistryTest, MergeSettingsFolder_ConflictingSpecializations_ReportsErrorAndReturnsFalse)
#endif // AZ_TRAIT_DISABLE_FAILED_MERGESETTINGSFOLDER_CONFLICTINGSPECIALIZATIONS
{
CreateTestFile("Memory.test.editor.setreg", "{}");
CreateTestFile("Memory.editor.test.setreg", "{}");
@@ -22,6 +22,35 @@ namespace AZ
using StringFuncTest = AllocatorsFixture;
TEST_F(StringFuncTest, Equal_CaseSensitive_OnNonNullTerminatedStringView_Success)
{
constexpr AZStd::string_view testString1 = "Hello World IceCream";
constexpr AZStd::string_view testString2 = "IceCream World Hello";
constexpr bool caseSensitive = true;
EXPECT_TRUE(AZ::StringFunc::Equal(testString1.substr(6,5), testString2.substr(9,5), caseSensitive));
}
TEST_F(StringFuncTest, Equal_CaseInsensitive_OnNonNullTerminatedStringView_Success)
{
constexpr AZStd::string_view testString1 = "Hello World IceCream";
constexpr AZStd::string_view testString2 = "IceCream woRLd Hello";
constexpr bool caseSensitive = false;
EXPECT_TRUE(AZ::StringFunc::Equal(testString1.substr(6, 5), testString2.substr(9, 5), caseSensitive));
}
TEST_F(StringFuncTest, Equal_CaseSensitive_OnNonNullTerminatedStringView_WithDifferentCases_Fails)
{
constexpr AZStd::string_view testString1 = "Hello World IceCream";
constexpr AZStd::string_view testString2 = "IceCream woRLd Hello";
constexpr bool caseSensitive = true;
EXPECT_FALSE(AZ::StringFunc::Equal(testString1.substr(6, 5), testString2.substr(9, 5), caseSensitive));
}
TEST_F(StringFuncTest, Equal_OnNonNullTerminatedStringView_WithDifferentSize_Fails)
{
constexpr AZStd::string_view testString1 = "Hello World IceCream";
constexpr AZStd::string_view testString2 = "IceCream World Hello";
EXPECT_FALSE(AZ::StringFunc::Equal(testString1.substr(6, 6), testString2.substr(9, 5)));
}
// Strip out any trailing path separators
TEST_F(StringFuncTest, Strip_ValidInputExtraEndingPathSeparators_Success)