Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,98 @@
/*
* 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 <Application.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AZ
{
namespace SerializeContextTools
{
Application::Application(int* argc, char*** argv)
: AzToolsFramework::ToolsApplication(argc, argv)
{
AZ::IO::FixedMaxPath sourceGameFolder;
if (!m_settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder))
{
AZ_Error("Serialize Context Tools", false, "Unable to determine the game root automatically. "
"Make sure a default project has been set or provide a default option on the command line. (See -help for more info.)");
return;
}
AZStd::string configFilePath = "Config/Editor.xml";
if (m_commandLine.HasSwitch("config"))
{
configFilePath = m_commandLine.GetSwitchValue("config", 0);
}
AZ::IO::FixedMaxPath absConfigFilePath = sourceGameFolder / configFilePath;
if (AZ::IO::SystemFile::Exists(absConfigFilePath.c_str()))
{
m_configFilePath = AZStd::move(absConfigFilePath);
}
else
{
AZ_Error("Serialize Context Tools", false, "Unable to resolve path to config file.");
}
// Merge the build system generated setting registry file by using either "Editor" or
// and "${ProjectName}_GameLauncher" as a specialization
bool projectNameFound{};
AZ::SettingsRegistryInterface::FixedValueString projectName;
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
constexpr auto sysGameFolderKey = AZ::SettingsRegistryInterface::FixedValueString(
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/sys_game_folder";
if (projectNameFound = registry.Get(projectName, sysGameFolderKey); !projectNameFound)
{
AZ_Error("Serialize Context Tools", false, "Unable to query the %s key from the SettingsRegistry",
sysGameFolderKey.c_str());
}
else
{
AZ::IO::PathView configFilenameStem = m_configFilePath.Stem();
AZ::SettingsRegistryInterface::Specializations projectSpecializations{ projectName };
size_t configFilenameStemSize = configFilenameStem.Native().size();
if (configFilenameStemSize > 0 && azstrnicmp(configFilenameStem.Native().data(), "Editor", configFilenameStemSize) == 0)
{
projectSpecializations.Append("editor");
}
else if (configFilenameStemSize > 0 && azstrnicmp(configFilenameStem.Native().data(), "Game", configFilenameStemSize) == 0)
{
projectSpecializations.Append(projectName + "_GameLauncher");
}
else
{
AZ_TracePrintf("Serialize Context Tools", "No Editor.xml or Game.xml supplied."
R"( Build dependency specialization will not use a specialization of "%s" nor "editor" for locating *.setreg files)",
(projectName + "_GameLauncher").c_str());
}
// Used the project specializations to merge the build dependencies *.setreg files
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_TargetBuildDependencyRegistry(registry,
AZ_TRAIT_OS_PLATFORM_CODENAME, projectSpecializations);
}
}
const char* Application::GetConfigFilePath() const
{
return m_configFilePath.c_str();
}
void Application::SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations)
{
AzToolsFramework::ToolsApplication::SetSettingsRegistrySpecializations(specializations);
specializations.Append("serializecontexttools");
}
} // namespace SerializeContextTools
} // namespace AZ
@@ -0,0 +1,37 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzCore/IO/Path/Path.h>
namespace AZ
{
namespace SerializeContextTools
{
class Application final
: public AzToolsFramework::ToolsApplication
{
public:
Application(int* argc, char*** argv);
~Application() override = default;
const char* GetConfigFilePath() const;
protected:
void SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations) override;
AZ::IO::FixedMaxPath m_configFilePath;
};
} // namespace SerializeContextTools
} // namespace AZ
@@ -0,0 +1,34 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
if (NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
include(Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
if (NOT PAL_TRAIT_BUILD_SERIALIZECONTEXTTOOLS)
return()
endif()
ly_add_target(
NAME SerializeContextTools EXECUTABLE
NAMESPACE AZ
FILES_CMAKE
serializecontexttools_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework
)
@@ -0,0 +1,922 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/Entity.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/Module/Module.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <Application.h>
#include <Converter.h>
#include <Utilities.h>
namespace AZ
{
namespace SerializeContextTools
{
bool Converter::ConvertObjectStreamFiles(Application& application)
{
using namespace AZ::JsonSerializationResult;
const AzFramework::CommandLine* commandLine = application.GetCommandLine();
if (!commandLine)
{
AZ_Error("SerializeContextTools", false, "Command line not available.");
return false;
}
JsonSerializerSettings convertSettings;
convertSettings.m_keepDefaults = commandLine->HasSwitch("keepdefaults");
convertSettings.m_registrationContext = application.GetJsonRegistrationContext();
convertSettings.m_serializeContext = application.GetSerializeContext();
if (!convertSettings.m_serializeContext)
{
AZ_Error("Convert", false, "No serialize context found.");
return false;
}
if (!convertSettings.m_registrationContext)
{
AZ_Error("Convert", false, "No json registration context found.");
return false;
}
AZStd::string logggingScratchBuffer;
SetupLogging(logggingScratchBuffer, convertSettings.m_reporting, *commandLine);
if (!commandLine->HasSwitch("ext"))
{
AZ_Error("Convert", false, "No extension provided through the 'ext' argument.");
return false;
}
const AZStd::string& extension = commandLine->GetSwitchValue("ext", 0);
bool isDryRun = commandLine->HasSwitch("dryrun");
bool skipVerify = commandLine->HasSwitch("skipverify");
JsonDeserializerSettings verifySettings;
if (!skipVerify)
{
verifySettings.m_registrationContext = application.GetJsonRegistrationContext();
verifySettings.m_serializeContext = application.GetSerializeContext();
SetupLogging(logggingScratchBuffer, verifySettings.m_reporting, *commandLine);
}
bool result = true;
rapidjson::StringBuffer scratchBuffer;
AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
for (AZStd::string& filePath : fileList)
{
AZ_Printf("Convert", "Converting '%s'\n", filePath.c_str());
PathDocumentContainer documents;
auto callback = [&result, &documents, &extension, &convertSettings, &verifySettings, skipVerify]
(void* classPtr, const Uuid& classId, SerializeContext* /*context*/)
{
rapidjson::Document document;
ResultCode parseResult = JsonSerialization::Store(document.SetObject(), document.GetAllocator(), classPtr, nullptr, classId, convertSettings);
if (parseResult.GetProcessing() != Processing::Halted)
{
if (skipVerify || VerifyConvertedData(document, classPtr, classId, verifySettings))
{
if (parseResult.GetOutcome() == Outcomes::DefaultsUsed)
{
AZ_Printf("Convert", " File not converted as only default values were found.\n");
}
else
{
documents.emplace_back(GetClassName(classId, convertSettings.m_serializeContext), AZStd::move(document));
}
}
else
{
AZ_Printf("Convert", " Verification of the converted file failed.\n");
result = false;
}
}
else
{
AZ_Printf("Convert", " Conversion to JSON failed.\n");
result = false;
}
return true;
};
if (!Utilities::InspectSerializedFile(filePath, convertSettings.m_serializeContext, callback))
{
AZ_Warning("Convert", false, "Failed to load '%s'. File may not contain an object stream.", filePath.c_str());
result = false;
}
// If there's only one file, then use the original name instead of the extended name
AzFramework::StringFunc::Path::ReplaceExtension(filePath, extension.c_str());
if (documents.size() == 1)
{
AZ_Printf("Convert", " Exporting to '%s'\n", filePath.c_str());
if (!isDryRun)
{
AZStd::string jsonDocumentRootPrefix;
if (commandLine->HasSwitch("json-prefix"))
{
jsonDocumentRootPrefix = commandLine->GetSwitchValue("json-prefix", 0);
}
result = WriteDocumentToDisk(filePath, documents[0].second, jsonDocumentRootPrefix, scratchBuffer) && result;
scratchBuffer.Clear();
}
}
else
{
AZStd::string fileName;
AzFramework::StringFunc::Path::GetFileName(filePath.c_str(), fileName);
for (PathDocumentPair& document : documents)
{
AZStd::string fileNameExtended = fileName;
fileNameExtended += '_';
fileNameExtended += document.first;
Utilities::SanitizeFilePath(fileNameExtended);
AZStd::string finalFilePath = filePath;
AzFramework::StringFunc::Path::ReplaceFullName(finalFilePath, fileNameExtended.c_str(), extension.c_str());
AZ_Printf("Convert", " Exporting to '%s'\n", finalFilePath.c_str());
if (!isDryRun)
{
AZStd::string jsonDocumentRootPrefix;
if (commandLine->HasSwitch("json-prefix"))
{
jsonDocumentRootPrefix = commandLine->GetSwitchValue("json-prefix", 0);
}
result = WriteDocumentToDisk(finalFilePath, document.second, jsonDocumentRootPrefix, scratchBuffer) && result;
scratchBuffer.Clear();
}
}
}
}
return result;
}
bool Converter::ConvertApplicationDescriptor(Application& application)
{
const AzFramework::CommandLine* commandLine = application.GetCommandLine();
if (!commandLine)
{
AZ_Error("SerializeContextTools", false, "Command line not available.");
return false;
}
JsonSerializerSettings convertSettings;
convertSettings.m_keepDefaults = commandLine->HasSwitch("keepdefaults");
convertSettings.m_registrationContext = application.GetJsonRegistrationContext();
convertSettings.m_serializeContext = application.GetSerializeContext();
if (!convertSettings.m_serializeContext)
{
AZ_Error("Convert", false, "No serialize context found.");
return false;
}
if (!convertSettings.m_registrationContext)
{
AZ_Error("Convert", false, "No json registration context found.");
return false;
}
AZStd::string logggingScratchBuffer;
SetupLogging(logggingScratchBuffer, convertSettings.m_reporting, *commandLine);
JsonDeserializerSettings verifySettings;
verifySettings.m_registrationContext = application.GetJsonRegistrationContext();
verifySettings.m_serializeContext = application.GetSerializeContext();
SetupLogging(logggingScratchBuffer, verifySettings.m_reporting, *commandLine);
bool skipGems = commandLine->HasSwitch("skipgems");
bool skipSystem = commandLine->HasSwitch("skipsystem");
bool isDryRun = commandLine->HasSwitch("dryrun");
const char* appRoot = const_cast<const Application&>(application).GetAppRoot();
PathDocumentContainer documents;
bool result = true;
const AZStd::string& filePath = application.GetConfigFilePath();
AZ_Printf("Convert", "Reading '%s' for conversion.\n", filePath.c_str());
AZStd::string configurationName;
if (!AzFramework::StringFunc::Path::GetFileName(filePath.c_str(), configurationName) ||
configurationName.empty())
{
AZ_Error("Convert", false, "Unable to extract configuration from '%s'.", filePath.c_str());
return false;
}
// Most folder names start with a capital letter, but most files with lower case. As the configuration name
// will be used as a folder, turn the first letter into a capital one.
AZStd::to_upper(configurationName.begin(), configurationName.begin() + 1);
AZ::IO::FixedMaxPath sourceGameFolder;
if (auto settingsRegistry = AZ::SettingsRegistry::Get();
!settingsRegistry
|| !settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder))
{
AZ_Error("Serialize Context Tools", false, "Unable to determine the game root automatically. "
"Make sure a default project has been set or provide a default option on the command line. (See -help for more info.)");
return false;
}
auto callback =
[&result, skipGems, skipSystem, &configurationName, sourceGameFolder, &appRoot, &documents, &convertSettings, &verifySettings]
(void* classPtr, const Uuid& classId, SerializeContext* context)
{
if (classId == azrtti_typeid<AZ::ComponentApplication::Descriptor>())
{
if (!skipSystem)
{
result = ConvertSystemSettings(documents, *reinterpret_cast<AZ::ComponentApplication::Descriptor*>(classPtr),
configurationName, sourceGameFolder, appRoot) && result;
}
// Cleanup the Serialized Element to allow any classes within the element's hierarchy to delete
// memory allocated by the SerializeContext
const AZ::SerializeContext::ClassData* classData = context->FindClassData(classId);
if (classData)
{
classData->m_factory->Destroy(classPtr);
}
}
else if (classId == azrtti_typeid<Entity>())
{
if (!skipSystem)
{
result = ConvertSystemComponents(documents, *reinterpret_cast<Entity*>(classPtr), configurationName,
sourceGameFolder, convertSettings, verifySettings) && result;
}
const AZ::SerializeContext::ClassData* classData = context->FindClassData(classId);
if (classData)
{
classData->m_factory->Destroy(classPtr);
}
}
else if (classId == azrtti_typeid<ModuleEntity>())
{
if (!skipGems)
{
result = ConvertModuleComponents(documents, *reinterpret_cast<ModuleEntity*>(classPtr), configurationName,
convertSettings, verifySettings) && result;
}
const AZ::SerializeContext::ClassData* classData = context->FindClassData(classId);
if (classData)
{
classData->m_factory->Destroy(classPtr);
}
}
else
{
AZ_Warning("Convert", false, "Unable to process component in Application Descriptor of type '%s'.",
classId.ToString<AZStd::string>().c_str());
result = false;
}
return true;
};
if (!Utilities::InspectSerializedFile(filePath, convertSettings.m_serializeContext, callback))
{
AZ_Warning("Convert", false, "Failed to load '%s'. File may not contain an object stream.", filePath.c_str());
result = false;
}
if (!isDryRun)
{
AZStd::string jsonDocumentRootPrefix;
if (commandLine->HasSwitch("json-prefix"))
{
jsonDocumentRootPrefix = commandLine->GetSwitchValue("json-prefix", 0);
}
rapidjson::StringBuffer scratchBuffer;
for (auto& pathDocPair : documents)
{
result = WriteDocumentToDisk(pathDocPair.first, pathDocPair.second, jsonDocumentRootPrefix, scratchBuffer) && result;
scratchBuffer.Clear();
}
}
return result;
}
bool Converter::ConvertConfigFile(Application& application)
{
bool result = true;
const AzFramework::CommandLine* commandLine = application.GetCommandLine();
if (!commandLine)
{
AZ_Error("SerializeContextTools", false, "Command line not available.");
return false;
}
AZStd::string outputExtension;
if (!commandLine->HasSwitch("ext"))
{
AZ_TracePrintf("Convert", "No extension provided through the 'ext' argument.\nThe extension of .setreg will be used instead\n");
outputExtension = "setreg";
}
else
{
outputExtension = commandLine->GetSwitchValue("ext", 0);
}
const bool isDryRun = commandLine->HasSwitch("dryrun");
// Use the Engine Root Folder from the Global Settings Registry
AZ::IO::FixedMaxPath engineRootPath;
if (auto globalSettingsRegistry = AZ::SettingsRegistry::Get(); globalSettingsRegistry != nullptr)
{
globalSettingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
// The AZ CommandLine internally splits switches on <comma> and semicolon
AZStd::vector<AZStd::string_view> fileList;
size_t filesToConvert = commandLine->GetNumSwitchValues("files");
for (size_t fileIndex{}; fileIndex < filesToConvert; ++fileIndex)
{
fileList.emplace_back(commandLine->GetSwitchValue("files", fileIndex));
}
// Gather list of INI style files to convert using the SystemFile::FindFiles function
PathDocumentContainer documents;
for (AZStd::string_view configFileView : fileList)
{
// Appends the paths in "files" argument to the EngineRootPath
// If the "files" argument is absolute AZ::IO::Path follows the python os.join logic
// and replaces the paths before it with the absolute path
AZ::IO::FixedMaxPath configFilePath = engineRootPath / configFileView;
auto callback = [&documents, &outputExtension, &configFilePath](AZ::IO::PathView configFileView, bool isFile) -> bool
{
if (configFileView == "." || configFileView == "..")
{
return true;
}
if (isFile)
{
AZ::IO::FixedMaxPath foundFilePath{ configFilePath.ParentPath() };
foundFilePath /= configFileView;
// Initialize added documents with an empty JSON object(instead of a JSON null)
// This prevents a JSON document from being output with just null when there
// are no configuration entries
documents.emplace_back(foundFilePath.String(), rapidjson::Document{rapidjson::kObjectType});
}
return true;
};
AZ::IO::SystemFile::FindFiles(configFilePath.c_str(), callback);
}
// JSON pointer prefix to use as a temporary root for merging the config file to the settings registry
// and dumping it to a rapidjson document. The prefix is used to make sure other settings outside
// of the config settings are not output
constexpr AZStd::string_view ConvertJsonPointer = "/Amazon/Config/Root";
for (auto&& [iniFilename, iniJsonDocument] : documents)
{
// Local Settings Registry is used to contain only the converted INI-style file settings
AZ::SettingsRegistryImpl settingsRegistry;
AZ::SettingsRegistryMergeUtils::ConfigParserSettings configParserSettings;
configParserSettings.m_commentPrefixFunc = [](AZStd::string_view line) -> AZStd::string_view
{
constexpr AZStd::string_view commentPrefixes[]{ "--", ";","#" };
for (AZStd::string_view commentPrefix : commentPrefixes)
{
if (size_t commentOffset = line.find(commentPrefix); commentOffset != AZStd::string_view::npos)
{
return line.substr(0, commentOffset);
}
}
return line;
};
configParserSettings.m_registryRootPointerPath = ConvertJsonPointer;
if (!AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ConfigFile(settingsRegistry, iniFilename, configParserSettings))
{
AZ_TracePrintf("Convert", "Merging of config file %s has failed. It will be skipped", iniFilename.c_str());
result = false;
continue;
}
// If the Config file contained no settings, then Settings Registry contains no settings to dump at the JSON Pointer
// In this scenario there are no settings to dump so continue to the next iteration
if (settingsRegistry.GetType(ConvertJsonPointer) == AZ::SettingsRegistryInterface::Type::Object)
{
// Dump the Settings Registry to a string that can be stored in a rapidjson::Document
AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings;
dumperSettings.m_prettifyOutput = true;
AZStd::string configJson;
AZ::IO::ByteContainerStream configJsonStream(&configJson);
if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(settingsRegistry, ConvertJsonPointer, configJsonStream, dumperSettings))
{
AZ_TracePrintf("Convert", "Config Settings for file %s cannot be queried from the Setting Registry", iniFilename.c_str());
result = false;
continue;
}
iniJsonDocument.Parse(configJson.c_str());
}
else
{
AZ_TracePrintf("Convert", "Config file %s contained no convertible settings, an empty JSON object anchored"
" at the -json-prefix will be output", iniFilename.c_str());
}
}
if (!isDryRun)
{
AZStd::string jsonDocumentRootPrefix;
if (commandLine->GetNumSwitchValues("json-prefix") > 0)
{
jsonDocumentRootPrefix = commandLine->GetSwitchValue("json-prefix", 0);
}
rapidjson::StringBuffer scratchBuffer;
for (auto&& [iniFilename, iniJsonDocument] : documents)
{
// Update the extension on the the input filename at this point
AZ::IO::Path outputFilename{ AZStd::move(iniFilename) };
outputFilename.ReplaceExtension(AZ::IO::PathView(outputExtension));
result = WriteDocumentToDisk(outputFilename.Native(), iniJsonDocument, jsonDocumentRootPrefix, scratchBuffer) && result;
scratchBuffer.Clear();
}
}
return result;
}
bool Converter::ConvertSystemSettings(PathDocumentContainer& documents, const ComponentApplication::Descriptor& descriptor,
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder, [[maybe_unused]] const AZStd::string& applicationRoot)
{
AZ::IO::FixedMaxPath memoryFilePath{ projectFolder };
memoryFilePath /= "Registry";
AZ::IO::FixedMaxPath modulesFilePath = memoryFilePath;
AZStd::string configurationNameLower = configurationName;
AZStd::to_lower(configurationNameLower.begin(), configurationNameLower.end());
modulesFilePath /= AZ::IO::FixedMaxPathString::format("module.%s.setreg", configurationNameLower.c_str());
memoryFilePath /= AZ::IO::FixedMaxPathString::format("memory.%s.setreg", configurationNameLower.c_str());
AZ_Printf("Convert", " Exporting application descriptor to '%s' and '%s'.\n", memoryFilePath.c_str(), modulesFilePath.c_str());
rapidjson::Document modulesDoc;
modulesDoc.SetObject();
rapidjson::Value moduleList(rapidjson::kArrayType);
for (auto& module : descriptor.m_modules)
{
moduleList.PushBack(rapidjson::StringRef(module.m_dynamicLibraryPath.c_str()), modulesDoc.GetAllocator());
}
modulesDoc.AddMember(rapidjson::StringRef("Modules"), AZStd::move(moduleList), modulesDoc.GetAllocator());
struct GemVisitor
: public AZ::SettingsRegistryInterface::Visitor
{
GemVisitor(rapidjson::Value& gemSourcePaths, rapidjson::Document& modulesDoc)
: m_gemSourcePaths{ gemSourcePaths }
, m_modulesDoc{ modulesDoc }
{}
AZ::SettingsRegistryInterface::VisitResponse Traverse([[maybe_unused]] AZStd::string_view path, [[maybe_unused]] AZStd::string_view valueName,
AZ::SettingsRegistryInterface::VisitAction action, [[maybe_unused]] AZ::SettingsRegistryInterface::Type type) override
{
if (valueName == "SourcePaths")
{
if (action == AZ::SettingsRegistryInterface::VisitAction::Begin)
{
// Allows merging of the registry folders within the gem source path array
// via the Visit function
m_processingSourcePathKey = true;
}
else if (action == AZ::SettingsRegistryInterface::VisitAction::End)
{
// The end of the gem source path array has been reached
m_processingSourcePathKey = false;
}
}
return AZ::SettingsRegistryInterface::VisitResponse::Continue;
}
void Visit(AZStd::string_view, [[maybe_unused]] AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value) override
{
if (m_processingSourcePathKey)
{
m_gemSourcePaths.PushBack(rapidjson::StringRef(value.data(), value.size()), m_modulesDoc.GetAllocator());
}
}
rapidjson::Value& m_gemSourcePaths;
rapidjson::Document& m_modulesDoc;
bool m_processingSourcePathKey{};
};
// Visit each gem target "SourcePaths" entry within the settings registry
rapidjson::Value gemPathList(rapidjson::kArrayType);
GemVisitor visitor{ gemPathList, modulesDoc };
const auto gemListKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/Gems",
AZ::SettingsRegistryMergeUtils::OrganizationRootKey);
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
registry.Visit(visitor, gemListKey);
modulesDoc.AddMember(rapidjson::StringRef("GemFolders"), AZStd::move(gemPathList), modulesDoc.GetAllocator());
documents.emplace_back(AZStd::move(modulesFilePath.Native()), AZStd::move(modulesDoc));
rapidjson::Document memoryDoc;
memoryDoc.SetObject();
memoryDoc.AddMember(rapidjson::StringRef("useExistingAllocator"),
rapidjson::Value(descriptor.m_useExistingAllocator), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("grabAllMemory"),
rapidjson::Value(descriptor.m_grabAllMemory), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("allocationRecords"),
rapidjson::Value(descriptor.m_allocationRecords), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("allocationRecordsSaveNames"),
rapidjson::Value(descriptor.m_allocationRecordsSaveNames), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("allocationRecordsAttemptDecodeImmediately"),
rapidjson::Value(descriptor.m_allocationRecordsAttemptDecodeImmediately), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("recordingMode"),
rapidjson::Value(descriptor.m_recordingMode), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("stackRecordLevels"),
rapidjson::Value(descriptor.m_stackRecordLevels), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("autoIntegrityCheck"),
rapidjson::Value(descriptor.m_autoIntegrityCheck), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("markUnallocatedMemory"),
rapidjson::Value(descriptor.m_markUnallocatedMemory), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("doNotUsePools"),
rapidjson::Value(descriptor.m_doNotUsePools), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("enableScriptReflection"),
rapidjson::Value(descriptor.m_enableScriptReflection), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("pageSize"),
rapidjson::Value(descriptor.m_pageSize), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("poolPageSize"),
rapidjson::Value(descriptor.m_poolPageSize), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("blockAlignment"),
rapidjson::Value(descriptor.m_memoryBlockAlignment), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("blockSize"),
rapidjson::Value(descriptor.m_memoryBlocksByteSize), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("reservedOS"),
rapidjson::Value(descriptor.m_reservedOS), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("reservedDebug"),
rapidjson::Value(descriptor.m_reservedDebug), memoryDoc.GetAllocator());
memoryDoc.AddMember(rapidjson::StringRef("enableDrilling"),
rapidjson::Value(descriptor.m_enableDrilling), memoryDoc.GetAllocator());
documents.emplace_back(AZStd::move(memoryFilePath.Native()), AZStd::move(memoryDoc));
return true;
}
bool Converter::ConvertSystemComponents(PathDocumentContainer& documents, const Entity& entity, const AZStd::string& configurationName,
const AZ::IO::PathView& projectFolder, const JsonSerializerSettings& convertSettings, const JsonDeserializerSettings& verifySettings)
{
using namespace AZ::JsonSerializationResult;
AZ::IO::FixedMaxPath systemFilePath{ projectFolder };
systemFilePath /= "Registry";
AZStd::string configurationNameLower = configurationName;
AZStd::to_lower(configurationNameLower.begin(), configurationNameLower.end());
systemFilePath /= AZ::IO::FixedMaxPathString::format("system.%s.setreg", configurationNameLower.c_str());
AZ_Printf("Convert", " Exporting Entity to '%s'\n", systemFilePath.c_str());
rapidjson::Document systemSettings;
ResultCode result = JsonSerialization::Store(systemSettings.SetObject(), systemSettings.GetAllocator(), entity, convertSettings);
if (result.GetProcessing() != Processing::Halted)
{
if (!VerifyConvertedData(systemSettings, &entity, azrtti_typeid(entity), verifySettings))
{
// Errors will already be reported by VerifyConvertedData.
return false;
}
if (result.GetProcessing() != Processing::Halted)
{
if (result.GetOutcome() == Outcomes::DefaultsUsed)
{
AZ_Printf("Convert", " System settings not exported as only default values were found.\n");
}
else
{
documents.emplace_back(AZStd::move(systemFilePath.Native()), AZStd::move(systemSettings));
}
}
else
{
AZ_Printf("Convert", " System settings not exported.\n");
}
return true;
}
else
{
// Other errors will already have been reported by the JsonSerialierManager.
return false;
}
}
bool Converter::ConvertModuleComponents(PathDocumentContainer& documents, const ModuleEntity& entity,
const AZStd::string& configurationName, const JsonSerializerSettings& convertSettings, const JsonDeserializerSettings& verifySettings)
{
using namespace AZ::JsonSerializationResult;
AZStd::fixed_string<128> gemName;
AZStd::vector<AZ::IO::FixedMaxPath> gemModuleSourcePaths;
AZ::ModuleManagerRequestBus::Broadcast([&gemModuleSourcePaths, &gemName, gemModuleClassId = entity.m_moduleClassId](AZ::ModuleManagerRequests* request)
{
request->EnumerateModules([&gemModuleSourcePaths, &gemName, &gemModuleClassId](const AZ::ModuleData& moduleData) -> bool
{
AZ::Module* moduleInst = moduleData.GetModule();
if (moduleInst && AZ::RttiTypeId(*moduleInst) == gemModuleClassId)
{
struct GemBuildSystemVisitor
: AZ::SettingsRegistryInterface::Visitor
{
GemBuildSystemVisitor(AZStd::string_view moduleFilename, AZStd::vector<AZ::IO::FixedMaxPath>& gemSourcePaths)
: m_gemModuleFilename(moduleFilename)
, m_gemSourcePaths(gemSourcePaths)
{}
AZ::SettingsRegistryInterface::VisitResponse Traverse([[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName,
AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type) override
{
if (m_gemSourcePathStored)
{
return AZ::SettingsRegistryInterface::VisitResponse::Done;
}
// Store off the name of the Gem target when it is parsed underneath the /Amazon/Gems JSON pointer path
// The names of gems are keys on the /Amazon/Gems JSON object which is at a depth of 1
if (m_keyDepthIndex == 1)
{
m_gemName = valueName;
}
if (action == SettingsRegistryInterface::VisitAction::Begin)
{
++m_keyDepthIndex;
}
else if (action == SettingsRegistryInterface::VisitAction::End)
{
--m_keyDepthIndex;
}
return AZ::SettingsRegistryInterface::VisitResponse::Continue;
}
void Visit(AZStd::string_view path, AZStd::string_view valueName, [[maybe_unused]] AZ::SettingsRegistryInterface::Type type,
AZStd::string_view value) override
{
if (valueName == "Module" && m_gemModuleFilename.find(value) != AZStd::string_view::npos)
{
m_moduleFilenameMatches = true;
}
else if (m_moduleFilenameMatches && path.find("SourcePaths") != AZStd::string_view::npos)
{
m_gemSourcePaths.emplace_back(value);
m_gemSourcePathStored = true;
m_moduleFilenameMatches = false;
}
}
AZStd::string_view m_gemModuleFilename;
AZStd::vector<AZ::IO::FixedMaxPath>& m_gemSourcePaths;
AZStd::fixed_string<128> m_gemName;
bool m_moduleFilenameMatches{};
bool m_gemSourcePathStored{};
int32_t m_keyDepthIndex{};
};
GemBuildSystemVisitor visitor{ AZStd::string_view{moduleData.GetDynamicModuleHandle()->GetFilename()}, gemModuleSourcePaths };
const auto gemListKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/Gems",
AZ::SettingsRegistryMergeUtils::OrganizationRootKey);
AZ::SettingsRegistry::Get()->Visit(visitor, gemListKey);
gemName = visitor.m_gemName;
}
return true;
});
});
if (gemModuleSourcePaths.empty())
{
AZ_Warning("Convert", false, "Unable to find a gem folder to write output registry for module entity '%s'.", entity.GetName().c_str());
return false;
}
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
AZ::IO::FixedMaxPath registryPath;
if (!settingsRegistry->Get(registryPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
AZ_Warning("Convert", false, "Unable To find Engine Root Path at key '%s' in the Settings Registry",
AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
registryPath /= gemModuleSourcePaths.front();
registryPath /= "Registry";
AZStd::string configurationNameLower = configurationName;
AZStd::to_lower(configurationNameLower.begin(), configurationNameLower.end());
registryPath /= AZ::IO::FixedMaxPathString::format("gem.%s.setreg", configurationNameLower.c_str());
AZ_Printf("Convert", " Exporting ModuleEntity to '%s'\n", registryPath.c_str());
rapidjson::Document moduleSettings;
moduleSettings.SetObject().AddMember(rapidjson::Value(gemName.c_str(),
aznumeric_cast<rapidjson::SizeType>(gemName.size()), moduleSettings.GetAllocator()),
rapidjson::Value(rapidjson::kObjectType).Move(), moduleSettings.GetAllocator());
rapidjson::Value& moduleSettingsValue = moduleSettings[gemName.c_str()];
ResultCode result = JsonSerialization::Store(moduleSettingsValue, moduleSettings.GetAllocator(), entity, convertSettings);
if (result.GetProcessing() != Processing::Halted)
{
if (!VerifyConvertedData(moduleSettingsValue, &entity, azrtti_typeid(entity), verifySettings))
{
// Errors will already be reported by VerifyConvertedData.
return false;
}
if (result.GetProcessing() != Processing::Halted)
{
if (result.GetOutcome() == Outcomes::DefaultsUsed)
{
AZ_Printf("Convert", " Gem settings not exported as only default values were found.\n");
}
else
{
// Add Converted module settings in a JSON pointer path underneath the Gem Name
documents.emplace_back(AZStd::move(registryPath.Native()), AZStd::move(moduleSettings));
}
}
else
{
AZ_Printf("Convert", " Gem settings not exported.\n");
}
return true;
}
else
{
// Other errors will already have been reported by the JsonSerialization.
return false;
}
}
bool Converter::VerifyConvertedData(rapidjson::Value& convertedData, const void* original, const Uuid& originalType,
const JsonDeserializerSettings& settings)
{
using namespace AZ::JsonSerializationResult;
AZStd::any convertedDeserialized = settings.m_serializeContext->CreateAny(originalType);
if (convertedDeserialized.empty())
{
AZ_Printf("Convert", " Failed to deserialized from converted document.\n");
return false;
}
ResultCode loadResult = JsonSerialization::Load(AZStd::any_cast<void>(&convertedDeserialized), originalType, convertedData, settings);
if (loadResult.GetProcessing() == Processing::Halted)
{
AZ_Printf("Convert", " Failed to verify converted document because it couldn't be loaded.\n");
return false;
}
const SerializeContext::ClassData* data = settings.m_serializeContext->FindClassData(originalType);
if (!data)
{
AZ_Printf("Convert", " Failed to find serialization information for type '%s'.\n",
originalType.ToString<AZStd::string>().c_str());
return false;
}
bool result = false;
if (data->m_serializer)
{
result = data->m_serializer->CompareValueData(original, AZStd::any_cast<void>(&convertedDeserialized));
}
else
{
AZStd::vector<AZ::u8> originalData;
AZ::IO::ByteContainerStream<decltype(originalData)> orignalStream(&originalData);
AZ::Utils::SaveObjectToStream(orignalStream, AZ::ObjectStream::ST_BINARY, original, originalType);
AZStd::vector<AZ::u8> loadedData;
AZ::IO::ByteContainerStream<decltype(loadedData)> loadedStream(&loadedData);
AZ::Utils::SaveObjectToStream(loadedStream, AZ::ObjectStream::ST_BINARY,
AZStd::any_cast<void>(&convertedDeserialized), convertedDeserialized.type());
result =
(originalData.size() == loadedData.size()) &&
(memcmp(originalData.data(), loadedData.data(), originalData.size()) == 0);
}
if (!result)
{
AZ_Printf("Convert", " Differences found between the original and converted data.\n");
}
return result;
}
AZStd::string Converter::GetClassName(const Uuid& classId, SerializeContext* context)
{
const SerializeContext::ClassData* data = context->FindClassData(classId);
if (data)
{
if (data->m_editData)
{
return data->m_editData->m_name;
}
else
{
return data->m_name;
}
}
else
{
return classId.ToString<AZStd::string>();
}
}
bool Converter::WriteDocumentToDisk(const AZStd::string& filename, const rapidjson::Document& document,
AZStd::string_view pointerRoot, rapidjson::StringBuffer& scratchBuffer)
{
IO::SystemFile outputFile;
if (!outputFile.Open(filename.c_str(),
IO::SystemFile::OpenMode::SF_OPEN_CREATE |
IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
{
AZ_Error("SerializeContextTools", false, "Unable to open output file '%s'.", filename.c_str());
return false;
}
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(scratchBuffer);
// rapidjson::Pointer constructor attempts to dereference the const char* index 0 even if the size is 0
// so make sure an string_view isn't referencing a nullptr
rapidjson::Pointer jsonPointerAnchor(pointerRoot.data() ? pointerRoot.data() : "", pointerRoot.size());
// Anchor the content in the Json Document under the Json Pointer root path
rapidjson::Document rootDocument;
rapidjson::SetValueByPointer(rootDocument, jsonPointerAnchor, document);
rootDocument.Accept(writer);
outputFile.Write(scratchBuffer.GetString(), scratchBuffer.GetSize());
outputFile.Close();
scratchBuffer.Clear();
return true;
}
void Converter::SetupLogging(AZStd::string& scratchBuffer, JsonSerializationResult::JsonIssueCallback& callback,
const AzFramework::CommandLine& commandLine)
{
if (commandLine.HasSwitch("verbose"))
{
callback = [&scratchBuffer](
AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path)
->JsonSerializationResult::ResultCode
{
return VerboseLogging(scratchBuffer, message, result, path);
};
}
else
{
callback = [&scratchBuffer](
AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path)
->JsonSerializationResult::ResultCode
{
return SimpleLogging(scratchBuffer, message, result, path);
};
}
}
AZ::JsonSerializationResult::ResultCode Converter::VerboseLogging(AZStd::string& scratchBuffer, AZStd::string_view message,
AZ::JsonSerializationResult::ResultCode result, AZStd::string_view path)
{
scratchBuffer.append(message.begin(), message.end());
scratchBuffer.append("\n Reason: ");
result.AppendToString(scratchBuffer, path);
scratchBuffer.append(".\n");
AZ_Printf("SerializeContextTools", "%s", scratchBuffer.c_str());
scratchBuffer.clear();
return result;
}
AZ::JsonSerializationResult::ResultCode Converter::SimpleLogging(AZStd::string& scratchBuffer, AZStd::string_view message,
AZ::JsonSerializationResult::ResultCode result, AZStd::string_view path)
{
using namespace JsonSerializationResult;
if (result.GetProcessing() != Processing::Completed)
{
scratchBuffer.append(message.begin(), message.end());
scratchBuffer.append(" @ ");
scratchBuffer.append(path.begin(), path.end());
scratchBuffer.append(".\n");
AZ_Printf("SerializeContextTools", "%s", scratchBuffer.c_str());
scratchBuffer.clear();
}
return result;
}
} // namespace SerializeContextTools
} // namespace AZ
@@ -0,0 +1,71 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/utils.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
class CommandLine;
class Entity;
class ModuleEntity;
class SerializeContext;
struct Uuid;
namespace SerializeContextTools
{
class Application;
class Converter
{
public:
static bool ConvertObjectStreamFiles(Application& application);
static bool ConvertApplicationDescriptor(Application& application);
//! Converts Windows INI Style File
//! Can be used to convert *.ini and *.cfg files
static bool ConvertConfigFile(Application& application);
private:
using PathDocumentPair = AZStd::pair<AZStd::string, rapidjson::Document>;
using PathDocumentContainer = AZStd::vector<PathDocumentPair>;
static bool ConvertSystemSettings(PathDocumentContainer& documents, const ComponentApplication::Descriptor& descriptor,
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder, const AZStd::string& applicationRoot);
static bool ConvertSystemComponents(PathDocumentContainer& documents, const Entity& entity,
const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder,
const JsonSerializerSettings& convertSettings, const JsonDeserializerSettings& verifySettings);
static bool ConvertModuleComponents(PathDocumentContainer& documents, const ModuleEntity& entity, const AZStd::string& configurationName,
const JsonSerializerSettings& convertSettings, const JsonDeserializerSettings& verifySettings);
static bool VerifyConvertedData(rapidjson::Value& convertedData, const void* original, const Uuid& originalType,
const JsonDeserializerSettings& settings);
static AZStd::string GetClassName(const Uuid& classId, SerializeContext* context);
static bool WriteDocumentToDisk(const AZStd::string& filename, const rapidjson::Document& document, AZStd::string_view documentRoot,
rapidjson::StringBuffer& scratchBuffer);
static void SetupLogging(AZStd::string& scratchBuffer, JsonSerializationResult::JsonIssueCallback& callback,
const AzFramework::CommandLine& commandLine);
static JsonSerializationResult::ResultCode VerboseLogging(AZStd::string& scratchBuffer, AZStd::string_view message,
JsonSerializationResult::ResultCode result, AZStd::string_view target);
static AZ::JsonSerializationResult::ResultCode SimpleLogging(AZStd::string& scratchBuffer, AZStd::string_view message,
JsonSerializationResult::ResultCode result, AZStd::string_view target);
};
} // namespace SerializeContextTools
} // namespace AZ
+584
View File
@@ -0,0 +1,584 @@
/*
* 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 <Dumper.h> // Moved to the top because AssetSerializer requires include for the SerializeContext
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/sort.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <Application.h>
#include <Utilities.h>
namespace AZ::SerializeContextTools
{
bool Dumper::DumpFiles(Application& application)
{
SerializeContext* sc = application.GetSerializeContext();
if (!sc)
{
AZ_Error("SerializeContextTools", false, "No serialize context found.");
return false;
}
AZStd::string outputFolder = Utilities::ReadOutputTargetFromCommandLine(application);
AZ::IO::Path sourceGameFolder;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder);
}
bool result = true;
AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
for (const AZStd::string& filePath : fileList)
{
AZ_Printf("DumpFiles", "Dumping file '%.*s'\n", aznumeric_cast<int>(filePath.size()), filePath.data());
AZ::IO::FixedMaxPath outputPath{ AZStd::string_view{ outputFolder }};
outputPath /= AZ::IO::FixedMaxPath(filePath).LexicallyRelative(sourceGameFolder);
outputPath.Native() += ".dump.txt";
IO::SystemFile outputFile;
if (!outputFile.Open(outputPath.c_str(),
IO::SystemFile::OpenMode::SF_OPEN_CREATE |
IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
{
AZ_Error("SerializeContextTools", false, "Unable to open file '%s' for writing.", outputPath.c_str());
result = false;
continue;
}
AZStd::string content;
content.reserve(1 * 1024 * 1024); // Reserve 1mb to avoid frequently resizing the string.
auto callback = [&content, &result](void* classPtr, const Uuid& classId, SerializeContext* context)
{
result = DumpClassContent(content, classPtr, classId, context) && result;
const SerializeContext::ClassData* classData = context->FindClassData(classId);
if (classData && classData->m_factory)
{
classData->m_factory->Destroy(classPtr);
}
else
{
AZ_Error("SerializeContextTools", false, "Missing class factory, so data will leak.");
result = false;
}
};
if (!Utilities::InspectSerializedFile(filePath, sc, callback))
{
result = false;
continue;
}
outputFile.Write(content.data(), content.length());
}
return result;
}
bool Dumper::DumpSerializeContext(Application& application)
{
AZStd::string outputPath = Utilities::ReadOutputTargetFromCommandLine(application, "SerializeContext.json");
AZ_Printf("dumpsc", "Writing Serialize Context at '%s'.\n", outputPath.c_str());
IO::SystemFile outputFile;
if (!outputFile.Open(outputPath.c_str(),
IO::SystemFile::OpenMode::SF_OPEN_CREATE |
IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
{
AZ_Error("SerializeContextTools", false, "Unable to open output file '%s'.", outputPath.c_str());
return false;
}
SerializeContext* context = application.GetSerializeContext();
AZStd::vector<Uuid> systemComponents = Utilities::GetSystemComponents(application);
AZStd::sort(systemComponents.begin(), systemComponents.end());
rapidjson::Document doc;
rapidjson::Value& root = doc.SetObject();
rapidjson::Value scObject;
scObject.SetObject();
AZStd::string temp;
temp.reserve(256 * 1024); // Reserve 256kb of memory to avoid the string constantly resizing.
bool result = true;
auto callback = [context, &doc, &scObject, &temp, &systemComponents, &result](const SerializeContext::ClassData* classData, const Uuid& /*typeId*/) -> bool
{
if (!DumpClassContent(classData, scObject, doc, systemComponents, context, temp))
{
result = false;
}
return true;
};
context->EnumerateAll(callback, true);
root.AddMember("SerializeContext", AZStd::move(scObject), doc.GetAllocator());
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
doc.Accept(writer);
outputFile.Write(buffer.GetString(), buffer.GetSize());
outputFile.Close();
return result;
}
AZStd::vector<Uuid> Dumper::CreateFilterListByNames(SerializeContext* context, AZStd::string_view name)
{
AZStd::vector<AZStd::string_view> names;
auto AppendNames = [&names](AZStd::string_view filename)
{
names.emplace_back(filename);
};
AZ::StringFunc::TokenizeVisitor(name, AppendNames, ';');
AZStd::vector<Uuid> filterIds;
filterIds.reserve(names.size());
for (const AZStd::string_view& singleName : names)
{
AZStd::vector<Uuid> foundFilters = context->FindClassId(Crc32(singleName.data(), singleName.length(), true));
filterIds.insert(filterIds.end(), foundFilters.begin(), foundFilters.end());
}
return filterIds;
}
AZStd::string_view Dumper::ExtractNamespace(const AZStd::string& name)
{
size_t offset = 0;
const char* startChar = name.data();
const char* currentChar = name.data();
while (*currentChar != 0 && *currentChar != '<')
{
if (*currentChar != ':')
{
++currentChar;
}
else
{
++currentChar;
if (*currentChar == ':')
{
AZ_Assert(currentChar - startChar >= 1, "Offset out of bounds while trying to extract namespace from name '%s'.", name.c_str());
offset = currentChar - startChar - 1; // -1 to exclude the last "::"
}
}
}
return AZStd::string_view(startChar, offset);
}
rapidjson::Value Dumper::WriteToJsonValue(const Uuid& uuid, rapidjson::Document& document)
{
char buffer[Uuid::MaxStringBuffer];
int writtenCount = uuid.ToString(buffer, AZ_ARRAY_SIZE(buffer));
if (writtenCount > 0)
{
return rapidjson::Value(buffer, writtenCount - 1, document.GetAllocator()); //-1 as the null character shouldn't be written.
}
else
{
return rapidjson::Value(rapidjson::StringRef("{uuid conversion failed}"));
}
}
bool Dumper::DumpClassContent(const SerializeContext::ClassData* classData, rapidjson::Value& parent, rapidjson::Document& document,
const AZStd::vector<Uuid>& systemComponents, SerializeContext* context, AZStd::string& scratchStringBuffer)
{
AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer wasn't empty.");
rapidjson::Value classNode(rapidjson::kObjectType);
DumpClassName(classNode, context, classData, document, scratchStringBuffer);
Edit::ClassData* editData = classData->m_editData;
GenericClassInfo* genericClassInfo = context->FindGenericClassInfo(classData->m_typeId);
if (editData && editData->m_description)
{
AZStd::string_view description = editData->m_description;
// Skipping if there's only one character as there are several cases where a blank description is given.
if (description.size() > 1)
{
classNode.AddMember("Description", rapidjson::Value(description.data(), document.GetAllocator()), document.GetAllocator());
}
}
classNode.AddMember("Id", rapidjson::StringRef(classData->m_name), document.GetAllocator());
classNode.AddMember("Version", classData->IsDeprecated() ?
rapidjson::Value(rapidjson::StringRef("Deprecated")) : rapidjson::Value(classData->m_version), document.GetAllocator());
auto systemComponentIt = AZStd::lower_bound(systemComponents.begin(), systemComponents.end(), classData->m_typeId);
bool isSystemComponent = systemComponentIt != systemComponents.end() && *systemComponentIt == classData->m_typeId;
classNode.AddMember("IsSystemComponent", isSystemComponent, document.GetAllocator());
classNode.AddMember("IsPrimitive", Utilities::IsSerializationPrimitive(genericClassInfo ? genericClassInfo->GetGenericTypeId() : classData->m_typeId), document.GetAllocator());
classNode.AddMember("IsContainer", classData->m_container != nullptr, document.GetAllocator());
if (genericClassInfo)
{
classNode.AddMember("GenericUuid", WriteToJsonValue(genericClassInfo->GetGenericTypeId(), document), document.GetAllocator());
classNode.AddMember("Generics", DumpGenericStructure(genericClassInfo, context, document, scratchStringBuffer), document.GetAllocator());
}
if (!classData->m_elements.empty())
{
rapidjson::Value fields(rapidjson::kArrayType);
rapidjson::Value bases(rapidjson::kArrayType);
for (const SerializeContext::ClassElement& element : classData->m_elements)
{
DumpElementInfo(element, classData, context, fields, bases, document, scratchStringBuffer);
}
if (!bases.Empty())
{
classNode.AddMember("Bases", AZStd::move(bases), document.GetAllocator());
}
if (!fields.Empty())
{
classNode.AddMember("Fields", AZStd::move(fields), document.GetAllocator());
}
}
parent.AddMember(WriteToJsonValue(classData->m_typeId, document), AZStd::move(classNode), document.GetAllocator());
return true;
}
bool Dumper::DumpClassContent(AZStd::string& output, void* classPtr, const Uuid& classId, SerializeContext* context)
{
const SerializeContext::ClassData* classData = context->FindClassData(classId);
if (!classData)
{
AZ_Printf("", " Class data for '%s' is missing.\n", classId.ToString<AZStd::string>().c_str());
return false;
}
size_t indention = 0;
auto begin = [context, &output, &indention](void* /*instance*/, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement) -> bool
{
for (size_t i = 0; i < indention; ++i)
{
output += ' ';
}
if (classData)
{
output += classData->m_name;
}
DumpElementInfo(output, classElement, context);
DumpPrimitiveTag(output, classData, classElement);
output += '\n';
indention += 2;
return true;
};
auto end = [&indention]() -> bool
{
indention = indention > 0 ? indention - 2 : 0;
return true;
};
SerializeContext::EnumerateInstanceCallContext callContext(begin, end, context, SerializeContext::ENUM_ACCESS_FOR_WRITE, nullptr);
context->EnumerateInstance(&callContext, classPtr, classId, classData, nullptr);
return true;
}
void Dumper::DumpElementInfo(const SerializeContext::ClassElement& element, const SerializeContext::ClassData* classData, SerializeContext* context,
rapidjson::Value& fields, rapidjson::Value& bases, rapidjson::Document& document, AZStd::string& scratchStringBuffer)
{
AZ_Assert(fields.IsArray(), "Expected 'fields' to be an array.");
AZ_Assert(bases.IsArray(), "Expected 'bases' to be an array.");
AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer wasn't empty.");
const SerializeContext::ClassData* elementClass = context->FindClassData(element.m_typeId, classData);
AppendTypeName(scratchStringBuffer, elementClass, element.m_typeId);
Uuid elementTypeId = element.m_typeId;
if (element.m_genericClassInfo)
{
DumpGenericStructure(scratchStringBuffer, element.m_genericClassInfo, context);
elementTypeId = element.m_genericClassInfo->GetSpecializedTypeId();
}
if ((element.m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
{
scratchStringBuffer += '*';
}
rapidjson::Value elementTypeString(scratchStringBuffer.c_str(), document.GetAllocator());
scratchStringBuffer.clear();
if ((element.m_flags & SerializeContext::ClassElement::FLG_BASE_CLASS) != 0)
{
rapidjson::Value baseNode(rapidjson::kObjectType);
baseNode.AddMember("Type", AZStd::move(elementTypeString), document.GetAllocator());
baseNode.AddMember("Uuid", WriteToJsonValue(elementTypeId, document), document.GetAllocator());
bases.PushBack(AZStd::move(baseNode), document.GetAllocator());
}
else
{
rapidjson::Value elementNode(rapidjson::kObjectType);
elementNode.AddMember("Name", rapidjson::StringRef(element.m_name), document.GetAllocator());
elementNode.AddMember("Type", AZStd::move(elementTypeString), document.GetAllocator());
elementNode.AddMember("Uuid", WriteToJsonValue(elementTypeId, document), document.GetAllocator());
elementNode.AddMember("HasDefault", (element.m_flags & SerializeContext::ClassElement::FLG_NO_DEFAULT_VALUE) == 0, document.GetAllocator());
elementNode.AddMember("IsDynamic", (element.m_flags & SerializeContext::ClassElement::FLG_DYNAMIC_FIELD) != 0, document.GetAllocator());
elementNode.AddMember("IsPointer", (element.m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0, document.GetAllocator());
elementNode.AddMember("IsUiElement", (element.m_flags & SerializeContext::ClassElement::FLG_UI_ELEMENT) != 0, document.GetAllocator());
elementNode.AddMember("DataSize", static_cast<uint64_t>(element.m_dataSize), document.GetAllocator());
elementNode.AddMember("Offset", static_cast<uint64_t>(element.m_offset), document.GetAllocator());
Edit::ElementData* elementEditData = element.m_editData;
if (elementEditData)
{
elementNode.AddMember("Description", rapidjson::StringRef(elementEditData->m_description), document.GetAllocator());
}
if (element.m_genericClassInfo)
{
rapidjson::Value genericArray(rapidjson::kArrayType);
rapidjson::Value classObject(rapidjson::kObjectType);
const SerializeContext::ClassData* genericClassData = element.m_genericClassInfo->GetClassData();
classObject.AddMember("Type", rapidjson::StringRef(genericClassData->m_name), document.GetAllocator());
classObject.AddMember("GenericUuid", WriteToJsonValue(element.m_genericClassInfo->GetGenericTypeId(), document), document.GetAllocator());
classObject.AddMember("SpecializedUuid", WriteToJsonValue(element.m_genericClassInfo->GetSpecializedTypeId(), document), document.GetAllocator());
classObject.AddMember("Generics", DumpGenericStructure(element.m_genericClassInfo, context, document, scratchStringBuffer), document.GetAllocator());
genericArray.PushBack(AZStd::move(classObject), document.GetAllocator());
elementNode.AddMember("Generics", AZStd::move(genericArray), document.GetAllocator());
}
fields.PushBack(AZStd::move(elementNode), document.GetAllocator());
}
}
void Dumper::DumpElementInfo(AZStd::string& output, const SerializeContext::ClassElement* classElement, SerializeContext* context)
{
if (classElement)
{
if (classElement->m_genericClassInfo)
{
DumpGenericStructure(output, classElement->m_genericClassInfo, context);
}
if ((classElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
{
output += '*';
}
output += ' ';
output += classElement->m_name;
if ((classElement->m_flags & SerializeContext::ClassElement::FLG_BASE_CLASS) != 0)
{
output += " [Base]";
}
}
}
void Dumper::DumpGenericStructure(AZStd::string& output, GenericClassInfo* genericClassInfo, SerializeContext* context)
{
output += '<';
const SerializeContext::ClassData* classData = genericClassInfo->GetClassData();
if (classData && classData->m_container)
{
bool firstArgument = true;
auto callback = [&output, context, &firstArgument](const Uuid& elementClassId, const SerializeContext::ClassElement* genericClassElement) -> bool
{
if (!firstArgument)
{
output += ',';
}
else
{
firstArgument = false;
}
const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
AppendTypeName(output, argClassData, elementClassId);
if (genericClassElement->m_genericClassInfo)
{
DumpGenericStructure(output, genericClassElement->m_genericClassInfo, context);
}
if ((genericClassElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0)
{
output += '*';
}
return true;
};
classData->m_container->EnumTypes(callback);
}
else
{
// No container information available, so as much as possible through other means, although
// this might not be complete information.
size_t numArgs = genericClassInfo->GetNumTemplatedArguments();
for (size_t i = 0; i < numArgs; ++i)
{
if (i != 0)
{
output += ',';
}
const Uuid& argClassId = genericClassInfo->GetTemplatedTypeId(i);
const SerializeContext::ClassData* argClass = context->FindClassData(argClassId);
AppendTypeName(output, argClass, argClassId);
}
}
output += '>';
}
rapidjson::Value Dumper::DumpGenericStructure(GenericClassInfo* genericClassInfo, SerializeContext* context,
rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer)
{
AZ_Assert(scratchStringBuffer.empty(), "Provided scratch string buffer still contains data.");
rapidjson::Value result(rapidjson::kArrayType);
const SerializeContext::ClassData* classData = genericClassInfo->GetClassData();
if (classData && classData->m_container)
{
auto callback = [&result, context, &parentDoc, &scratchStringBuffer](const Uuid& elementClassId,
const SerializeContext::ClassElement* genericClassElement) -> bool
{
rapidjson::Value classObject(rapidjson::kObjectType);
const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
AppendTypeName(scratchStringBuffer, argClassData, elementClassId);
classObject.AddMember("Type", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
scratchStringBuffer.clear();
classObject.AddMember("IsPointer", (genericClassElement->m_flags & SerializeContext::ClassElement::FLG_POINTER) != 0, parentDoc.GetAllocator());
if (genericClassElement->m_genericClassInfo)
{
GenericClassInfo* genericClassInfo = genericClassElement->m_genericClassInfo;
classObject.AddMember("GenericUuid", WriteToJsonValue(genericClassInfo->GetGenericTypeId(), parentDoc), parentDoc.GetAllocator());
classObject.AddMember("SpecializedUuid", WriteToJsonValue(genericClassInfo->GetSpecializedTypeId(), parentDoc), parentDoc.GetAllocator());
classObject.AddMember("Generics", DumpGenericStructure(genericClassInfo, context, parentDoc, scratchStringBuffer), parentDoc.GetAllocator());
}
else
{
classObject.AddMember("GenericUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
classObject.AddMember("SpecializedUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
}
result.PushBack(AZStd::move(classObject), parentDoc.GetAllocator());
return true;
};
classData->m_container->EnumTypes(callback);
}
else
{
// No container information available, so as much as possible through other means, although
// this might not be complete information.
size_t numArgs = genericClassInfo->GetNumTemplatedArguments();
for (size_t i = 0; i < numArgs; ++i)
{
const Uuid& elementClassId = genericClassInfo->GetTemplatedTypeId(i);
rapidjson::Value classObject(rapidjson::kObjectType);
const SerializeContext::ClassData* argClassData = context->FindClassData(elementClassId);
AppendTypeName(scratchStringBuffer, argClassData, elementClassId);
classObject.AddMember("Type", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
scratchStringBuffer.clear();
classObject.AddMember("GenericUuid",
WriteToJsonValue(argClassData ? argClassData->m_typeId : elementClassId, parentDoc), parentDoc.GetAllocator());
classObject.AddMember("SpecializedUuid", WriteToJsonValue(elementClassId, parentDoc), parentDoc.GetAllocator());
classObject.AddMember("IsPointer", false, parentDoc.GetAllocator());
result.PushBack(AZStd::move(classObject), parentDoc.GetAllocator());
}
}
return result;
}
void Dumper::DumpPrimitiveTag(AZStd::string& output, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement)
{
if (classData)
{
Uuid classId = classData->m_typeId;
if (classElement && classElement->m_genericClassInfo)
{
classId = classElement->m_genericClassInfo->GetGenericTypeId();
}
if (Utilities::IsSerializationPrimitive(classId))
{
output += " [Primitive]";
}
}
}
void Dumper::DumpClassName(rapidjson::Value& parent, SerializeContext* context, const SerializeContext::ClassData* classData,
rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer)
{
AZ_Assert(scratchStringBuffer.empty(), "Scratch string buffer is not empty.");
Edit::ClassData* editData = classData->m_editData;
GenericClassInfo* genericClassInfo = context->FindGenericClassInfo(classData->m_typeId);
if (genericClassInfo)
{
// If the type itself is a generic, dump it's information.
scratchStringBuffer = classData->m_name;
DumpGenericStructure(scratchStringBuffer, genericClassInfo, context);
}
else
{
bool hasEditName = editData && editData->m_name && strlen(editData->m_name) > 0;
scratchStringBuffer = hasEditName ? editData->m_name : classData->m_name;
}
AZStd::string_view namespacePortion = ExtractNamespace(scratchStringBuffer);
if (!namespacePortion.empty())
{
parent.AddMember("Namespace",
rapidjson::Value(namespacePortion.data(), azlossy_caster(namespacePortion.length()), parentDoc.GetAllocator()),
parentDoc.GetAllocator());
parent.AddMember("Name", rapidjson::Value(scratchStringBuffer.c_str() + namespacePortion.length() + 2, parentDoc.GetAllocator()), parentDoc.GetAllocator());
}
else
{
parent.AddMember("Name", rapidjson::Value(scratchStringBuffer.c_str(), parentDoc.GetAllocator()), parentDoc.GetAllocator());
}
scratchStringBuffer.clear();
}
void Dumper::AppendTypeName(AZStd::string& output, const SerializeContext::ClassData* classData, const Uuid& classId)
{
if (classData)
{
output += classData->m_name;
}
else if (classId == GetAssetClassId())
{
output += "Asset";
}
else
{
output += classId.ToString<AZStd::string>();
}
}
// namespace AZ::SerializeContextTools
}
+61
View File
@@ -0,0 +1,61 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/JSON/document.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
namespace AZ
{
namespace SerializeContextTools
{
class Application;
class Dumper
{
public:
static bool DumpFiles(Application& application);
static bool DumpSerializeContext(Application& application);
private:
static AZStd::vector<Uuid> CreateFilterListByNames(SerializeContext* context, AZStd::string_view name);
static AZStd::string_view ExtractNamespace(const AZStd::string& name);
static rapidjson::Value WriteToJsonValue(const Uuid& uuid, rapidjson::Document& document);
static bool DumpClassContent(const SerializeContext::ClassData* classData, rapidjson::Value& parent, rapidjson::Document& document,
const AZStd::vector<Uuid>& systemComponents, SerializeContext* context, AZStd::string& scratchStringBuffer);
static bool DumpClassContent(AZStd::string& output, void* classPtr, const Uuid& classId, SerializeContext* context);
static void DumpElementInfo(const SerializeContext::ClassElement& element, const SerializeContext::ClassData* classData, SerializeContext* context,
rapidjson::Value& fields, rapidjson::Value& bases, rapidjson::Document& document, AZStd::string& scratchStringBuffer);
static void DumpElementInfo(AZStd::string& output, const SerializeContext::ClassElement* classElement, SerializeContext* context);
static void DumpGenericStructure(AZStd::string& output, GenericClassInfo* genericClassInfo, SerializeContext* context);
static rapidjson::Value DumpGenericStructure(GenericClassInfo* genericClassInfo, SerializeContext* context,
rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer);
static void DumpPrimitiveTag(AZStd::string& output, const SerializeContext::ClassData* classData,
const SerializeContext::ClassElement* classElement);
static void DumpClassName(rapidjson::Value& parent, SerializeContext* context, const SerializeContext::ClassData* classData,
rapidjson::Document& parentDoc, AZStd::string& scratchStringBuffer);
static void AppendTypeName(AZStd::string& output, const SerializeContext::ClassData* classData, const Uuid& classId);
};
} // namespace SerializeContextTools
} // namespace AZ
@@ -0,0 +1,12 @@
#
# 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.
#
set(PAL_TRAIT_BUILD_SERIALIZECONTEXTTOOLS FALSE)
@@ -0,0 +1,12 @@
#
# 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.
#
set(PAL_TRAIT_BUILD_SERIALIZECONTEXTTOOLS TRUE)
@@ -0,0 +1,12 @@
#
# 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.
#
set(PAL_TRAIT_BUILD_SERIALIZECONTEXTTOOLS TRUE)
@@ -0,0 +1,250 @@
/*
* 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/Serialization/SerializeContext.h> // Needs to be on top due to missing include in AssetSerializer.h
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Module/Module.h>
#include <AzCore/Module/ModuleManagerBus.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Settings/CommandLine.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/wildcard.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <Application.h>
#include <Utilities.h>
namespace AZ::SerializeContextTools
{
AZStd::string Utilities::ReadOutputTargetFromCommandLine(Application& application, const char* defaultFileOrFolder)
{
AZ::IO::Path sourceGameFolder;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder);
}
AZ::IO::Path outputPath;
if (application.GetCommandLine()->HasSwitch("output"))
{
outputPath.Native() = application.GetCommandLine()->GetSwitchValue("output", 0);
if (outputPath.IsRelative())
{
outputPath = sourceGameFolder / outputPath;
}
}
else
{
outputPath = sourceGameFolder / defaultFileOrFolder;
}
return outputPath.Native();
}
AZStd::vector<AZStd::string> Utilities::ReadFileListFromCommandLine(Application& application, AZStd::string_view switchName)
{
AZStd::vector<AZStd::string> result;
const AZ::CommandLine* commandLine = application.GetCommandLine();
if (!commandLine)
{
AZ_Error("SerializeContextTools", false, "Command line not available.");
return result;
}
if (!commandLine->HasSwitch(switchName))
{
AZ_Error("SerializeContextTools", false, "Missing command line argument '-%*s' which should contain the requested files.",
aznumeric_cast<int>(switchName.size()), switchName.data());
return result;
}
AZ::IO::Path sourceGameFolder;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(sourceGameFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder);
}
AZStd::vector<AZStd::string_view> fileList;
auto AppendFileList = [&fileList](AZStd::string_view filename)
{
fileList.emplace_back(filename);
};
for (size_t switchIndex{}; switchIndex < commandLine->GetNumSwitchValues(switchName); ++switchIndex)
{
AZ::StringFunc::TokenizeVisitor(commandLine->GetSwitchValue(switchName, switchIndex), AppendFileList, ";");
}
return Utilities::ExpandFileList(sourceGameFolder.c_str(), fileList);
}
AZStd::vector<AZStd::string> Utilities::ExpandFileList(const char* root, const AZStd::vector<AZStd::string_view>& fileList)
{
AZStd::vector<AZStd::string> result;
result.reserve(fileList.size());
for (const AZStd::string_view& file : fileList)
{
if (HasWildCard(file))
{
AZ::IO::FixedMaxPath filterPath{ file };
AZ::IO::FixedMaxPath parentPath{ filterPath.ParentPath() };
if (filterPath.IsRelative())
{
parentPath = AZ::IO::FixedMaxPath(root) / parentPath;
}
AZ::IO::PathView filterFilename = filterPath.Filename();
if (filterFilename.empty())
{
AZ_Error("SerializeContextTools", false, "Unable to get folder path for '%.*s'.",
aznumeric_cast<int>(filterFilename.Native().size()), filterFilename.Native().data());
continue;
}
AZStd::queue<AZ::IO::FixedMaxPath> pendingFolders;
pendingFolders.push(AZStd::move(parentPath));
while (!pendingFolders.empty())
{
const AZ::IO::FixedMaxPath& filterFolder = pendingFolders.front();
auto callback = [&pendingFolders, &filterFolder, &filterFilename, &result](AZ::IO::PathView item, bool isFile) -> bool
{
if (item == "." || item == "..")
{
return true;
}
AZ::IO::FixedMaxPath fullPath = filterFolder / item;
if (isFile)
{
if (AZStd::wildcard_match(filterFilename.Native(), item.Native()))
{
result.emplace_back(fullPath.c_str(), fullPath.Native().size());
}
}
else
{
pendingFolders.push(AZStd::move(fullPath));
}
return true;
};
AZ::IO::SystemFile::FindFiles((filterFolder / "*").c_str(), callback);
pendingFolders.pop();
}
}
else
{
AZ::IO::FixedMaxPath filePath{ file };
if (filePath.IsRelative())
{
filePath = AZ::IO::FixedMaxPath(root) / filePath;
}
result.emplace_back(filePath.c_str(), filePath.Native().size());
}
}
return result;
}
bool Utilities::HasWildCard(AZStd::string_view string)
{
// Wild cards vary between platforms, but these are the most common ones.
return string.find_first_of("*?[]!@#", 0) != AZStd::string_view::npos;
}
void Utilities::SanitizeFilePath(AZStd::string& filePath)
{
auto invalidCharacters = [](char letter)
{
return
letter == ':' || letter == '"' || letter == '\'' ||
letter == '{' || letter == '}' ||
letter == '<' || letter == '>';
};
AZStd::replace_if(filePath.begin(), filePath.end(), invalidCharacters, '_');
}
bool Utilities::IsSerializationPrimitive(const AZ::Uuid& classId)
{
JsonRegistrationContext* registrationContext;
AZ::ComponentApplicationBus::BroadcastResult(registrationContext, &AZ::ComponentApplicationBus::Events::GetJsonRegistrationContext);
if (!registrationContext)
{
AZ_Error("SerializeContextTools", false, "Failed to retrieve json registration context.");
return false;
}
return registrationContext->GetSerializerForType(classId) != nullptr;
}
AZStd::vector<AZ::Uuid> Utilities::GetSystemComponents(const Application& application)
{
AZStd::vector<AZ::Uuid> result = application.GetRequiredSystemComponents();
auto getModuleSystemComponentsCB = [&result](const ModuleData& moduleData) -> bool
{
if (AZ::Module* module = moduleData.GetModule())
{
AZ::ComponentTypeList moduleRequiredComponents = module->GetRequiredSystemComponents();
result.reserve(result.size() + moduleRequiredComponents.size());
result.insert(result.end(), moduleRequiredComponents.begin(), moduleRequiredComponents.end());
}
return true;
};
ModuleManagerRequestBus::Broadcast(&ModuleManagerRequests::EnumerateModules, getModuleSystemComponentsCB);
return result;
}
bool Utilities::InspectSerializedFile(const AZStd::string& filePath, SerializeContext* sc, const ObjectStream::ClassReadyCB& classCallback)
{
if (!AZ::IO::SystemFile::Exists(filePath.c_str()))
{
AZ_Error("Verify", false, "Unable to open file '%s' as it doesn't exist.", filePath.c_str());
return false;
}
u64 fileLength = AZ::IO::SystemFile::Length(filePath.c_str());
if (fileLength == 0)
{
AZ_Error("Verify", false, "File '%s' doesn't have content.", filePath.c_str());
return false;
}
AZStd::vector<u8> data;
data.resize_no_construct(fileLength);
u64 bytesRead = AZ::IO::SystemFile::Read(filePath.c_str(), data.data());
if (bytesRead != fileLength)
{
AZ_Error("Verify", false, "Unable to read file '%s'.", filePath.c_str());
return false;
}
AZ::IO::MemoryStream stream(data.data(), fileLength);
ObjectStream::FilterDescriptor filter;
filter.m_flags = ObjectStream::FILTERFLAG_IGNORE_UNKNOWN_CLASSES;
// Never load dependencies. That's another file that would need to be processed
// separately from this one.
filter.m_assetCB = AZ::Data::AssetFilterNoAssetLoading;
if (!ObjectStream::LoadBlocking(&stream, *sc, classCallback, filter))
{
AZ_Printf("Verify", "Failed to deserialize '%s'\n", filePath.c_str());
return false;
}
return true;
}
} // namespace AZ::SerializeContextTools
@@ -0,0 +1,53 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Uuid.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/string_view.h>
namespace AZ
{
class SerializeContext;
namespace SerializeContextTools
{
class Application;
class Utilities final
{
public:
static AZStd::string ReadOutputTargetFromCommandLine(Application& application, const char* defaultFileOrFolder = "");
static AZStd::vector<AZStd::string> ReadFileListFromCommandLine(Application& application, AZStd::string_view switchName);
static AZStd::vector<AZStd::string> ExpandFileList(const char* root, const AZStd::vector<AZStd::string_view>& fileList);
static bool HasWildCard(AZStd::string_view string);
static void SanitizeFilePath(AZStd::string& filePath);
static bool IsSerializationPrimitive(const AZ::Uuid& classId);
static AZStd::vector<AZ::Uuid> GetSystemComponents(const Application& application);
static bool InspectSerializedFile(const AZStd::string& filePath, SerializeContext* sc, const ObjectStream::ClassReadyCB& classCallback);
private:
Utilities() = delete;
~Utilities() = delete;
Utilities(const Utilities&) = delete;
Utilities(Utilities&&) = delete;
Utilities& operator=(const Utilities&) = delete;
Utilities& operator=(Utilities&&) = delete;
};
} // namespace SerializeContextTools
} // namespace AZ
+134
View File
@@ -0,0 +1,134 @@
/*
* 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/Debug/Trace.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <Application.h>
#include <Converter.h>
#include <Dumper.h>
void PrintHelp()
{
AZ_Printf("Help", "Serialize Context Tool\n");
AZ_Printf("Help", " <action> [-config] <action arguments>*\n");
AZ_Printf("Help", " [opt] -config=<path>: optional path to application's config file. Default is 'config/editor.xml'.\n");
AZ_Printf("Help", "\n");
AZ_Printf("Help", " 'help': Print this help\n");
AZ_Printf("Help", " example: 'help'\n");
AZ_Printf("Help", "\n");
AZ_Printf("Help", " 'dumpfiles': Dump the content to a .dump.txt file next to the original file.\n");
AZ_Printf("Help", " [arg] -files=<path>: ;-separated list of files to verify. Supports wildcards.\n");
AZ_Printf("Help", " [opt] -output=<path>: Path to the folder to write to instead of next to the original file.\n");
AZ_Printf("Help", " example: 'dumpfiles -files=folder/*.ext;a.ext;folder/another/z.ext'\n");
AZ_Printf("Help", "\n");
AZ_Printf("Help", " 'dumpsc': Dump the content of the Serialize and Edit Context to a JSON file.\n");
AZ_Printf("Help", " [opt] -output=<path>: Path to the folder to write to instead of next to the original file.\n");
AZ_Printf("Help", " example: 'dumpsc -output=../TargetFolder/SerializeContext.json\n");
AZ_Printf("Help", "\n");
AZ_Printf("Help", " 'convert': Converts a file with an ObjectStream to the new JSON formats.\n");
AZ_Printf("Help", " [arg] -files=<path>: <comma or semicolon>-separated list of files to verify. Supports wildcards.\n");
AZ_Printf("Help", " [arg] -ext=<string>: Extension to use for the new file.\n");
AZ_Printf("Help", " [opt] -dryrun: Processes as normal, but doesn't write files.\n");
AZ_Printf("Help", " [opt] -skipverify: After conversion the result will not be compared to the original.\n");
AZ_Printf("Help", " [opt] -keepdefaults: Fields are written if a default value was found.\n");
AZ_Printf("Help", " [opt] -json-prefix=<prefix>: JSON pointer path prefix to anchor the JSON output underneath.\n");
AZ_Printf("Help", " On Windows the <prefix> should be in quotes, as \"/\" is treated as command option prefix\n");
AZ_Printf("Help", " [opt] -json-prefix=prefix: Json pointer path prefix to use as a \"root\" for settings.\n");
AZ_Printf("Help", " [opt] -verbose: Report additional details during the conversion process.\n");
AZ_Printf("Help", " example: 'convert -file=*.slice;*.uislice -ext=slice2\n");
AZ_Printf("Help", "\n");
AZ_Printf("Help", " 'convertad': Converts an Application Descriptor to the new JSON formats.\n");
AZ_Printf("Help", " [opt] -dryrun: Processes as normal, but doesn't write files.\n");
AZ_Printf("Help", " [opt] -skipgems: No module entities will be converted and no data will be written to gems.\n");
AZ_Printf("Help", " [opt] -skipsystem: No system information is converted and no data will be written to the game registry.\n");
AZ_Printf("Help", " [opt] -keepdefaults: Fields are written if a default value was found.\n");
AZ_Printf("Help", " [opt] -json-prefix=<prefix>: JSON pointer path prefix to anchor the JSON output underneath.\n");
AZ_Printf("Help", " On Windows the <prefix> should be in quotes, as \"/\" is treated as command option prefix\n");
AZ_Printf("Help", " [opt] -verbose: Report additional details during the conversion process.\n");
AZ_Printf("Help", " [opt] -regset <setreg_key>=<setreg_value>: Set setreg_value at key setreg_key within the settings registry.\n");
AZ_Printf("Help", " This can be used for example to override the Active Game Project in the settings registry.\n");
AZ_Printf("Help", " instead of using the sys_game_folder value from the bootstrap.cfg.\n");
AZ_Printf("Help", R"( Ex. -regset "/Amazon/AzCore/Bootstrap/sys_game_folder=AutomatedTesting"\n)");
AZ_Printf("Help", " This sets the active game project as AutomatedTesting, overrideing the value in the bootstrap.cfg\n");
AZ_Printf("Help", " example: 'convertad -config=config/game.xml -dryrun\n");
AZ_Printf("Help", R"( 'convert-ini': Converts windows-style INI file to a json format file.)" "\n");
AZ_Printf("Help", R"( The converted file is suitable for being loaded into the Settings Registry.)" "\n");
AZ_Printf("Help", R"( Can be used to convert .cfg/.ini files.)" "\n");
AZ_Printf("Help", R"( [arg] -files=<path...>: <comma or semicolon>-separated list of files to verify. Supports wildcards.)" "\n");
AZ_Printf("Help", R"( [opt] -ext=<string>: Extension to use for the new files. default=setreg)" "\n");
AZ_Printf("Help", R"( [opt] -dryrun: Processes as normal, but doesn't write files.)" "\n");
AZ_Printf("Help", R"( [opt] -json-prefix=<prefix>: JSON pointer path prefix to anchor the JSON output underneath.)" "\n");
AZ_Printf("Help", R"( On Windows the <prefix> should be in quotes, as \"/\" is treated as command option prefix)" "\n");
AZ_Printf("Help", R"( [opt] -verbose: Report additional details during the conversion process.)" "\n");
AZ_Printf("Help", R"( example: 'convertconfig --files=AssetProcessorPlatformConfigIni;bootstrap.cfg --ext=setreg)" "\n");
}
int main(int argc, char** argv)
{
using namespace AZ::SerializeContextTools;
bool result = false;
Application application(&argc, &argv);
AZ::ComponentApplication::StartupParameters startupParameters;
startupParameters.m_loadDynamicModules = false;
application.Start({}, startupParameters);
// Load the DynamicModules after the Application starts to prevent Gem System Components
// from activating
application.LoadDynamicModules();
const AZ::CommandLine* commandLine = application.GetCommandLine();
if (commandLine->GetNumMiscValues() < 1)
{
PrintHelp();
result = true;
}
else
{
const AZStd::string& action = commandLine->GetMiscValue(0);
if (AzFramework::StringFunc::Equal("dumpfiles", action.c_str()))
{
result = Dumper::DumpFiles(application);
}
else if (AzFramework::StringFunc::Equal("dumpsc", action.c_str()))
{
result = Dumper::DumpSerializeContext(application);
}
else if (AzFramework::StringFunc::Equal("convert", action.c_str()))
{
result = Converter::ConvertObjectStreamFiles(application);
}
else if (AzFramework::StringFunc::Equal("convertad", action.c_str()))
{
result = Converter::ConvertApplicationDescriptor(application);
}
else if (AzFramework::StringFunc::Equal("convert-ini", action.c_str()))
{
result = Converter::ConvertConfigFile(application);
}
else
{
PrintHelp();
result = true;
}
}
if (!result)
{
AZ_Printf("SerializeContextTools", "Processing didn't complete fully as problems were encountered.\n");
}
application.Stop();
return result ? 0 : -1;
}
@@ -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.
#
set(FILES
Application.h
Application.cpp
Converter.h
Converter.cpp
Dumper.h
Dumper.cpp
main.cpp
Utilities.h
Utilities.cpp
)