Merge branch 'main' into LoadPipelineFromGitHub

This commit is contained in:
Brian Herrera
2021-03-26 17:05:57 -07:00
765 changed files with 11513 additions and 20246 deletions
@@ -35,7 +35,7 @@ subprojects {
sdkVer = ${SDK_VER}
ndkPlatformVer = ${NDK_PLATFORM_VER}
buildToolsVer = '${SDK_BUILD_TOOL_VER}'
lyDevRoot = '${LY_DEV_ROOT}'
lyEngineRoot = '${LY_ENGINE_ROOT}'
}
}
@@ -15,7 +15,6 @@
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Jobs/Algorithms.h>
#include <AzCore/Jobs/JobManagerComponent.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Module/DynamicModuleHandle.h>
@@ -23,6 +22,7 @@
#include <AzCore/Slice/SliceSystemComponent.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/Driller/RemoteDrillerInterface.h>
@@ -38,18 +38,12 @@
#include <AzToolsFramework/Asset/AssetUtils.h>
#include <AzToolsFramework/AssetBundle/AssetBundleComponent.h>
#include <AzToolsFramework/AssetCatalog/PlatformAddressedAssetCatalogBus.h>
#include <AzCore/Jobs/Algorithms.h>
namespace AssetBundler
{
const char compareVariablePrefix = '$';
GemInfo::GemInfo(AZStd::string name, AZStd::string relativeFilePath, AZStd::string absoluteFilePath)
: m_gemName(name)
, m_relativeFilePath(relativeFilePath)
, m_absoluteFilePath(absoluteFilePath)
{
}
ApplicationManager::ApplicationManager(int* argc, char*** argv)
: AzToolsFramework::ToolsApplication(argc, argv)
{
@@ -123,41 +117,21 @@ namespace AssetBundler
}
m_showVerboseOutput = ShouldPrintVerbose(parser);
ComputeEngineRoot();
m_currentProjectName = AZStd::string_view{ AZ::Utils::GetProjectName() };
const char* devRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(devRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
if (devRoot)
if (m_currentProjectName.empty())
{
AZ_TracePrintf(AssetBundler::AppWindowNameVerbose, "Setting devroot alias to ( %s ).\n", devRoot);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@devroot@", devRoot);
}
AZStd::string platformName(AzToolsFramework::AssetSystem::GetHostAssetPlatform());
AZStd::string assetsAlias;
AZStd::string assetCatalogFile;
AZ::Outcome<void, AZStd::string> result = AssetBundler::ComputeAssetAliasAndGameName(platformName, assetCatalogFile, assetsAlias, m_currentProjectName);
if (!result.IsSuccess())
{
AZ_Error(AppWindowName, false, result.GetError().c_str());
AZ_Error(AppWindowName, false, "Unable to retrieve project name from the Settings Registry");
return false;
}
const char* appRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot);
// Gems
if (!AzToolsFramework::AssetUtils::GetGemsInfo(g_cachedEngineRoot, appRoot, m_currentProjectName.c_str(), m_gemInfoList))
if (!AzFramework::GetGemsInfo(m_gemInfoList, *m_settingsRegistry))
{
AZ_Error(AppWindowName, false, "Failed to read Gems for project: %s\n", m_currentProjectName.c_str());
return false;
}
// @assets@ alias
AZ_TracePrintf(AssetBundler::AppWindowNameVerbose, "Setting asset alias to ( %s ).\n", assetsAlias.c_str());
AZ::IO::FileIOBase::GetInstance()->SetAlias("@assets@", assetsAlias.c_str());
m_platformCatalogManager = AZStd::make_unique<AzToolsFramework::PlatformAddressedAssetCatalogManager>();
InitArgValidationLists();
@@ -1304,13 +1278,18 @@ namespace AssetBundler
AZ::Outcome<void, AZStd::string> ApplicationManager::ValidateInputArgs(const AzFramework::CommandLine* parser, const AZStd::vector<const char*>& validArgList)
{
for (const auto& paramInfo : parser->GetSwitchList())
for (const auto& paramInfo : *parser)
{
// Skip positional arguments
if (paramInfo.m_option.empty())
{
continue;
}
bool isValidArg = false;
for (const auto& validArg : validArgList)
{
if (AzFramework::StringFunc::Equal(paramInfo.first.c_str(), validArg))
if (AzFramework::StringFunc::Equal(paramInfo.m_option, validArg))
{
isValidArg = true;
break;
@@ -1319,7 +1298,7 @@ namespace AssetBundler
if (!isValidArg)
{
return AZ::Failure(AZStd::string::format("Invalid command: \"--%s\" is not a valid argument for this sub-command.", paramInfo.first.c_str()));
return AZ::Failure(AZStd::string::format("Invalid command: \"--%s\" is not a valid argument for this sub-command.", paramInfo.m_option.c_str()));
}
}
@@ -1407,7 +1386,7 @@ namespace AssetBundler
const char* appRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot);
AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags(g_cachedEngineRoot, appRoot, m_currentProjectName.c_str());
AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags(GetEngineRoot(), appRoot, AZ::Utils::GetProjectPath().c_str());
auto platformsString = AzFramework::PlatformHelper::GetCommaSeparatedPlatformList(platformFlags);
AZ_TracePrintf(AppWindowName, "No platform specified, defaulting to platforms ( %s ).\n", platformsString.c_str());
@@ -1573,7 +1552,8 @@ namespace AssetBundler
// Add Default Seed List Files
if (params.m_addDefaultSeedListFiles)
{
AZStd::unordered_map<AZStd::string, AZStd::string> defaultSeedListFiles = GetDefaultSeedListFiles(AssetBundler::g_cachedEngineRoot, m_currentProjectName.c_str(), m_gemInfoList, params.m_platformFlags);
AZStd::unordered_map<AZStd::string, AZStd::string> defaultSeedListFiles = GetDefaultSeedListFiles(GetEngineRoot(), AZ::Utils::GetProjectPath(),
m_gemInfoList, params.m_platformFlags);
if (defaultSeedListFiles.empty())
{
// Error has already been thrown
@@ -1590,7 +1570,7 @@ namespace AssetBundler
}
}
AZStd::vector<AZStd::string> defaultSeeds = GetDefaultSeeds(AssetBundler::g_cachedEngineRoot, m_currentProjectName.c_str());
AZStd::vector<AZStd::string> defaultSeeds = GetDefaultSeeds(GetEngineRoot(), AZ::Utils::GetProjectPath(), m_currentProjectName);
if (defaultSeeds.empty())
{
// Error has already been thrown
@@ -1830,9 +1810,9 @@ namespace AssetBundler
bool hasError = false;
for (const AZStd::string_view& platformName : AzFramework::PlatformHelper::GetPlatformsInterpreted(paramsOutcome.GetValue().m_platformFlags))
for (AZStd::string platformName : AzFramework::PlatformHelper::GetPlatformsInterpreted(paramsOutcome.GetValue().m_platformFlags))
{
AZ_TracePrintf(AssetBundler::AppWindowName, "Running Compare command for the %.*s platform...\n", aznumeric_cast<int>(platformName.size()), platformName.data());
AZ_TracePrintf(AssetBundler::AppWindowName, "Running Compare command for the %s platform...\n", platformName.c_str());
ComparisonParams params = paramsOutcome.GetValue();
AddPlatformToAllComparisonParams(params, platformName);
@@ -2687,7 +2667,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " \"path\" - This refers to an Engine-Root-Relative path.\n");
AZ_Printf(AppWindowName, " - Example: \"C:\\Lumberyard\\dev\\SamplesProject\\test.txt\" can be represented as \"SamplesProject\\test.txt\".\n");
AZ_Printf(AppWindowName, " \"cache path\" - This refers to a Cache-Relative path.\n");
AZ_Printf(AppWindowName, " - Example: \"C:\\Lumberyard\\dev\\Cache\\SamplesProject\\pc\\samplesproject\\animations\\skeletonlist.xml\" is represented as \"animations\\skeletonlist.xml\".\n");
AZ_Printf(AppWindowName, " - Example: \"C:\\Lumberyard\\dev\\SamplesProject\\Cache\\pc\\animations\\skeletonlist.xml\" is represented as \"animations\\skeletonlist.xml\".\n");
AZ_Printf(AppWindowName, "\n");
OutputHelpSeeds();
@@ -2718,7 +2698,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, "%-31s---Takes in a cache path to a pre-processed asset.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Removes the asset from the list of root assets for the specified platform.\n", RemoveSeedArg);
AZ_Printf(AppWindowName, "%-31s---To completely remove the asset, it must be removed for all platforms.\n", "");
AZ_Printf(AppWindowName, "%-31s---Takes in a cache path to a pre-processed asset. A cache path is a path relative to \"...\\dev\\Cache\\ProjectName\\platform\\projectname\\\"\n", "");
AZ_Printf(AppWindowName, "%-31s---Takes in a cache path to a pre-processed asset. A cache path is a path relative to \"ProjectPath\\Cache\\platform\\\"\n", "");
AZ_Printf(AppWindowName, " --%-25s-Adds the specified platform to every Seed in the Seed List file, if possible.\n", AddPlatformToAllSeedsFlag);
AZ_Printf(AppWindowName, " --%-25s-Removes the specified platform from every Seed in the Seed List file, if possible.\n", RemovePlatformFromAllSeedsFlag);
AZ_Printf(AppWindowName, " --%-25s-Outputs the contents of the Seed List file after performing any specified operations.\n", PrintFlag);
@@ -2730,7 +2710,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " --%-25s-Allows input file path to still match if the file path case is different than on disk.\n", IgnoreFileCaseFlag);
AZ_Printf(AppWindowName, " --%-25s-[Testing] Specifies the Asset Catalog file referenced by all Seed operations.\n", AssetCatalogFileArg);
AZ_Printf(AppWindowName, "%-31s---Designed to be used in Unit Tests.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootsrap.cfg's sys_game_folder.\n", ProjectArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootstrap.cfg's project_path.\n", ProjectArg);
}
void ApplicationManager::OutputHelpAssetLists()
@@ -2740,7 +2720,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " --%-25s-Specifies the Asset List file to operate on by path. Must include (.%s) file extension.\n", AssetListFileArg, AssetSeedManager::GetAssetListFileExtension());
AZ_Printf(AppWindowName, " --%-25s-Specifies the Seed List file(s) that will be used as root(s) when generating this Asset List file.\n", SeedListFileArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the Seed(s) to use as root(s) when generating this Asset List File.\n", AddSeedArg);
AZ_Printf(AppWindowName, "%-31s---Takes in a cache path to a pre-processed asset. A cache path is a path relative to \"...\\dev\\Cache\\ProjectName\\platform\\projectname\\\"\n", "");
AZ_Printf(AppWindowName, "%-31s---Takes in a cache path to a pre-processed asset. A cache path is a path relative to \"ProjectPath\\Cache\\platform\\\"\n", "");
AZ_Printf(AppWindowName, " --%-25s-The specified files and all dependencies will be ignored when generating the Asset List file.\n", SkipArg);
AZ_Printf(AppWindowName, "%-31s---Takes in a comma-separated list of either: cache paths to pre-processed assets, or wildcard patterns.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Automatically include all default Seed List files in generated Asset List File.\n", AddDefaultSeedListFilesFlag);
@@ -2754,7 +2734,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " --%-25s-Run all input commands, without saving to the specified Asset List file.\n", DryRunFlag);
AZ_Printf(AppWindowName, " --%-25s-Generates a human-readable file that maps every entry in the Asset List file to the Seed that generated it.\n", GenerateDebugFileFlag);
AZ_Printf(AppWindowName, " --%-25s-Allow destructive overwrites of files. Include this arg in automation.\n", AllowOverwritesFlag);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootsrap.cfg's sys_game_folder.\n", ProjectArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootstrap.cfg's project_path.\n", ProjectArg);
}
void ApplicationManager::OutputHelpComparisonRules()
@@ -2781,7 +2761,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " --%-25s-The Token name of the Comparison Step you wish to use as the second input of this Comparison Step.\n", ComparisonSecondInputArg);
AZ_Printf(AppWindowName, "%-31s---Comparison Steps of the ( FilePattern ) type only accept one input Token, and cannot be used with this arg.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Outputs the contents of the Comparison Rules file after performing any specified operations.\n", PrintFlag);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootsrap.cfg's sys_game_folder.\n", ProjectArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootstrap.cfg's project_path.\n", ProjectArg);
}
void ApplicationManager::OutputHelpCompare()
@@ -2813,7 +2793,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, "%-31s---All input Asset List files must exist for all specified platforms\n", "");
AZ_Printf(AppWindowName, "%-31s---Defaults to all enabled platforms. Platforms can be changed by modifying AssetProcessorPlatformConfig.ini.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Allow destructive overwrites of files. Include this arg in automation.\n", AllowOverwritesFlag);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootsrap.cfg's sys_game_folder.\n", ProjectArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootstrap.cfg's project_path.\n", ProjectArg);
}
void ApplicationManager::OutputHelpBundleSettings()
@@ -2829,7 +2809,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " --%-25s-Specifies the platform(s) referenced by all Bundle Settings operations.\n", PlatformArg);
AZ_Printf(AppWindowName, "%-31s---Defaults to all enabled platforms. Platforms can be changed by modifying AssetProcessorPlatformConfig.ini.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Outputs the contents of the Bundle Settings file after modifying any specified values.\n", PrintFlag);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootsrap.cfg's sys_game_folder.\n", ProjectArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootstrap.cfg's project_path.\n", ProjectArg);
}
void ApplicationManager::OutputHelpBundles()
@@ -2846,7 +2826,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " --%-25s-Specifies the platform(s) that will be referenced when generating Bundles.\n", PlatformArg);
AZ_Printf(AppWindowName, "%-31s---If no platforms are specified, Bundles will be generated for all available platforms.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Allow destructive overwrites of files. Include this arg in automation.\n", AllowOverwritesFlag);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootsrap.cfg's sys_game_folder.\n", ProjectArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootstrap.cfg's project_path.\n", ProjectArg);
}
void ApplicationManager::OutputHelpBundleSeed()
@@ -2854,7 +2834,7 @@ namespace AssetBundler
using namespace AzToolsFramework;
AZ_Printf(AppWindowName, "\n%-25s-Subcommand for generating bundles directly from seeds. Must provide either (--%s) or (--%s).\n", BundleSeedCommand, BundleSettingsFileArg, OutputBundlePathArg);
AZ_Printf(AppWindowName, " --%-25s-Adds the asset to the list of root assets for the specified platform.\n", AddSeedArg);
AZ_Printf(AppWindowName, "%-31s---Takes in a cache path to a pre-processed asset. A cache path is a path relative to \"...\\dev\\Cache\\ProjectName\\platform\\projectname\\\"\n", "");
AZ_Printf(AppWindowName, "%-31s---Takes in a cache path to a pre-processed asset. A cache path is a path relative to \"ProjectPath\\Cache\\platform\\\"\n", "");
AZ_Printf(AppWindowName, " --%-25s-Specifies the Bundle Settings file to operate on by path. Must include (.%s) file extension.\n", BundleSettingsFileArg, AssetBundleSettings::GetBundleSettingsFileExtension());
AZ_Printf(AppWindowName, " --%-25s-Sets the path where generated Bundles will be stored. Must include (.%s) file extension.\n", OutputBundlePathArg, AssetBundleSettings::GetBundleFileExtension());
AZ_Printf(AppWindowName, " --%-25s-Determines which version of Lumberyard Bundles to generate. Current version is (%i).\n", BundleVersionArg, AzFramework::AssetBundleManifest::CurrentBundleVersion);
@@ -2865,7 +2845,7 @@ namespace AssetBundler
AZ_Printf(AppWindowName, " --%-25s-Allow destructive overwrites of files. Include this arg in automation.\n", AllowOverwritesFlag);
AZ_Printf(AppWindowName, " --%-25s-[Testing] Specifies the Asset Catalog file referenced by all Bundle operations.\n", AssetCatalogFileArg);
AZ_Printf(AppWindowName, "%-31s---Designed to be used in Unit Tests.\n", "");
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootsrap.cfg's sys_game_folder.\n", ProjectArg);
AZ_Printf(AppWindowName, " --%-25s-Specifies the game project to use rather than the current default project set in bootstrap.cfg's project_path.\n", ProjectArg);
}
////////////////////////////////////////////////////////////////////////////////////////////
@@ -185,7 +185,7 @@ namespace AssetBundler
////////////////////////////////////////////////////////////////////////////////////////////
AZStd::string GetCurrentProjectName() { return m_currentProjectName; }
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> GetGemInfoList() { return m_gemInfoList; }
AZStd::vector<AzFramework::GemInfo> GetGemInfoList() { return m_gemInfoList; }
protected:
////////////////////////////////////////////////////////////////////////////////////////////
@@ -288,7 +288,7 @@ namespace AssetBundler
AZStd::unique_ptr<AzToolsFramework::AssetSeedManager> m_assetSeedManager;
AZStd::unique_ptr<AzToolsFramework::PlatformAddressedAssetCatalogManager> m_platformCatalogManager;
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> m_gemInfoList;
AZStd::vector<AzFramework::GemInfo> m_gemInfoList;
bool m_showVerboseOutput = false;
AZStd::string m_currentProjectName;
+136 -267
View File
@@ -24,6 +24,7 @@
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/string/regex.h>
#include <AzCore/Utils/Utils.h>
#include <cctype>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option")
@@ -57,8 +58,8 @@ namespace AssetBundler
const char* RemovePlatformFromAllSeedsFlag = "removePlatformFromSeeds";
const char* UpdateSeedPathArg = "updateSeedPath";
const char* RemoveSeedPathArg = "removeSeedPath";
const char* DefaultProjectTemplatePath = "ProjectTemplates/DefaultTemplate/${ProjectName}";
const char* ProjectName = "${ProjectName}";
const char* DefaultProjectTemplatePath = "Templates/DefaultProject/Template";
const char* ProjectName = "${Name}";
const char* DependenciesFileSuffix = "_Dependencies";
const char* DependenciesFileExtension = "xml";
@@ -107,8 +108,6 @@ namespace AssetBundler
const char* AssetCatalogFilename = "assetcatalog.xml";
char g_cachedEngineRoot[AZ_MAX_PATH_LEN];
const char EngineDirectoryName[] = "Engine";
const char RestrictedDirectoryName[] = "restricted";
@@ -124,16 +123,15 @@ namespace AssetBundler
const AZ::u32 PlatformFlags_RESTRICTED = aznumeric_cast<AZ::u32>(AzFramework::PlatformFlags::Platform_JASPER | AzFramework::PlatformFlags::Platform_PROVO | AzFramework::PlatformFlags::Platform_SALEM);
void AddPlatformSeeds(
AZStd::string rootFolder,
const AZ::IO::Path& engineDirectory,
const AZStd::string& rootFolderDisplayName,
AZStd::unordered_map<AZStd::string, AZStd::string>& defaultSeedLists,
AzFramework::PlatformFlags platformFlags)
{
AZ::IO::FixedMaxPath engineRoot(GetEngineRoot());
AZ::IO::FixedMaxPath engineRestrcitedRoot = engineRoot / RestrictedDirectoryName;
AZ::IO::FixedMaxPath engineRestrictedRoot = engineRoot / RestrictedDirectoryName;
AZ::IO::FixedMaxPath inputPath = AZ::IO::FixedMaxPath(rootFolder);
AZ::IO::FixedMaxPath engineLocalPath = inputPath.LexicallyRelative(engineRoot);
AZ::IO::FixedMaxPath engineLocalPath = AZ::IO::PathView(engineDirectory.LexicallyRelative(engineRoot));
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
auto platformsIdxList = AzFramework::PlatformHelper::GetPlatformIndicesInterpreted(platformFlags);
@@ -146,11 +144,11 @@ namespace AssetBundler
AZ::IO::FixedMaxPath platformDirectory;
if (aznumeric_cast<AZ::u32>(platformFlag) & PlatformFlags_RESTRICTED)
{
platformDirectory = engineRestrcitedRoot / platformDirName / engineLocalPath;
platformDirectory = engineRestrictedRoot / platformDirName / engineLocalPath;
}
else
{
platformDirectory = inputPath / PlatformsDirectoryName / platformDirName;
platformDirectory = engineDirectory / PlatformsDirectoryName / platformDirName;
}
if (fileIO->Exists(platformDirectory.c_str()))
@@ -174,7 +172,7 @@ namespace AssetBundler
}
void AddPlatformsDirectorySeeds(
const AZStd::string& rootFolder,
const AZ::IO::Path& engineDirectory,
const AZStd::string& rootFolderDisplayName,
AZStd::unordered_map<AZStd::string, AZStd::string>& defaultSeedLists,
AzFramework::PlatformFlags platformFlags)
@@ -185,8 +183,7 @@ namespace AssetBundler
// Check whether platforms directory exists inside the root, if yes than add
// * All seed files from the platforms directory
// * All platform specific seed files based on the platform flags specified.
AZStd::string platformsDirectory;
AzFramework::StringFunc::Path::Join(rootFolder.c_str(), PlatformsDirectoryName, platformsDirectory);
auto platformsDirectory = engineDirectory / PlatformsDirectoryName;
if (fileIO->Exists(platformsDirectory.c_str()))
{
fileIO->FindFiles(platformsDirectory.c_str(),
@@ -200,119 +197,19 @@ namespace AssetBundler
});
}
AddPlatformSeeds(rootFolder, rootFolderDisplayName, defaultSeedLists, platformFlags);
AddPlatformSeeds(engineDirectory, rootFolderDisplayName, defaultSeedLists, platformFlags);
}
}
bool ComputeEngineRoot()
AZ::IO::FixedMaxPath GetEngineRoot()
{
if (g_cachedEngineRoot[0])
AZ::IO::FixedMaxPath engineRootPath;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
return true;
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
const char* engineRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
if (!engineRoot)
{
AZ_Error(AssetBundler::AppWindowName, false, "Unable to locate engine root.\n");
return false;
}
azstrcpy(g_cachedEngineRoot, AZ_MAX_PATH_LEN, engineRoot);
return true;
}
const char* GetEngineRoot()
{
if (!g_cachedEngineRoot[0])
{
ComputeEngineRoot();
}
return g_cachedEngineRoot;
}
AZ::Outcome<void, AZStd::string> ComputeAssetAliasAndGameName(const AZStd::string& platformIdentifier, const AZStd::string& assetCatalogFile, AZStd::string& assetAlias, AZStd::string& gameName)
{
AZStd::string assetPath;
AZStd::string gameFolder;
if (!ComputeEngineRoot())
{
return AZ::Failure(AZStd::string("Unable to compute engine root.\n"));
}
if (assetCatalogFile.empty())
{
if (gameName.empty())
{
bool checkPlatform = false;
bool result{};
auto gameFolderKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::ProjectName);
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
result = settingsRegistry->Get(gameFolder, gameFolderKey);
}
if (!result)
{
return AZ::Failure(AZStd::string("Unable to locate game name in bootstrap.\n"));
}
gameName = gameFolder;
}
else
{
gameFolder = gameName;
}
// Appending Cache/%gamename%/%platform%/%gameName% to the engine root
bool success = AzFramework::StringFunc::Path::ConstructFull(g_cachedEngineRoot, "Cache", assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), gameFolder.c_str(), assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), platformIdentifier.c_str(), assetPath);
if (success)
{
AZStd::to_lower(gameFolder.begin(), gameFolder.end());
success = AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), gameFolder.c_str(), assetPath); // game name is lowercase
}
if (success)
{
assetAlias = assetPath;
}
}
else if (AzFramework::StringFunc::Path::GetFullPath(assetCatalogFile.c_str(), assetPath))
{
AzFramework::StringFunc::Strip(assetPath, AZ_CORRECT_FILESYSTEM_SEPARATOR, false, false, true);
assetAlias = assetPath;
// 3rd component from reverse should give us the correct case game name because the assetalias directory
// looks like ./Cache/%GameName%/%platform%/%gameName%/assetcatalog.xml
// GetComponent util method returns the component with the separator appended at the end
// therefore we need to strip the separator to get the game name string
gameFolder = AZ::IO::PathView(assetPath).ParentPath().ParentPath().Filename().Native();
if (gameFolder.empty())
{
return AZ::Failure(AZStd::string::format("Unable to retrieve game name from assetCatalog file (%s).\n", assetCatalogFile.c_str()));
}
if (!AzFramework::StringFunc::Strip(gameFolder, AZ_CORRECT_FILESYSTEM_SEPARATOR, false, false, true))
{
return AZ::Failure(AZStd::string::format("Unable to strip separator from game name (%s).\n", gameFolder.c_str()));
}
if (!gameName.empty() && !AzFramework::StringFunc::Equal(gameFolder.c_str(), gameName.c_str()))
{
return AZ::Failure(AZStd::string::format("Game name retrieved from the assetCatalog file (%s) does not match the inputted game name (%s).\n", gameFolder.c_str(), gameName.c_str()));
}
else
{
gameName = gameFolder;
}
}
return AZ::Success();
return engineRootPath;
}
void AddPlatformIdentifier(AZStd::string& filePath, const AZStd::string& platformIdentifier)
@@ -346,7 +243,8 @@ namespace AssetBundler
return platformFlags;
}
AZStd::unordered_map<AZStd::string, AZStd::string> GetDefaultSeedListFiles(const char* root, const char* projectName, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlag)
AZStd::unordered_map<AZStd::string, AZStd::string> GetDefaultSeedListFiles(AZStd::string_view enginePath, AZStd::string_view projectPath,
const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlag)
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO, "AZ::IO::FileIOBase must be ready for use.\n");
@@ -354,63 +252,65 @@ namespace AssetBundler
// Add all seed list files of enabled gems for the given project
AZStd::unordered_map<AZStd::string, AZStd::string> defaultSeedLists = GetGemSeedListFilePathToGemNameMap(gemInfoList, platformFlag);
// Add the engine seed list file
AZStd::string engineDirectory;
AzFramework::StringFunc::Path::Join(root, EngineDirectoryName, engineDirectory);
AZStd::string absoluteEngineSeedFilePath;
AzFramework::StringFunc::Path::ConstructFull(engineDirectory.c_str(), EngineSeedFileName, AzToolsFramework::AssetSeedManager::GetSeedFileExtension(), absoluteEngineSeedFilePath, true);
AZ::IO::Path engineDirectory = AZ::IO::Path(enginePath) / EngineDirectoryName;
auto absoluteEngineSeedFilePath = engineDirectory / EngineSeedFileName;
absoluteEngineSeedFilePath.ReplaceExtension(AzToolsFramework::AssetSeedManager::GetSeedFileExtension());
if (fileIO->Exists(absoluteEngineSeedFilePath.c_str()))
{
defaultSeedLists[absoluteEngineSeedFilePath] = EngineDirectoryName;
defaultSeedLists[absoluteEngineSeedFilePath.Native()] = EngineDirectoryName;
}
// Add Seed Lists from the Platforms directory
Internal::AddPlatformsDirectorySeeds(engineDirectory, EngineDirectoryName, defaultSeedLists, platformFlag);
AZStd::string absoluteProjectDefaultSeedFilePath;
AzFramework::StringFunc::Path::ConstructFull(root, projectName, EngineSeedFileName, AzToolsFramework::AssetSeedManager::GetSeedFileExtension(), absoluteProjectDefaultSeedFilePath, true);
auto absoluteProjectDefaultSeedFilePath = AZ::IO::Path(projectPath) / EngineSeedFileName;
absoluteProjectDefaultSeedFilePath.ReplaceExtension(AzToolsFramework::AssetSeedManager::GetSeedFileExtension());
if (fileIO->Exists(absoluteProjectDefaultSeedFilePath.c_str()))
{
defaultSeedLists[absoluteProjectDefaultSeedFilePath] = projectName;
defaultSeedLists[absoluteProjectDefaultSeedFilePath.Native()] = projectPath;
}
return defaultSeedLists;
}
AZStd::vector<AZStd::string> GetDefaultSeeds(const char* root, const char* projectName)
AZStd::vector<AZStd::string> GetDefaultSeeds(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName)
{
AZStd::vector<AZStd::string> defaultSeeds;
defaultSeeds.emplace_back(GetProjectDependenciesAssetPath(root, projectName));
defaultSeeds.emplace_back(GetProjectDependenciesAssetPath(enginePath, projectPath, projectName));
return defaultSeeds;
}
AZStd::string GetProjectDependenciesFile(const char* root, const char* projectName)
AZ::IO::Path GetProjectDependenciesFile(AZStd::string_view projectPath, AZStd::string_view projectName)
{
AZStd::string projectDependenciesFilePath = AZStd::string::format("%s%s", projectName, DependenciesFileSuffix);
AzFramework::StringFunc::Path::ConstructFull(root, projectName, projectDependenciesFilePath.c_str(), DependenciesFileExtension, projectDependenciesFilePath, true);
return projectDependenciesFilePath;
AZ::IO::Path projectDependenciesFilePath = projectPath;
projectDependenciesFilePath /= AZStd::string::format("%.*s%s", aznumeric_cast<int>(projectName.size()), projectName.data(),
DependenciesFileSuffix);
projectDependenciesFilePath.ReplaceExtension(DependenciesFileExtension);
return projectDependenciesFilePath.LexicallyNormal();
}
AZStd::string GetProjectDependenciesFileTemplate(const char* root)
AZ::IO::Path GetProjectDependenciesFileTemplate(AZStd::string_view engineRoot)
{
AZStd::string projectDependenciesFileTemplate = ProjectName;
projectDependenciesFileTemplate += DependenciesFileSuffix;
AzFramework::StringFunc::Path::ConstructFull(root, DefaultProjectTemplatePath, projectDependenciesFileTemplate.c_str(), DependenciesFileExtension, projectDependenciesFileTemplate, true);
return projectDependenciesFileTemplate;
AZ::IO::Path projectDependenciesFileTemplate = engineRoot;
projectDependenciesFileTemplate /= DefaultProjectTemplatePath;
projectDependenciesFileTemplate /= AZStd::string::format("%s%s", ProjectName, DependenciesFileSuffix);
projectDependenciesFileTemplate.ReplaceExtension(DependenciesFileExtension);
return projectDependenciesFileTemplate.LexicallyNormal();
}
AZStd::string GetProjectDependenciesAssetPath(const char* root, const char* projectName)
AZ::IO::Path GetProjectDependenciesAssetPath(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName)
{
AZStd::string projectDependenciesFile = AZStd::move(GetProjectDependenciesFile(root, projectName));
AZ::IO::Path projectDependenciesFile = GetProjectDependenciesFile(projectPath, projectName);
if (!AZ::IO::FileIOBase::GetInstance()->Exists(projectDependenciesFile.c_str()))
{
AZ_TracePrintf(AssetBundler::AppWindowName, "Project dependencies file %s doesn't exist.\n", projectDependenciesFile.c_str());
AZStd::string projectDependenciesFileTemplate = AZStd::move(GetProjectDependenciesFileTemplate(root));
AZ::IO::Path projectDependenciesFileTemplate = GetProjectDependenciesFileTemplate(enginePath);
if (AZ::IO::FileIOBase::GetInstance()->Copy(projectDependenciesFileTemplate.c_str(), projectDependenciesFile.c_str()))
{
AZ_TracePrintf(AssetBundler::AppWindowName, "Copied project dependencies file template %s to the current project.\n",
@@ -425,37 +325,39 @@ namespace AssetBundler
}
// Turn the absolute path into a cache-relative path
AZStd::string relativeProductPath;
AzFramework::StringFunc::Path::GetFullFileName(projectDependenciesFile.c_str(), relativeProductPath);
AZStd::to_lower(relativeProductPath.begin(), relativeProductPath.end());
AZ::IO::Path relativeProductPath = projectDependenciesFile.Filename().Native();
AZStd::to_lower(relativeProductPath.Native().begin(), relativeProductPath.Native().end());
return relativeProductPath;
}
AZStd::unordered_map<AZStd::string, AZStd::string> GetGemSeedListFilePathToGemNameMap(const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags)
AZStd::unordered_map<AZStd::string, AZStd::string> GetGemSeedListFilePathToGemNameMap(const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags)
{
AZStd::unordered_map<AZStd::string, AZStd::string> filePathToGemNameMap;
for (const AzToolsFramework::AssetUtils::GemInfo& gemInfo : gemInfoList)
for (const AzFramework::GemInfo& gemInfo : gemInfoList)
{
AZ::IO::Path gemInfoAssetFilePath = gemInfo.m_absoluteFilePath;
gemInfoAssetFilePath /= gemInfo.GetGemAssetFolder();
AZ::IO::Path absoluteGemSeedFilePath = gemInfoAssetFilePath / GemsSeedFileName;
absoluteGemSeedFilePath.ReplaceExtension(AZ::IO::PathView{ AzToolsFramework::AssetSeedManager::GetSeedFileExtension() });
absoluteGemSeedFilePath = absoluteGemSeedFilePath.LexicallyNormal();
AZStd::string gemName = gemInfo.m_gemName + " Gem";
if (AZ::IO::FileIOBase::GetInstance()->Exists(absoluteGemSeedFilePath.c_str()))
for (const AZ::IO::Path& gemAbsoluteSourcePath : gemInfo.m_absoluteSourcePaths)
{
filePathToGemNameMap[absoluteGemSeedFilePath.Native()] = gemName;
}
AZ::IO::Path gemInfoAssetFilePath = gemAbsoluteSourcePath;
gemInfoAssetFilePath /= gemInfo.GetGemAssetFolder();
AZ::IO::Path absoluteGemSeedFilePath = gemInfoAssetFilePath / GemsSeedFileName;
absoluteGemSeedFilePath.ReplaceExtension(AZ::IO::PathView{ AzToolsFramework::AssetSeedManager::GetSeedFileExtension() });
absoluteGemSeedFilePath = absoluteGemSeedFilePath.LexicallyNormal();
Internal::AddPlatformsDirectorySeeds(gemInfoAssetFilePath.Native(), gemName, filePathToGemNameMap, platformFlags);
AZStd::string gemName = gemInfo.m_gemName + " Gem";
if (AZ::IO::FileIOBase::GetInstance()->Exists(absoluteGemSeedFilePath.c_str()))
{
filePathToGemNameMap[absoluteGemSeedFilePath.Native()] = gemName;
}
Internal::AddPlatformsDirectorySeeds(gemInfoAssetFilePath.Native(), gemName, filePathToGemNameMap, platformFlags);
}
}
return filePathToGemNameMap;
}
bool IsGemSeedFilePathValid(const char* root, AZStd::string seedAbsoluteFilePath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags)
bool IsGemSeedFilePathValid(AZStd::string_view engineRoot, AZStd::string seedAbsoluteFilePath, const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags)
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO, "AZ::IO::FileIOBase must be ready for use.\n");
@@ -465,9 +367,8 @@ namespace AssetBundler
return false;
}
AZ::IO::Path gemsFolder{ root };
AZ::IO::Path gemsFolder{ engineRoot };
gemsFolder /= GemsDirectoryName;
gemsFolder /= GemsAssetsDirectoryName;
gemsFolder = gemsFolder.LexicallyNormal();
if (!AzFramework::StringFunc::StartsWith(seedAbsoluteFilePath, gemsFolder.Native()))
{
@@ -476,36 +377,45 @@ namespace AssetBundler
return true;
}
for (const AzToolsFramework::AssetUtils::GemInfo& gemInfo : gemInfoList)
for (const AzFramework::GemInfo& gemInfo : gemInfoList)
{
// We want to check the path before going through the effort of creating the default Seed List file map
if (!AzFramework::StringFunc::StartsWith(seedAbsoluteFilePath, gemInfo.m_absoluteFilePath))
for (const AZ::IO::Path& gemAbsoluteSourcePath : gemInfo.m_absoluteSourcePaths)
{
continue;
}
// We want to check the path before going through the effort of creating the default Seed List file map
if (!AzFramework::StringFunc::StartsWith(seedAbsoluteFilePath, gemAbsoluteSourcePath.Native()))
{
continue;
}
AZStd::unordered_map<AZStd::string, AZStd::string> seeds = GetGemSeedListFilePathToGemNameMap({gemInfo}, platformFlags);
AZStd::unordered_map<AZStd::string, AZStd::string> seeds = GetGemSeedListFilePathToGemNameMap({ gemInfo }, platformFlags);
if (seeds.find(seedAbsoluteFilePath) != seeds.end())
{
return true;
if (seeds.find(seedAbsoluteFilePath) != seeds.end())
{
return true;
}
// If we have not validated the input path yet, we need to keep looking, or we will return false negatives
// for Gems that have the same prefix in their name
}
// If we have not validated the input path yet, we need to keep looking, or we will return false negatives
// for Gems that have the same prefix in their name
}
return false;
}
AzFramework::PlatformFlags GetEnabledPlatformFlags(const char* root, const char* assetRoot, const char* gameName)
AzFramework::PlatformFlags GetEnabledPlatformFlags(AZStd::string_view engineRoot, AZStd::string_view assetRoot, AZStd::string_view projectPath)
{
QStringList configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(root, assetRoot, gameName, true, true);
QStringList enabaledPlatformList = AzToolsFramework::AssetUtils::GetEnabledPlatforms(configFiles);
AzFramework::PlatformFlags platformFlags = AzFramework::PlatformFlags::Platform_NONE;
for (const QString& enabledPlatform : enabaledPlatformList)
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry == nullptr)
{
AzFramework::PlatformFlags platformFlag = AzFramework::PlatformHelper::GetPlatformFlag(enabledPlatform.toUtf8().data());
AZ_Error(AssetBundler::AppWindowName, false, "Settings Registry is not available, enabled platform flags cannot be queried");
return AzFramework::PlatformFlags::Platform_NONE;
}
auto configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(engineRoot, assetRoot, projectPath, true, true, settingsRegistry);
auto enabledPlatformList = AzToolsFramework::AssetUtils::GetEnabledPlatforms(*settingsRegistry, configFiles);
AzFramework::PlatformFlags platformFlags = AzFramework::PlatformFlags::Platform_NONE;
for (const auto& enabledPlatform : enabledPlatformList)
{
AzFramework::PlatformFlags platformFlag = AzFramework::PlatformHelper::GetPlatformFlag(enabledPlatform);
if (platformFlag != AzFramework::PlatformFlags::Platform_NONE)
{
@@ -513,7 +423,7 @@ namespace AssetBundler
}
else
{
AZ_Warning(AssetBundler::AppWindowName, false, "Platform Helper is not aware of the platform (%s).\n ", enabledPlatform.toUtf8().data());
AZ_Warning(AssetBundler::AppWindowName, false, "Platform Helper is not aware of the platform (%s).\n ", enabledPlatform.c_str());
}
}
@@ -535,68 +445,60 @@ namespace AssetBundler
AZ::Outcome<AZStd::string, AZStd::string> GetCurrentProjectName()
{
AZStd::string gameName;
bool result{ false };
auto gameFolderKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::ProjectName);
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
AZStd::string projectName{ AZStd::string_view(AZ::Utils::GetProjectName()) };
if (!projectName.empty())
{
result = settingsRegistry->Get(gameName, gameFolderKey);
}
if (result)
{
return AZ::Success(gameName);
return AZ::Success(projectName);
}
else
{
return AZ::Failure(AZStd::string("Unable to locate current project name in bootstrap.cfg"));
return AZ::Failure(AZStd::string("Unable to obtain current project name from registry"));
}
}
AZ::Outcome<AZStd::string, AZStd::string> GetProjectFolderPath(const AZStd::string& engineRoot, const AZStd::string& projectName)
AZ::Outcome<AZ::IO::Path, AZStd::string> GetProjectFolderPath()
{
AZStd::string projectFolderPath;
bool success = AzFramework::StringFunc::Path::ConstructFull(engineRoot.c_str(), projectName.c_str(), projectFolderPath);
if (success && AZ::IO::FileIOBase::GetInstance()->Exists(projectFolderPath.c_str()))
AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath();
if (!projectPath.empty())
{
return AZ::Success(projectFolderPath);
return AZ::Success(AZ::IO::Path{ AZ::IO::PathView(projectPath) });
}
else
{
return AZ::Failure(AZStd::string::format( "Unable to locate the current Project folder: %s", projectName.c_str()));
return AZ::Failure(AZStd::string::format("Unable to obtain current project path from registry"));
}
}
AZ::Outcome<AZStd::string, AZStd::string> GetProjectCacheFolderPath(const AZStd::string& engineRoot, const AZStd::string& projectName)
AZ::Outcome<AZ::IO::Path, AZStd::string> GetProjectCacheFolderPath()
{
AZStd::string projectCacheFolderPath;
AZ::IO::Path projectCacheFolderPath;
bool success = AzFramework::StringFunc::Path::ConstructFull(engineRoot.c_str(), "Cache", projectCacheFolderPath);
if (!success || !AZ::IO::FileIOBase::GetInstance()->Exists(projectCacheFolderPath.c_str()))
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry && settingsRegistry->Get(projectCacheFolderPath.Native(),
AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
{
return AZ::Failure(AZStd::string::format(
"Unable to locate the Cache in the engine directory: %s. Please run the Lumberyard Asset Processor to generate a Cache and build assets.",
engineRoot.c_str()));
if (AZ::IO::FileIOBase::GetInstance()->Exists(projectCacheFolderPath.c_str()))
{
return AZ::Success(projectCacheFolderPath);
}
}
success = AzFramework::StringFunc::Path::ConstructFull(projectCacheFolderPath.c_str(), projectName.c_str(), projectCacheFolderPath);
if (success && AZ::IO::FileIOBase::GetInstance()->Exists(projectCacheFolderPath.c_str()))
{
return AZ::Success(projectCacheFolderPath);
}
else
{
return AZ::Failure(AZStd::string::format(
"Unable to locate the current Project in the Cache folder: %s. Please run the Lumberyard Asset Processor to generate a Cache and build assets.",
projectName.c_str()));
}
return AZ::Failure(AZStd::string::format(
"Unable to locate the Project Cache path from Settings Registry at key %s."
" Please run the Lumberyard Asset Processor to generate a Cache and build assets.",
AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder));
}
AZ::Outcome<void, AZStd::string> GetPlatformNamesFromCacheFolder(const AZStd::string& projectCacheFolder, AZStd::vector<AZStd::string>& platformNames)
AZ::Outcome<void, AZStd::string> GetPlatformNamesFromCacheFolder(AZStd::vector<AZStd::string>& platformNames)
{
QDir projectCacheDir(QString(projectCacheFolder.c_str()));
AZ::Outcome<AZ::IO::Path, AZStd::string> projectCacheRootFolder = GetProjectCacheFolderPath();
if (!projectCacheRootFolder)
{
return AZ::Failure(projectCacheRootFolder.TakeError());
}
const AZStd::string& projectCacheRootPath = projectCacheRootFolder.GetValue().Native();
QDir projectCacheDir(QString::fromUtf8(projectCacheRootPath.c_str(), aznumeric_cast<int>(projectCacheRootPath.size())));
auto tempPlatformList = projectCacheDir.entryList(QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot);
if (tempPlatformList.empty())
@@ -612,66 +514,33 @@ namespace AssetBundler
return AZ::Success();
}
AZ::Outcome<AZStd::string, AZStd::string> GetAssetCatalogFilePath(const char* pathToCacheFolder, const char* platformIdentifier, const char* projectName)
AZ::Outcome<AZ::IO::Path, AZStd::string> GetAssetCatalogFilePath()
{
AZStd::string assetCatalogFilePath;
bool success = AzFramework::StringFunc::Path::ConstructFull(pathToCacheFolder, platformIdentifier, assetCatalogFilePath, true);
if (!success)
AZ::IO::Path assetCatalogFilePath = GetPlatformSpecificCacheFolderPath();
if (assetCatalogFilePath.empty())
{
return AZ::Failure(AZStd::string::format(
"Unable to find platform folder %s in cache found at: %s. Please run the Lumberyard Asset Processor to generate platform-specific cache folders and build assets.",
platformIdentifier,
pathToCacheFolder));
}
// Project name is lower case in the platform-specific cache folder
AZStd::string lowerCaseProjectName = AZStd::string(projectName);
AZStd::to_lower(lowerCaseProjectName.begin(), lowerCaseProjectName.end());
success = AzFramework::StringFunc::Path::ConstructFull(assetCatalogFilePath.c_str(), lowerCaseProjectName.c_str(), assetCatalogFilePath)
&& AzFramework::StringFunc::Path::ConstructFull(assetCatalogFilePath.c_str(), AssetCatalogFilename, assetCatalogFilePath);
if (!success)
{
return AZ::Failure(AZStd::string("Unable to find the asset catalog. Please run the Lumberyard Asset Processor to generate a Cache and build assets."));
"Unable to retrieve cache platform path from Settings Registry at key: %s. Please run the Lumberyard Asset Processor to generate platform-specific cache folders and build assets.",
AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder));
}
assetCatalogFilePath /= AssetCatalogFilename;
return AZ::Success(assetCatalogFilePath);
}
AZStd::string GetPlatformSpecificCacheFolderPath(const AZStd::string& projectSpecificCacheFolderAbsolutePath, const AZStd::string& platform, const AZStd::string& projectName)
AZ::IO::Path GetPlatformSpecificCacheFolderPath()
{
// C:/dev/Cache/ProjectName -> C:/dev/Cache/ProjectName/platform
AZStd::string platformSpecificCacheFolderPath;
AzFramework::StringFunc::Path::ConstructFull(projectSpecificCacheFolderAbsolutePath.c_str(), platform.c_str(), platformSpecificCacheFolderPath, true);
// C:/dev/Cache/ProjectName/platform -> C:/dev/Cache/ProjectName/platform/projectname
AZStd::string lowerCaseProjectName = AZStd::string(projectName);
AZStd::to_lower(lowerCaseProjectName.begin(), lowerCaseProjectName.end());
AzFramework::StringFunc::Path::ConstructFull(platformSpecificCacheFolderPath.c_str(), lowerCaseProjectName.c_str(), platformSpecificCacheFolderPath, true);
AZ::IO::Path platformSpecificCacheFolderPath;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(platformSpecificCacheFolderPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
}
return platformSpecificCacheFolderPath;
}
AZStd::string GenerateKeyFromAbsolutePath(const AZStd::string& absoluteFilePath)
void ConvertToRelativePath(AZStd::string_view parentFolderPath, AZStd::string& absoluteFilePath)
{
AZStd::string key(absoluteFilePath);
AzFramework::StringFunc::Path::Normalize(key);
AzFramework::StringFunc::Path::StripDrive(key);
return key;
}
void ConvertToRelativePath(const AZStd::string& parentFolderPath, AZStd::string& absoluteFilePath)
{
// Qt and AZ return different Drive Letter formats, so strip them away before doing a comparison
AZStd::string parentFolderPathWithoutDrive(parentFolderPath);
AzFramework::StringFunc::Path::StripDrive(parentFolderPathWithoutDrive);
AzFramework::StringFunc::Path::Normalize(parentFolderPathWithoutDrive);
AzFramework::StringFunc::Path::StripDrive(absoluteFilePath);
AzFramework::StringFunc::Path::Normalize(absoluteFilePath);
AzFramework::StringFunc::Replace(absoluteFilePath, parentFolderPathWithoutDrive.c_str(), "");
absoluteFilePath = AZ::IO::PathView(absoluteFilePath).LexicallyRelative(parentFolderPath).String();
}
AZ::Outcome<void, AZStd::string> MakePath(const AZStd::string& path)
+26 -50
View File
@@ -120,20 +120,8 @@ namespace AssetBundler
////////////////////////////////////////////////////////////////////////////////////////////
extern const char* AssetCatalogFilename;
extern char g_cachedEngineRoot[AZ_MAX_PATH_LEN];
static const size_t MaxErrorMessageLength = 4096;
//! This struct stores gem related information
struct GemInfo
{
AZ_CLASS_ALLOCATOR(GemInfo, AZ::SystemAllocator, 0);
GemInfo(AZStd::string name, AZStd::string relativeFilePath, AZStd::string absoluteFilePath);
GemInfo() = default;
AZStd::string m_gemName;
AZStd::string m_relativeFilePath;
AZStd::string m_absoluteFilePath;
};
// The Warning Absorber is used to absorb warnings
// One case that this is being used is during loading of the asset catalog.
@@ -149,14 +137,8 @@ namespace AssetBundler
bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override;
};
// computes the asset alias and game name either through the asset catalog file provided by the user or using the platform and game folder
AZ::Outcome<void, AZStd::string> ComputeAssetAliasAndGameName(const AZStd::string& platformIdentifier, const AZStd::string& assetCatalogFile, AZStd::string& assetAlias, AZStd::string& gameName);
// Computes the engine root and cache it locally
bool ComputeEngineRoot();
// Retrurns the engine root
const char* GetEngineRoot();
// Returns the engine root
AZ::IO::FixedMaxPath GetEngineRoot();
/**
* Determines the name of the currently enabled game project
@@ -166,58 +148,51 @@ namespace AssetBundler
/**
* Constructs an absolute path to the project folder found at: dev/ProjectName
* @param engineRoot The absolute path of the dev/ folder
* @param projectName A project present in the dev/ folder
* Retrieve the project path from the Settings Registry
* @return Absolute path of the Project Folder on success, error message on failure
*/
AZ::Outcome<AZStd::string, AZStd::string> GetProjectFolderPath(const AZStd::string& engineRoot, const AZStd::string& projectName);
AZ::Outcome<AZ::IO::Path, AZStd::string> GetProjectFolderPath();
/**
* Constructs an absolute path to the project-specific cache folder found at: dev/Cache/ProjectName
* @param engineRoot The absolute path of the dev/ folder
* @param projectName A project present in the dev/ folder
* Retrieve the project path from the Settings Registry
* @return Absolute path of the project-specific cache folder on success, error message on failure
*/
AZ::Outcome<AZStd::string, AZStd::string> GetProjectCacheFolderPath(const AZStd::string& engineRoot, const AZStd::string& projectName);
AZ::Outcome<AZ::IO::Path, AZStd::string> GetProjectCacheFolderPath();
/**
* Calculates the list of enabled platforms for the input project by reading the folder names inside the project-specific cache folder.
* If the Asset Processor has not been run yet, or has not been run since the enabled platform list inside AssetProcessorPlatformConfig.ini
* If the Asset Processor has not been run yet, or has not been run since the enabled platform list inside AssetProcessorPlatformConfig.setreg
* was changed, the output of this function will be incorrect.
*
* @param projectCacheFolder The directory of a project-specific cache folder: dev/Cache/ProjectName
* @param projectCacheFolder The directory of a project-specific cache folder: /ProjectPath/Cache
* @param platformNames [out] The list of platforms enabled in the project
* @return void on success, error message on failure
*/
AZ::Outcome<void, AZStd::string> GetPlatformNamesFromCacheFolder(const AZStd::string& projectCacheFolder, AZStd::vector<AZStd::string>& platformNames);
AZ::Outcome<void, AZStd::string> GetPlatformNamesFromCacheFolder(AZStd::vector<AZStd::string>& platformNames);
/**
* Computes the absolute path to the Asset Catalog file for a specified project and platform.
* With platform set as "pc" and project as "ProjectName", the path will resemble: C:/dev/Cache/ProjectName/pc/projectname/assetcatalog.xml
* With platform set as "pc" and project as "ProjectName", the path will resemble: C:/ProjectPath/Cache/pc/assetcatalog.xml
*
* @param pathToCacheFolder The absolute path to the Cache folder. ex: C:/dev/Cache
* @param pathToCacheFolder The absolute path to the Cache folder. ex: C:/ProjectPath/Cache
* @param platformIdentifier The platform identifier of the desired Asset Catalog. Valid inputs can be found by reading the folder names
* found inside dev/Cache/ProjectName
* found inside ProjectPath/Cache
* @param projectName The name of the project you want to search
* @return Absolute Path to the Asset Catalog file on success, error message on failure
*/
AZ::Outcome<AZStd::string, AZStd::string> GetAssetCatalogFilePath(const char* pathToCacheFolder, const char* platformIdentifier, const char* projectName);
AZ::Outcome<AZ::IO::Path, AZStd::string> GetAssetCatalogFilePath();
/**
* Computes the absolute path to the platform-specific Cache folder where product assets are stored.
* With platform set as "pc" and project as "ProjectName", the path will resemble: C:/dev/Cache/ProjectName/pc/projectname/
* With platform set as "pc" the path will resemble: C:/ProjectPath/Cache/pc/projectname/
*
* @param projectSpecificCacheFolderAbsolutePath The absolute path to the Cache folder. Example: C:/dev/Cache/ProjectName
* @param projectSpecificCacheFolderAbsolutePath The absolute path to the Cache folder. Example: C:/ProjectPath/Cache
* @param platform the platform of the desired cache location
* @param projectName The name of the current project
* @return Absolute path to the platform-specific Cache folder where product assets are stored
*/
AZStd::string GetPlatformSpecificCacheFolderPath(const AZStd::string& projectSpecificCacheFolderAbsolutePath, const AZStd::string& platform, const AZStd::string& projectName);
AZ::IO::Path GetPlatformSpecificCacheFolderPath();
AZStd::string GenerateKeyFromAbsolutePath(const AZStd::string& absoluteFilePath);
void ConvertToRelativePath(const AZStd::string& parentFolderPath, AZStd::string& absoluteFilePath);
void ConvertToRelativePath(AZStd::string_view parentFolderPath, AZStd::string& absoluteFilePath);
AZ::Outcome<void, AZStd::string> MakePath(const AZStd::string& path);
@@ -228,30 +203,31 @@ namespace AssetBundler
AzFramework::PlatformFlags GetPlatformsOnDiskForPlatformSpecificFile(const AZStd::string& platformIndependentAbsolutePath);
//! Returns a map of <absolute file path, source folder display name> of all default Seed List files for the current game project.
AZStd::unordered_map<AZStd::string, AZStd::string> GetDefaultSeedListFiles(const char* root, const char* projectName, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags);
AZStd::unordered_map<AZStd::string, AZStd::string> GetDefaultSeedListFiles(AZStd::string_view enginePath, AZStd::string_view projectPath,
const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags);
//! Returns a vector of relative paths to Assets that should be included as default Seeds, but are not already in a Seed List file.
AZStd::vector<AZStd::string> GetDefaultSeeds(const char* root, const char* projectName);
AZStd::vector<AZStd::string> GetDefaultSeeds(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName);
//! Returns the absolute path of {ProjectName}_Dependencies.xml
AZStd::string GetProjectDependenciesFile(const char* root, const char* projectName);
AZ::IO::Path GetProjectDependenciesFile(AZStd::string_view productPath, AZStd::string_view projectName);
//! Returns the absolute path of the project dependencies file in the default project template
AZStd::string GetProjectDependenciesFileTemplate(const char* root);
AZ::IO::Path GetProjectDependenciesFileTemplate(AZStd::string_view enginePath);
//! Creates the ProjectName_Dependencies.xml file if it does not exist, and adds returns the relative path to the asset in the Cache.
AZStd::string GetProjectDependenciesAssetPath(const char* root, const char* projectName);
AZ::IO::Path GetProjectDependenciesAssetPath(AZStd::string_view enginePath, AZStd::string_view projectPath, AZStd::string_view projectName);
//! Returns the map from gem seed list file path to gem name
AZStd::unordered_map<AZStd::string, AZStd::string> GetGemSeedListFilePathToGemNameMap(const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags);
AZStd::unordered_map<AZStd::string, AZStd::string> GetGemSeedListFilePathToGemNameMap(const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags);
//! Given an absolute gem seed file path determines whether the file is valid for the current game project.
//! This method is for validating gem seed list files only.
bool IsGemSeedFilePathValid(const char* root, AZStd::string seedAbsoluteFilePath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags);
bool IsGemSeedFilePathValid(AZStd::string_view enginePath, AZStd::string seedAbsoluteFilePath, const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AzFramework::PlatformFlags platformFlags);
//! Returns platformFlags of all enabled platforms by parsing all the asset processor config files.
//! Please note that the game project could be in a different location to the engine therefore we need the assetRoot param.
AzFramework::PlatformFlags GetEnabledPlatformFlags(const char* root, const char* assetRoot, const char* gameName);
AzFramework::PlatformFlags GetEnabledPlatformFlags(AZStd::string_view enginePath, AZStd::string_view assetRoot, AZStd::string_view projectPath);
//! Filepath is a helper class that is used to find the absolute path of a file
//! if the inputted file path is an absolute path than it does nothing
@@ -1,25 +0,0 @@
{
"GemListFormatVersion": 2,
"Gems": [
{
"Path": "Gems/GemA",
"Uuid": "044a63ea67d04479aa5daf62ded9d9cb",
"Version": "0.1.0",
"_comment": "GemA"
},
{
"Path": "Gems/GemB",
"Uuid": "07375b61b1a2424bb03088bbdf28b2c9",
"Version": "0.1.0",
"_comment": "GemB"
},
{
"Path": "Gems/GemC",
"Uuid": "0945e21b7ae848ac80b4ec1f34c459cd",
"Version": "0.1.0",
"_comment": "GemC"
}
]
}
+1 -1
View File
@@ -51,7 +51,7 @@ namespace AssetBundler
// AzFramework::ApplicationRequests::Bus::Handler interface
void NormalizePath(AZStd::string& /*path*/) override {}
void NormalizePathKeepCase(AZStd::string& /*path*/) override {}
void CalculateBranchTokenForAppRoot(AZStd::string& /*token*/) const override {}
void CalculateBranchTokenForEngineRoot(AZStd::string& /*token*/) const override {}
const char* GetAppRoot() const override
{
@@ -115,9 +115,9 @@ namespace AssetBundler
settingsRegistry->Set(gemSourcePathKey, gemSourcePath.Native());
}
AzToolsFramework::AssetUtils::GetGemsInfo(m_data->m_testEngineRoot.c_str(), m_data->m_testEngineRoot.c_str(), DummyProjectName, m_data->m_applicationManager->m_gemInfoList);
AzFramework::GetGemsInfo(m_data->m_applicationManager->m_gemInfoList, *settingsRegistry);
EXPECT_GE(m_data->m_applicationManager->m_gemInfoList.size(), 3);
for (const AzToolsFramework::AssetUtils::GemInfo& gemInfo : m_data->m_applicationManager->m_gemInfoList)
for (const AzFramework::GemInfo& gemInfo : m_data->m_applicationManager->m_gemInfoList)
{
gemsNameMap.erase(gemInfo.m_gemName);
}
+18 -139
View File
@@ -128,7 +128,7 @@ namespace AssetBundler
// underneath code assumes that we might be leaking the previous instance
AZ::IO::FileIOBase::SetInstance(nullptr);
AZ::IO::FileIOBase::SetInstance(m_data->m_localFileIO);
AddGemData(m_data->m_testEngineRoot.c_str(), "GemA");
AddGemData(m_data->m_testEngineRoot.c_str(), "GemB");
@@ -169,20 +169,21 @@ namespace AssetBundler
m_data->m_gemSeedFilePairList.emplace_back(absoluteGemSeedFilePath, seedFileExists);
m_data->m_gemInfoList.emplace_back(AzToolsFramework::AssetUtils::GemInfo(gemName, relativeGemPath.Native(), absoluteGemPath.Native(), AZ::Uuid::CreateRandom().ToString<AZStd::string>().c_str(), false, false));
m_data->m_gemInfoList.emplace_back(gemName);
m_data->m_gemInfoList.back().m_absoluteSourcePaths.push_back(absoluteGemPath.Native());
AZ::IO::Path platformsDirectory = absoluteGemPath / "Assets" / PlatformsFolder;
if (m_data->m_localFileIO->Exists(platformsDirectory.c_str()))
{
m_data->m_localFileIO->FindFiles(platformsDirectory.c_str(),
AZStd::string::format("*.%s", AzToolsFramework::AssetSeedManager::GetSeedFileExtension()).c_str(),
[&](const char* fileName)
{
AZStd::string normalizedFilePath = fileName;
AzFramework::StringFunc::Path::Normalize(normalizedFilePath);
m_data->m_gemSeedFilePairList.emplace_back(AZStd::make_pair(normalizedFilePath, seedFileExists));
return true;
});
{
AZStd::string normalizedFilePath = fileName;
AzFramework::StringFunc::Path::Normalize(normalizedFilePath);
m_data->m_gemSeedFilePairList.emplace_back(AZStd::make_pair(normalizedFilePath, seedFileExists));
return true;
});
}
AZ::IO::Path iosDirectory = platformsDirectory / AzFramework::PlatformIOS;
@@ -195,7 +196,7 @@ namespace AssetBundler
if (result.IsSuccess())
{
AZStd::list<AZStd::string> seedFiles = result.TakeValue();
for(AZStd::string& seedFile : seedFiles)
for (AZStd::string& seedFile : seedFiles)
{
AZStd::string normalizedFilePath = seedFile;
AzFramework::StringFunc::Path::Normalize(normalizedFilePath);
@@ -207,7 +208,7 @@ namespace AssetBundler
struct StaticData
{
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> m_gemInfoList;
AZStd::vector<AzFramework::GemInfo> m_gemInfoList;
AZStd::vector<AZStd::pair<AZStd::string, bool>> m_gemSeedFilePairList;
AZStd::unique_ptr<AzToolsFramework::ToolsApplication> m_application = {};
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
@@ -230,7 +231,8 @@ namespace AssetBundler
TEST_F(AssetBundlerGemsUtilTest, GetDefaultSeedFiles_AllSeedFiles_Found)
{
// DummyProject and fake Engine/Gem structure lives at dev/Code/Tools/AssetBundler/tests/
auto defaultSeedList = AssetBundler::GetDefaultSeedListFiles(m_data->m_testEngineRoot.c_str(), DummyProjectFolder, m_data->m_gemInfoList, AzFramework::PlatformFlags::Platform_PC);
auto dummyProjectPath = AZ::IO::Path(m_data->m_testEngineRoot) / DummyProjectFolder;
auto defaultSeedList = AssetBundler::GetDefaultSeedListFiles(m_data->m_testEngineRoot.c_str(), dummyProjectPath.Native(), m_data->m_gemInfoList, AzFramework::PlatformFlags::Platform_PC);
ASSERT_EQ(defaultSeedList.size(), 5); //adding one for the engine seed file and one for the project file
// Validate whether both GemA and GemB seed file are present
@@ -246,7 +248,8 @@ namespace AssetBundler
TEST_F(AssetBundlerGemsUtilTest, GetDefaultSeedFilesForMultiplePlatforms_AllSeedFiles_Found)
{
// DummyProject and fake Engine/Gem structure lives at dev/Code/Tools/AssetBundler/tests/
auto defaultSeedList = AssetBundler::GetDefaultSeedListFiles(m_data->m_testEngineRoot.c_str(), DummyProjectFolder, m_data->m_gemInfoList, AzFramework::PlatformFlags::Platform_PC | AzFramework::PlatformFlags::Platform_IOS);
auto dummyProjectPath = AZ::IO::Path(m_data->m_testEngineRoot) / DummyProjectFolder;
auto defaultSeedList = AssetBundler::GetDefaultSeedListFiles(m_data->m_testEngineRoot.c_str(), dummyProjectPath.Native(), m_data->m_gemInfoList, AzFramework::PlatformFlags::Platform_PC | AzFramework::PlatformFlags::Platform_IOS);
ASSERT_EQ(defaultSeedList.size(), 6); //adding one for the engine seed file and one for the project file
@@ -264,136 +267,12 @@ namespace AssetBundler
TEST_F(AssetBundlerGemsUtilTest, IsSeedFileValid_Ok)
{
for (const auto& pair : m_data->m_gemSeedFilePairList)
for (const auto& pair : m_data->m_gemSeedFilePairList)
{
bool result = IsGemSeedFilePathValid(m_data->m_testEngineRoot.c_str(), pair.first, m_data->m_gemInfoList, AzFramework::PlatformFlags::Platform_PC | AzFramework::PlatformFlags::Platform_IOS);
EXPECT_EQ(result,pair.second);
EXPECT_EQ(result, pair.second);
}
}
const char TestProject[] = "TestProject";
const char TestProjectLowerCase[] = "testproject";
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
const char TestRoot[] = "D:\\Dummy\\Test\\dev\\";
#else
const char TestRoot[] = "/Dummy/Test/dev/";
#endif
class MockApplication
: public AzFramework::ApplicationRequests::Bus::Handler
{
public:
MockApplication()
{
// ensure the cached engine root from previous tests is cleared
// so the mock application behaves properly
g_cachedEngineRoot[0] = 0;
if (AZ::SettingsRegistry::Get() == nullptr)
{
m_settingsRegistry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
AZ::SettingsRegistry::Register(m_settingsRegistry.get());
auto gameProjectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, "sys_game_folder");
m_settingsRegistry->Set(gameProjectKey, TestProject);
}
AzFramework::ApplicationRequests::Bus::Handler::BusConnect();
}
~MockApplication()
{
AzFramework::ApplicationRequests::Bus::Handler::BusDisconnect();
if (m_settingsRegistry.get() == AZ::SettingsRegistry::Get())
{
AZ::SettingsRegistry::Unregister(m_settingsRegistry.get());
}
}
// AzFramework::ApplicationRequests::Bus::Handler interface
void NormalizePath(AZStd::string& /*path*/) override {};
void NormalizePathKeepCase(AZStd::string& /*path*/) override {};
void CalculateBranchTokenForAppRoot(AZStd::string& /*token*/) const override {};
const char* GetEngineRoot() const { return TestRoot; }
private:
AZStd::unique_ptr<AZ::SettingsRegistryInterface> m_settingsRegistry;
};
class AssetBundlerPathUtilTest
: public UnitTest::ScopedAllocatorSetupFixture
{
public:
void SetUp() override
{
UnitTest::ScopedAllocatorSetupFixture::SetUp();
m_data = AZStd::make_unique<StaticData>();
}
void TearDown() override
{
m_data.reset();
UnitTest::ScopedAllocatorSetupFixture::TearDown();
}
struct StaticData
{
MockApplication m_mockApplication;
};
AZStd::unique_ptr<StaticData> m_data;
};
TEST_F(AssetBundlerPathUtilTest, ComputeAssetAliasAndGameName_AssetCatalogPathNotProvided_Valid)
{
AZStd::string platformIdentifier = "pc";
AZStd::string assetCatalogFile;
AZStd::string assetAlias;
AZStd::string gameName;
EXPECT_TRUE(ComputeAssetAliasAndGameName(platformIdentifier, assetCatalogFile, assetAlias, gameName).IsSuccess());
EXPECT_TRUE(gameName == TestProject);
AZStd::string assetPath;
bool success = AzFramework::StringFunc::Path::ConstructFull(TestRoot, "Cache", assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), TestProject, assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), platformIdentifier.c_str(), assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), TestProjectLowerCase, assetPath);
EXPECT_EQ(assetAlias, assetPath);
}
TEST_F(AssetBundlerPathUtilTest, ComputeAssetAliasAndGameName_AssetCatalogPathProvided_Valid)
{
AZStd::string platformIdentifier = "pc";
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
AZStd::string assetCatalogFile = "D:\\Dummy\\Test\\dev\\Cache\\TestProject1\\pc\\testproject1\\assetcatalog.xml";
#else
AZStd::string assetCatalogFile = "/Dummy/Test/dev/Cache/TestProject1/pc/testproject1/assetcatalog.xml";
#endif
AZStd::string assetAlias;
AZStd::string gameName;
EXPECT_TRUE(ComputeAssetAliasAndGameName(platformIdentifier, assetCatalogFile, assetAlias, gameName).IsSuccess());
EXPECT_EQ(gameName, "TestProject1");
AZStd::string assetPath;
bool success = AzFramework::StringFunc::Path::ConstructFull(TestRoot, "Cache", assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), "TestProject1", assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), platformIdentifier.c_str(), assetPath) &&
AzFramework::StringFunc::Path::ConstructFull(assetPath.c_str(), "testproject1", assetPath);
EXPECT_EQ(assetAlias, assetPath);
}
TEST_F(AssetBundlerPathUtilTest, ComputeAssetAliasAndGameName_GameNameMismatch_Failure)
{
AZStd::string platformIdentifier = "pc";
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
AZStd::string assetCatalogFile = "D:\\Dummy\\Cache\\TestProject1\\pc\\testproject1\\assetcatalog.xml";
#else
AZStd::string assetCatalogFile = "/Dummy/Cache/TestProject1/pc/testproject1/assetcatalog.xml";
#endif
AZStd::string assetAlias;
AZStd::string gameName="SomeOtherGamename";
EXPECT_FALSE(ComputeAssetAliasAndGameName(platformIdentifier, assetCatalogFile, assetAlias, gameName).IsSuccess());
}
}
int main(int argc, char* argv[])
@@ -18,6 +18,7 @@
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Slice/SliceSystemComponent.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Asset/AssetCatalogComponent.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
@@ -97,21 +98,6 @@ AssetBuilderApplication::AssetBuilderApplication(int* argc, char*** argv)
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
*settingsRegistry, AssetBuilder::GetBuildTargetName());
// Override the /Amazon/AzCore/Bootstrap/sys_game_folder entry in the Settings Registry using the -gameName parameter
if (m_commandLine.GetNumSwitchValues("gameName") > 0)
{
const AZStd::string& gameFolderOverride = m_commandLine.GetSwitchValue("gameName", 0);
auto gameFolderCommandLineOverride = AZStd::string::format("--regset=%s/sys_game_folder=%s", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey,
gameFolderOverride.c_str());
AZ::CommandLine::ParamContainer commandLineArgs;
m_commandLine.Dump(commandLineArgs);
commandLineArgs.emplace_back(gameFolderCommandLineOverride);
m_commandLine.Parse(commandLineArgs);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*settingsRegistry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
}
AZ::Interface<IBuilderApplication>::Register(this);
}
@@ -123,7 +109,7 @@ AssetBuilderApplication::~AssetBuilderApplication()
void AssetBuilderApplication::RegisterCoreComponents()
{
AzToolsFramework::ToolsApplication::RegisterCoreComponents();
RegisterComponentDescriptor(AssetBuilderComponent::CreateDescriptor());
RegisterComponentDescriptor(AssetProcessor::ToolsAssetCatalogComponent::CreateDescriptor());
}
@@ -134,32 +120,8 @@ void AssetBuilderApplication::StartCommon(AZ::Entity* systemEntity)
// Merge in the SettingsRegistry for the game being processed. This does not
// necessarily correspond to the project name in the bootstrap.cfg since it
// the AssetBuilder supports overriding the gameName on the command line
// the AssetBuilder supports overriding the project-path on the command line
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
AZ::SettingsRegistryInterface::FixedValueString gameName;
if (m_commandLine.GetNumSwitchValues("gameName") > 0)
{
gameName = AZStd::string_view(m_commandLine.GetSwitchValue("gameName", 0));
}
// Add the supplied gameName to the specialization key in the registry
if (!gameName.empty())
{
auto gameNameSpecialization = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%.*s",
AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, aznumeric_cast<int>(gameName.size()), gameName.data());
registry.Set(gameNameSpecialization, true);
}
else
{
// Add the project name as a registry specialization
auto projectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/sys_game_folder", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
if (AZ::SettingsRegistryInterface::FixedValueString bootstrapGameName; registry.Get(bootstrapGameName, projectKey) && !bootstrapGameName.empty())
{
registry.Set(AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, bootstrapGameName.c_str()),
true);
}
}
// Retrieve specializations from the Settings Registry and ComponentApplication derived classes
AZ::SettingsRegistryInterface::Specializations specializations;
@@ -177,41 +139,25 @@ void AssetBuilderApplication::StartCommon(AZ::Entity* systemEntity)
// the executable, we need to set the PATH environment variable.
AZStd::string exeFolder;
AZ::ComponentApplicationBus::BroadcastResult(exeFolder, &AZ::ComponentApplicationBus::Events::GetExecutableFolder);
setenv("PATH", exeFolder.c_str(), 1);
#endif // AZ_PLATFORM_MAC
AZStd::string gameRoot;
if (m_commandLine.GetNumSwitchValues("gameRoot") > 0)
{
gameRoot = m_commandLine.GetSwitchValue("gameRoot", 0);
}
if (gameRoot.empty())
// Make sure a project path was set to settings registry and error/warn if not.
auto projectPath = AZ::Utils::GetProjectPath();
if (projectPath.empty())
{
if (IsInDebugMode())
{
if (!AZ::SettingsRegistry::Get()->Get(gameRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder))
{
AZ_Error("AssetBuilder", 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;
}
AZ_Error("AssetBuilder", false, "Unable to determine the project path automatically. "
"Make sure a default project path has been set or provide a --project-path option on the command line. "
"(See -help for more info.)");
return;
}
else
{
AZ_Printf(AssetBuilderSDK::InfoWindow, "gameRoot not specified on the command line, assuming current directory.\n");
AZ_Printf(AssetBuilderSDK::InfoWindow, "gameRoot is best specified as the full path to the game's asset folder.");
}
}
if (!gameRoot.empty())
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO)
{
fileIO->SetAlias("@devassets@", gameRoot.c_str());
AZ_Printf(AssetBuilderSDK::InfoWindow, "project-path not specified on the command line, assuming current directory.\n");
AZ_Printf(AssetBuilderSDK::InfoWindow, "project-path is best specified as the full path to the project's folder.");
}
}
@@ -227,7 +173,7 @@ void AssetBuilderApplication::StartCommon(AZ::Entity* systemEntity)
// Disable parallel dependency loads since the builders can't count on all other assets and their info being ready.
// Specifically, asset builders can trigger asset loads during the building process. The ToolsAssetCatalog doesn't
// implement the dependency APIs, so the asset loads will fail to load any dependent assets.
//
//
// NOTE: The ToolsAssetCatalog could *potentially* implement the dependency APIs by querying the live Asset Processor instance,
// but this will return incomplete dependency information based on the subset of assets that have already processed.
// In theory, if the Asset Builder dependencies are set up correctly, the needed subset should always be processed first,
@@ -242,61 +188,6 @@ bool AssetBuilderApplication::IsInDebugMode() const
return AssetBuilderComponent::IsInDebugMode(m_commandLine);
}
bool AssetBuilderApplication::GetOptionalAppRootArg(char destinationRootArgBuffer[], size_t destinationRootArgBufferSize) const
{
// Only continue if the application received any arguments from the command line
if ((!this->m_argC) || (!this->m_argV))
{
return false;
}
int argc = this->m_argC;
char** argv = this->m_argV;
// Search for the app root argument (-approot=<PATH>) where <PATH> is the app root path to set for the application
const static char* appRootArgPrefix = "-approot=";
size_t appRootArgPrefixLen = strlen(appRootArgPrefix);
const char* appRootArg = nullptr;
for (int index = 0; index < argc; index++)
{
if (strncmp(appRootArgPrefix, argv[index], appRootArgPrefixLen) == 0)
{
appRootArg = &argv[index][appRootArgPrefixLen];
break;
}
}
if (appRootArg)
{
AZStd::string_view appRootArgView = appRootArg;
size_t afterStartQuotes = appRootArgView.find_first_not_of(R"(")");
if (afterStartQuotes != AZStd::string_view::npos)
{
appRootArgView.remove_prefix(afterStartQuotes);
}
size_t beforeEndQuotes = appRootArgView.find_last_not_of(R"(")");
if (beforeEndQuotes != AZStd::string_view::npos)
{
appRootArgView.remove_suffix(appRootArgView.size() - (beforeEndQuotes + 1));
}
appRootArgView.copy(destinationRootArgBuffer, destinationRootArgBufferSize);
destinationRootArgBuffer[appRootArgView.size()] = '\0';
const char lastChar = destinationRootArgBuffer[strlen(destinationRootArgBuffer) - 1];
bool needsTrailingPathDelim = (lastChar != AZ_CORRECT_FILESYSTEM_SEPARATOR) && (lastChar != AZ_WRONG_FILESYSTEM_SEPARATOR);
if (needsTrailingPathDelim)
{
azstrncat(destinationRootArgBuffer, destinationRootArgBufferSize, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING, 1);
}
return true;
}
else
{
return false;
}
}
void AssetBuilderApplication::InitializeBuilderComponents()
{
CreateAndAddEntityFromComponentTags(AZStd::vector<AZ::Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }), "AssetBuilders Entity");
@@ -45,8 +45,6 @@ public:
bool IsInDebugMode() const;
bool GetOptionalAppRootArg(char destinationRootArgBuffer[], size_t destinationRootArgBufferSize) const;
void InitializeBuilderComponents() override;
private:
@@ -19,13 +19,15 @@
#include <AzCore/IO/Path/Path.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
@@ -40,8 +42,8 @@
// Command-line parameter options:
static const char* const s_paramHelp = "help"; // Print help information.
static const char* const s_paramTask = "task"; // Task to run.
static const char* const s_paramGameName = "gamename"; // Name of the current project.
static const char* const s_paramGameCache = "gamecache"; // Full path to the project cache folder.
static const char* const s_paramProjectName = "project-name"; // Name of the current project.
static const char* const s_paramProjectCacheRoot = "project-cache-path"; // Full path to the project cache folder.
static const char* const s_paramModule = "module"; // For resident mode, the path to the builder dll folder, otherwise the full path to a single builder dll to use.
static const char* const s_paramPort = "port"; // Optional, port number to use to connect to the AP.
static const char* const s_paramIp = "remoteip"; // optional, IP address to use to connect to the AP
@@ -63,6 +65,78 @@ static const char* const s_taskDebug = "debug"; // runs a one shot job in a fake
static const char* const s_taskDebugCreate = "debug_create"; // runs a one shot job in a fake environment for a specified file.
static const char* const s_taskDebugProcess = "debug_process"; // runs a one shot job in a fake environment for a specified file.
//! Scoped Setters for the SettingsRegistry to its previous value on destruction
struct ScopedSettingsRegistrySetter
{
using SettingsRegistrySetterTypes = AZStd::variant<bool, AZ::s64, AZ::u64, double, AZStd::string_view>;
using SettingsRegistryGetterTypes = AZStd::variant<bool, AZ::s64, AZ::u64, double, AZStd::string>;
ScopedSettingsRegistrySetter(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view jsonPointer,
SettingsRegistrySetterTypes newValue)
: m_settingsRegistry(settingsRegistry)
, m_jsonPointer(jsonPointer)
{
AZStd::string oldValue;
if (m_settingsRegistry.Get(oldValue, jsonPointer))
{
m_oldValue = AZStd::move(oldValue);
}
AZStd::visit([this](auto&& value) {m_settingsRegistry.Set(m_jsonPointer, AZStd::move(value)); }, AZStd::move(newValue));
}
~ScopedSettingsRegistrySetter()
{
// Reset the old value within the Settings Registry if it was set
// Or remove it if not
if (m_oldValue)
{
AZStd::visit([this](auto&& value) {m_settingsRegistry.Set(m_jsonPointer, AZStd::move(value)); }, AZStd::move(*m_oldValue));
}
else
{
m_settingsRegistry.Remove(m_jsonPointer);
}
}
AZ::SettingsRegistryInterface& m_settingsRegistry;
AZStd::string_view m_jsonPointer;
AZStd::optional<SettingsRegistryGetterTypes> m_oldValue;
};
//! FileIO classes which resets the set key to its previous value on destruction
struct ScopedAliasSetter
{
ScopedAliasSetter(AZ::IO::FileIOBase& fileIoBase, const char* alias,
const char* newValue)
: m_fileIoBase(fileIoBase)
, m_alias(alias)
{
if (const char* oldValue = m_fileIoBase.GetAlias(m_alias); oldValue != nullptr)
{
m_oldValue = oldValue;
}
m_fileIoBase.SetAlias(alias, newValue);
}
~ScopedAliasSetter()
{
// Reset the old alias if it was set or clear it if not
if (m_oldValue)
{
m_fileIoBase.SetAlias(m_alias, m_oldValue->c_str());
}
else
{
m_fileIoBase.ClearAlias(m_alias);
}
}
AZ::IO::FileIOBase& m_fileIoBase;
const char* m_alias;
AZStd::optional<AZStd::string> m_oldValue;
};
//////////////////////////////////////////////////////////////////////////
void AssetBuilderComponent::PrintHelp()
@@ -71,8 +145,8 @@ void AssetBuilderComponent::PrintHelp()
AZ_TracePrintf("Help", "The following command line options are available for the AssetBuilder.\n");
AZ_TracePrintf("Help", "%s - Print help information.\n", s_paramHelp);
AZ_TracePrintf("Help", "%s - Task to run.\n", s_paramTask);
AZ_TracePrintf("Help", "%s - Name of the current project.\n", s_paramGameName);
AZ_TracePrintf("Help", "%s - Full path to the project cache folder.\n", s_paramGameCache);
AZ_TracePrintf("Help", "%s - Name of the current project.\n", s_paramProjectName);
AZ_TracePrintf("Help", "%s - Full path to the project cache folder.\n", s_paramProjectCacheRoot);
AZ_TracePrintf("Help", "%s - For resident mode, the path to the builder dll folder, otherwise the full path to a single builder dll to use.\n", s_paramModule);
AZ_TracePrintf("Help", "%s - Optional, port number to use to connect to the AP.\n", s_paramPort);
AZ_TracePrintf("Help", "%s - UUID string that identifies the builder. Only used for resident mode when the AP directly starts up the AssetBuilder.\n", s_paramId);
@@ -106,6 +180,7 @@ bool AssetBuilderComponent::IsInDebugMode(const AzFramework::CommandLine& comman
return false;
}
void AssetBuilderComponent::Activate()
{
BuilderBus::Handler::BusConnect();
@@ -168,17 +243,12 @@ bool AssetBuilderComponent::Run()
}
bool isDebugTask = (task == s_taskDebug || task == s_taskDebugCreate || task == s_taskDebugProcess);
if (!GetParameter(s_paramGameName, m_gameName, !isDebugTask))
if (!GetParameter(s_paramProjectName, m_gameName, !isDebugTask))
{
auto gameNameKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::ProjectName);
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(m_gameName, gameNameKey);
}
m_gameName = AZ::Utils::GetProjectName();
}
if (!GetParameter(s_paramGameCache, m_gameCache, !isDebugTask))
if (!GetParameter(s_paramProjectCacheRoot, m_gameCache, !isDebugTask))
{
if (!isDebugTask)
{
@@ -283,7 +353,7 @@ bool AssetBuilderComponent::ConnectToAssetProcessor()
//the asset builder may have been given an optional project name to use
AZStd::string overrideProjectName;
if (GetParameter(s_paramGameName, overrideProjectName, false))
if (GetParameter(s_paramProjectName, overrideProjectName, false))
{
connectionSettings.m_projectName = overrideProjectName;
}
@@ -380,21 +450,15 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
return false;
}
}
AzFramework::StringFunc::Path::Normalize(debugFile);
AZ::StringFunc::Path::Normalize(debugFile);
if (!GetParameter(s_paramGameCache, m_gameCache, false))
if (!GetParameter(s_paramProjectCacheRoot, m_gameCache, false))
{
if (m_gameCache.empty())
{
// Setup the cache path
AZStd::string assetRoot;
AzFramework::ApplicationRequests::Bus::BroadcastResult(assetRoot, &AzFramework::ApplicationRequests::GetAssetRoot);
if (!assetRoot.empty())
{
AZStd::string tempString = AZStd::string::format("Cache/%s", m_gameName.c_str());
AzFramework::StringFunc::Path::Join(assetRoot.c_str(), tempString.c_str(), m_gameCache);
}
else
// Query the project cache root path from the Settings Registry
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (!settingsRegistry || !settingsRegistry->Get(m_gameCache, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
{
m_gameCache = ".";
}
@@ -415,7 +479,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
AZStd::string module;
if (GetParameter(s_paramModule, module, false))
{
AzFramework::StringFunc::Path::GetFullPath(module.c_str(), binDir);
AZ::StringFunc::Path::GetFullPath(module.c_str(), binDir);
if (!LoadBuilder(module))
{
AZ_Error("AssetBuilder", false, "Failed to load module '%s'.", module.c_str());
@@ -432,7 +496,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
return false;
}
AzFramework::StringFunc::Path::Join(executableFolder, "Builders", binDir);
AZ::StringFunc::Path::Join(executableFolder, "Builders", binDir);
if (!LoadBuilders(binDir))
{
@@ -445,11 +509,11 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
if (!GetParameter(s_paramOutput, baseTempDirPath, false))
{
AZStd::string fileName;
AzFramework::StringFunc::Path::GetFullFileName(debugFile.c_str(), fileName);
AZ::StringFunc::Path::GetFullFileName(debugFile.c_str(), fileName);
AZStd::replace(fileName.begin(), fileName.end(), '.', '_');
AzFramework::StringFunc::Path::Join(binDir.c_str(), "Debug", baseTempDirPath);
AzFramework::StringFunc::Path::Join(baseTempDirPath.c_str(), fileName.c_str(), baseTempDirPath);
AZ::StringFunc::Path::Join(binDir.c_str(), "Debug", baseTempDirPath);
AZ::StringFunc::Path::Join(baseTempDirPath.c_str(), fileName.c_str(), baseTempDirPath);
}
// Default tags for the debug task are "tools" and "debug"
@@ -489,7 +553,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Debugging builder '%s'.\n", builder->m_name.c_str());
AZStd::string tempDirPath;
AzFramework::StringFunc::Path::Join(baseTempDirPath.c_str(), builder->m_name.c_str(), tempDirPath);
AZ::StringFunc::Path::Join(baseTempDirPath.c_str(), builder->m_name.c_str(), tempDirPath);
AZStd::vector<AssetBuilderSDK::PlatformInfo> enabledDebugPlatformInfos =
{
@@ -502,7 +566,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
if (runCreateJobs)
{
AZStd::string createJobsTempDirPath;
AzFramework::StringFunc::Path::Join(tempDirPath.c_str(), "CreateJobs", createJobsTempDirPath);
AZ::StringFunc::Path::Join(tempDirPath.c_str(), "CreateJobs", createJobsTempDirPath);
AZ::IO::Result fileResult = fileIO->CreatePath(createJobsTempDirPath.c_str());
if (!fileResult)
{
@@ -520,7 +584,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
builder->m_createJobFunction(createRequest, createResponse);
AZStd::string responseFile;
AzFramework::StringFunc::Path::Join(createJobsTempDirPath.c_str(), "CreateJobsResponse.xml", responseFile);
AZ::StringFunc::Path::Join(createJobsTempDirPath.c_str(), "CreateJobsResponse.xml", responseFile);
if (!AZ::Utils::SaveObjectToFile(responseFile, AZ::DataStream::ST_XML, &createResponse))
{
AZ_Error("AssetBuilder", false, "Failed to serialize response to file: %s", responseFile.c_str());
@@ -538,7 +602,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
if (runProcessJob)
{
AZStd::string processJobTempDirPath;
AzFramework::StringFunc::Path::Join(tempDirPath.c_str(), "ProcessJobs", processJobTempDirPath);
AZ::StringFunc::Path::Join(tempDirPath.c_str(), "ProcessJobs", processJobTempDirPath);
AZ::IO::Result fileResult = fileIO->CreatePath(processJobTempDirPath.c_str());
if (!fileResult)
{
@@ -555,7 +619,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
processRequest.m_sourceFile = info.m_relativePath;
processRequest.m_platformInfo = enabledDebugPlatformInfo;
processRequest.m_sourceFileUUID = info.m_assetId.m_guid;
AzFramework::StringFunc::AssetDatabasePath::Join(processRequest.m_watchFolder.c_str(), processRequest.m_sourceFile.c_str(), processRequest.m_fullPath);
AZ::StringFunc::AssetDatabasePath::Join(processRequest.m_watchFolder.c_str(), processRequest.m_sourceFile.c_str(), processRequest.m_fullPath);
processRequest.m_tempDirPath = processJobTempDirPath;
processRequest.m_jobId = 0;
processRequest.m_builderGuid = builder->m_busId;
@@ -585,7 +649,7 @@ bool AssetBuilderComponent::RunDebugTask(AZStd::string&& debugFile, bool runCrea
ProcessJob(builder->m_processJobFunction, processRequest, processResponse);
AZStd::string responseFile;
AzFramework::StringFunc::Path::Join(processJobTempDirPath.c_str(),
AZ::StringFunc::Path::Join(processJobTempDirPath.c_str(),
AZStd::string::format("%zu_%s", i, AssetBuilderSDK::s_processJobResponseFileName).c_str(), responseFile);
if (!AZ::Utils::SaveObjectToFile(responseFile, AZ::DataStream::ST_XML, &processResponse))
{
@@ -619,36 +683,39 @@ void AssetBuilderComponent::ProcessJob(const AssetBuilderSDK::ProcessJobFunction
auto ioBase = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(ioBase != nullptr, "AZ::IO::FileIOBase must be ready for use.");
// Save out the prior paths.
const char* priorAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@assets@");
AZStd::string priorAssets = priorAlias ? priorAlias : AZStd::string();
priorAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@root@");
AZStd::string priorRoot = priorAlias ? priorAlias : AZStd::string();
priorAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@log@");
AZStd::string priorLog = priorAlias ? priorAlias : AZStd::string();
// The game name needs to be lower case within the cache area itself.
AZStd::string gameName = m_gameName;
AZStd::to_lower(gameName.begin(), gameName.end());
auto settingsRegistry = AZ::SettingsRegistry::Get();
AZ_Assert(settingsRegistry != nullptr, "SettingsRegistry must be ready for use in the AssetBuilder.");
// The root path is the cache plus the platform name.
AZ::IO::FixedMaxPath newRoot(m_gameCache);
newRoot /= request.m_platformInfo.m_identifier;
// Check if the platform identifier is a valid "asset platform"
// If so, use it, other wise use the OS default platform as a fail safe
// This is to make sure the "debug platform" isn't added as a path segment
// the Cache Root folder
if (AzFramework::PlatformHelper::GetPlatformIdFromName(request.m_platformInfo.m_identifier) != AzFramework::PlatformId::Invalid)
{
newRoot /= request.m_platformInfo.m_identifier;
}
else
{
newRoot /= AzFramework::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME);
}
// The asset path is root and the lower case game name.
AZ::IO::FixedMaxPath newAssets = newRoot / gameName;
// The log path is the asset
AZ::IO::FixedMaxPath newLog = newRoot / "user" / "log";
AZ::IO::FixedMaxPath newAssets = newRoot;
// Now set the paths and run the job.
ioBase->SetAlias("@assets@", newAssets.c_str());
ioBase->SetAlias("@root@", newRoot.c_str());
ioBase->SetAlias("@log@", newLog.c_str());
{
// Save out the prior paths.
ScopedAliasSetter assetAliasScope(*ioBase, "@assets@", newAssets.c_str());
ScopedAliasSetter rootAliasScope(*ioBase, "@root@", newRoot.c_str());
ScopedAliasSetter projectplatformCacheAliasScope(*ioBase, "@projectplatformcache@", newRoot.c_str());
ScopedSettingsRegistrySetter cacheRootFolderScope(*settingsRegistry,
AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder, newRoot.Native());
job(request, outResponse);
// Invoke the Process Job function
job(request, outResponse);
}
// The asset building ProcessJob method might read any number of source files while processing the asset.
// Ensure that any exclusive file handle locks caused by this are cleared so that other AssetBuilder processes
@@ -656,37 +723,6 @@ void AssetBuilderComponent::ProcessJob(const AssetBuilderSDK::ProcessJobFunction
// This needs to occur after the ProcessJob call, but before the file aliases get cleared.
FlushFileStreamerCache();
// Clean up the paths.
// Restore previous @assets@ alias
if (priorAssets.empty())
{
ioBase->ClearAlias("@assets");
}
else
{
ioBase->SetAlias("@assets@", priorAssets.c_str());
}
// Restore previous @root@ alias
if (priorRoot.empty())
{
ioBase->ClearAlias("@root@");
}
else
{
ioBase->SetAlias("@root@", priorRoot.c_str());
}
// Restore previous @log@ alias
if (priorLog.empty())
{
ioBase->ClearAlias("@log@");
}
else
{
ioBase->SetAlias("@log@", priorLog.c_str());
}
UpdateResultCode(request, outResponse);
}
@@ -707,8 +743,8 @@ bool AssetBuilderComponent::RunOneShotTask(const AZStd::string& task)
return false;
}
AzFramework::StringFunc::Path::Normalize(inputFilePath);
AzFramework::StringFunc::Path::Normalize(outputFilePath);
AZ::StringFunc::Path::Normalize(inputFilePath);
AZ::StringFunc::Path::Normalize(outputFilePath);
if (task == s_taskRegisterBuilder)
{
return HandleRegisterBuilder(inputFilePath, outputFilePath);
@@ -1021,7 +1057,11 @@ bool AssetBuilderComponent::GetParameter(const char* paramName, AZStd::string& o
const AzFramework::CommandLine* commandLine = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
outValue = commandLine->GetSwitchValue(paramName, 0);
size_t optionCount = commandLine->GetNumSwitchValues(paramName);
if (optionCount > 0)
{
outValue = commandLine->GetSwitchValue(paramName, optionCount - 1);
}
if (outValue.empty())
{
@@ -165,5 +165,6 @@ protected:
AZStd::unique_ptr<Job> m_queuedJob;
AZStd::string m_gameName;
AZStd::string m_projectPath;
AZStd::string m_gameCache;
};
@@ -26,62 +26,6 @@ namespace AssetBuilder
using AssetBuilderAppTest = AllocatorsFixture;
TEST_F(AssetBuilderAppTest, GetAppRootArg_AssetBuilderAppNoArgs_NoExtraction)
{
AssetBuilderApplication app(nullptr, nullptr);
char appRootBuffer[AZ_MAX_PATH_LEN];
ASSERT_FALSE(app.GetOptionalAppRootArg(appRootBuffer, AZ_MAX_PATH_LEN));
}
TEST_F(AssetBuilderAppTest, GetAppRootArg_AssetBuilderAppQuotedAppRoot_Success)
{
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
const char* appRootArg = R"str(-approot="C:\path\to\app\root\")str";
const char* expectedResult = R"str(C:\path\to\app\root\)str";
#else
const char* appRootArg = R"str(-approot="/path/to/app/root")str";
const char* expectedResult = R"str(/path/to/app/root/)str";
#endif
const char* argArray[] = {
appRootArg
};
int argc = AZ_ARRAY_SIZE(argArray);
char** argv = const_cast<char**>(argArray); // this is unfortunately necessary to get around osx's strict non-const string literal stance
AssetBuilderApplication app(&argc, &argv);
char appRootBuffer[AZ_MAX_PATH_LEN] = { 0 };
ASSERT_TRUE(app.GetOptionalAppRootArg(appRootBuffer, AZ_ARRAY_SIZE(appRootBuffer)));
ASSERT_STREQ(appRootBuffer, expectedResult);
}
TEST_F(AssetBuilderAppTest, GetAppRootArg_AssetBuilderAppNoQuotedAppRoot_Success)
{
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
const char* appRootArg = R"str(-approot=C:\path\to\app\root\)str";
const char* expectedResult = R"str(C:\path\to\app\root\)str";
#else
const char* appRootArg = R"str(-approot=/path/to/app/root)str";
const char* expectedResult = R"str(/path/to/app/root/)str";
#endif
const char* argArray[] = {
appRootArg
};
int argc = AZ_ARRAY_SIZE(argArray);
char** argv = const_cast<char**>(argArray); // this is unfortunately necessary to get around osx's strict non-const string literal stance
AssetBuilderApplication app(&argc, &argv);
char appRootBuffer[AZ_MAX_PATH_LEN] = { 0 };
ASSERT_TRUE(app.GetOptionalAppRootArg(appRootBuffer, AZ_ARRAY_SIZE(appRootBuffer)));
ASSERT_STREQ(appRootBuffer, expectedResult);
}
TEST_F(AssetBuilderAppTest, AssetBuilder_EditorScriptingComponents_Exists)
{
AssetBuilderApplication app(nullptr, nullptr);
@@ -20,13 +20,7 @@ int main(int argc, char** argv)
traceMessageHook.EnableTraceContext(true);
AZ::Debug::Trace::HandleExceptions(true);
// Perform an additional check for an override app root argument, and set it in the startup params if appropriate
char destinationRootArgBuffer[AZ_MAX_PATH_LEN];
AZ::ComponentApplication::StartupParameters startupParams;
if (app.GetOptionalAppRootArg(destinationRootArgBuffer, AZ_MAX_PATH_LEN))
{
startupParams.m_appRootOverride = destinationRootArgBuffer;
}
startupParams.m_loadDynamicModules = false;
app.Start(AzFramework::Application::Descriptor(), startupParams);
@@ -494,8 +494,8 @@ namespace AssetBuilderSDK
}
#endif // defined(ENABLE_LEGACY_PLATFORMFLAGS_SUPPORT)
PlatformInfo::PlatformInfo(const char* identifier, const AZStd::unordered_set<AZStd::string>& tags)
: m_identifier(identifier)
PlatformInfo::PlatformInfo(AZStd::string identifier, const AZStd::unordered_set<AZStd::string>& tags)
: m_identifier(AZStd::move(identifier))
, m_tags(tags)
{
}
@@ -185,7 +185,7 @@ namespace AssetBuilderSDK
};
AZStd::string m_pattern;
PatternType m_type;
PatternType m_type{};
AssetBuilderPattern() = default;
AssetBuilderPattern(const AssetBuilderPattern& src) = default;
@@ -221,8 +221,8 @@ namespace AssetBuilderSDK
AssetBuilderSDK::AssetBuilderPattern m_pattern;
RegexType m_regex;
AZStd::string m_errorString;
bool m_isRegex;
bool m_isValid;
bool m_isRegex{};
bool m_isValid{};
};
//!Information that builders will send to the assetprocessor
@@ -503,7 +503,7 @@ namespace AssetBuilderSDK
AZStd::unordered_set<AZStd::string> m_tags; ///< The tags like "console" or "tools" on that platform
PlatformInfo() = default;
PlatformInfo(const char* identifier, const AZStd::unordered_set<AZStd::string>& tags);
PlatformInfo(AZStd::string identifier, const AZStd::unordered_set<AZStd::string>& tags);
bool operator==(const PlatformInfo& other);
///! utility function. It just searches the set for you:
+65
View File
@@ -176,6 +176,71 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
VALUES LY_CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}"
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/config_broken_badplatform/AssetProcessorPlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/config_broken_badplatform
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/config_broken_noscans/AssetProcessorPlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/config_broken_noscans
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/config_broken_recognizers/AssetProcessorPlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/config_broken_recognizers
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/config_broken_noplatform/AssetProcessorPlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/config_broken_noplatform
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/config_regular/AssetProcessorPlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/config_regular
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/config_regular_platform_scanfolder/AssetProcessorPlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/config_regular_platform_scanfolder
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/EmptyDummyProject/AssetProcessorGamePlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/EmptyDummyProject
)
ly_add_target_files(
TARGETS
AssetProcessor.Tests
FILES
${CMAKE_CURRENT_SOURCE_DIR}/testdata/DummyProject/AssetProcessorGamePlatformConfig.ini
OUTPUT_SUBDIRECTORY
testdata/DummyProject
)
# Have the AssetProcessorTest use the LY_CMAKE_TARGET define of AssetProcessorBatch for the purpose
# of looking up the generated cmake build dependencies settings registry .setreg file
# It is tied to the UnitTestRunner.cpp file
@@ -10,7 +10,6 @@
#
set(FILES
testdata/unittests.qrc
testdata/config_broken_badplatform/AssetProcessorPlatformConfig.ini
testdata/config_broken_noplatform/AssetProcessorPlatformConfig.ini
testdata/config_broken_noscans/AssetProcessorPlatformConfig.ini
@@ -12,11 +12,12 @@
#include "native/AssetManager/AssetCatalog.h"
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/string/wildcard.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzFramework/FileTag/FileTagBus.h>
#include <AzFramework/FileTag/FileTag.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <QElapsedTimer>
#include "PathDependencyManager.h"
@@ -45,14 +46,12 @@ namespace AssetProcessor
m_absoluteDevFolderPath[0] = 0;
m_absoluteDevGameFolderPath[0] = 0;
AZStd::string appRoot;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot);
azstrcpy(m_absoluteDevFolderPath, AZ_MAX_PATH_LEN, appRoot.c_str());
AZStd::string gameFolderPath;
AzFramework::StringFunc::Path::Join(appRoot.c_str(), AssetUtilities::ComputeGameName().toUtf8().constData(), gameFolderPath);
azstrcpy(m_absoluteDevGameFolderPath, AZ_MAX_PATH_LEN, gameFolderPath.c_str());
AZStd::string engineRoot;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
azstrcpy(m_absoluteDevFolderPath, AZ_MAX_PATH_LEN, engineRoot.c_str());
AZStd::string gameFolderPath{AssetUtilities::ComputeProjectPath().toUtf8().constData()};
azstrcpy(m_absoluteDevGameFolderPath, AZ_MAX_PATH_LEN, gameFolderPath.c_str());
AssetUtilities::ComputeProjectCacheRoot(m_cacheRootDir);
@@ -340,9 +339,13 @@ namespace AssetProcessor
}
else
{
auto settingsRegistry = AZ::SettingsRegistry::Get();
AZ::SettingsRegistryInterface::FixedValueString cacheRootFolder;
settingsRegistry->Get(cacheRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
QString tempRegistryFile = QString("%1/%2").arg(workSpace).arg("assetcatalog.xml.tmp");
QString platformGameDir = QString("%1/%2/").arg(m_cacheRoot.absoluteFilePath(platform)).arg(AssetUtilities::ComputeGameName().toLower());
QString actualRegistryFile = QString("%1%2").arg(platformGameDir).arg("assetcatalog.xml");
QString platformCacheDir = QString::fromUtf8(cacheRootFolder.c_str(), aznumeric_cast<int>(cacheRootFolder.size()));
QString actualRegistryFile = QString("%1/%2").arg(platformCacheDir).arg("assetcatalog.xml");
AZ_TracePrintf(AssetProcessor::DebugChannel, "Creating asset catalog: %s --> %s\n", tempRegistryFile.toUtf8().constData(), actualRegistryFile.toUtf8().constData());
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
@@ -352,12 +355,12 @@ namespace AssetProcessor
AZ::IO::FileIOBase::GetInstance()->Close(fileHandle);
// Make sure that the destination folder of the registry file exists
QDir registryDir(platformGameDir);
QDir registryDir(platformCacheDir);
if (!registryDir.exists())
{
QString absPath = registryDir.absolutePath();
bool makeDirResult = AZ::IO::SystemFile::CreateDir(absPath.toUtf8().constData());
AZ_Warning(AssetProcessor::ConsoleChannel, makeDirResult, "Failed create folder %s", platformGameDir.toUtf8().constData());
AZ_Warning(AssetProcessor::ConsoleChannel, makeDirResult, "Failed create folder %s", platformCacheDir.toUtf8().constData());
}
// if we succeeded in doing this, then use "rename" to move the file over the previous copy.
@@ -488,9 +491,7 @@ namespace AssetProcessor
AZ::Data::AssetId assetId(combined.m_sourceGuid, combined.m_subID);
// relative file path is gotten by removing the platform and game from the product name
QString relativeProductPath = combined.m_productName.c_str();
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // remove PLATFORM and an extra slash
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // remove GAMENAME and an extra slash
QString relativeProductPath = AssetUtilities::StripAssetPlatform(combined.m_productName);
QString fullProductPath = m_cacheRoot.absoluteFilePath(combined.m_productName.c_str());
AZ::Data::AssetInfo info;
@@ -1133,31 +1134,17 @@ namespace AssetProcessor
return assetInfo;
}
bool ConvertDatabaseProductPathToProductFileName(QString dbPath, QString& productFileName)
bool ConvertDatabaseProductPathToProductFilename(AZStd::string_view dbPath, QString& productFileName)
{
QString gameName = AssetUtilities::ComputeGameName();
bool result = false;
int gameNameIndex = dbPath.indexOf(gameName, 0, Qt::CaseInsensitive);
if (gameNameIndex != -1)
// Always strip the leading directory from the product path
// The leading directory can be either an asset platform path or a subfolder
AZ::StringFunc::TokenizeNext(dbPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
if (!dbPath.empty())
{
//we will now remove the gameName and the native separator to get the assetId
dbPath.remove(0, gameNameIndex + gameName.length() + 1); // adding one for the native separator also
result = true;
productFileName = QString::fromUtf8(dbPath.data(), aznumeric_cast<int>(dbPath.size()));
return true;
}
else
{
//we will just remove the platform and the native separator to get the assetId
int separatorIndex = dbPath.indexOf("/");
if (separatorIndex != -1)
{
dbPath.remove(0, separatorIndex + 1); // adding one for the native separator
result = true;
}
}
productFileName = dbPath;
return result;
return false;
}
void AssetCatalog::ProcessGetRelativeProductPathFromFullSourceOrProductPathRequest(const AZStd::string& fullPath, AZStd::string& relativeProductPath)
@@ -1166,7 +1153,7 @@ namespace AssetProcessor
QString normalizedSourceOrProductPath = AssetUtilities::NormalizeFilePath(sourceOrProductPath);
QString productFileName;
int resultCode = 0;
bool resultCode = false;
QDir inputPath(normalizedSourceOrProductPath);
AZ_TracePrintf(AssetProcessor::DebugChannel, "ProcessGetRelativeProductPath: %s...\n", sourceOrProductPath.toUtf8().constData());
@@ -1175,7 +1162,7 @@ namespace AssetProcessor
{
//if the path coming in is already a relative path,we just send it back
productFileName = sourceOrProductPath;
resultCode = 1;
resultCode = true;
}
else
{
@@ -1196,7 +1183,7 @@ namespace AssetProcessor
// Now after removing the cache root,normalizedInputAssetPath can either be $Platform/$Game/xxx/yyy or something like $Platform/zzz
// and the corresponding assetId have to be either xxx/yyy or zzz
resultCode = ConvertDatabaseProductPathToProductFileName(normalizedSourceOrProductPath, productFileName);
resultCode = ConvertDatabaseProductPathToProductFilename(normalizedSourceOrProductPath.toUtf8().data(), productFileName);
}
else
{
@@ -1224,12 +1211,12 @@ namespace AssetProcessor
if (m_db->GetProductsBySourceName(relativeName, products))
{
resultCode = ConvertDatabaseProductPathToProductFileName(products[0].m_productName.c_str(), productFileName);
resultCode = ConvertDatabaseProductPathToProductFilename(products[0].m_productName, productFileName);
}
else
{
productFileName = relativeName;
resultCode = 1;
resultCode = true;
}
}
}
@@ -1242,7 +1229,6 @@ namespace AssetProcessor
}
relativeProductPath = productFileName.toUtf8().data();
}
void AssetCatalog::ProcessGetFullSourcePathFromRelativeProductPathRequest(const AZStd::string& relPath, AZStd::string& fullSourcePath)
@@ -30,7 +30,7 @@ namespace AssetProcessor
PathDependencyManager::PathDependencyManager(AZStd::shared_ptr<AssetDatabaseConnection> stateData, PlatformConfiguration* platformConfig)
: m_stateData(stateData), m_platformConfig(platformConfig)
{
}
void PathDependencyManager::SaveUnresolvedDependenciesToDatabase(AssetBuilderSDK::ProductPathDependencySet& unresolvedDependencies, const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry, const AZStd::string& platform)
@@ -55,7 +55,7 @@ namespace AssetProcessor
// other problems. This string says that something went wrong in this function.
AZStd::string("INVALID_PATH"),
dependencyType);
AZStd::string path = AssetUtilities::NormalizeFilePath(unresolvedPathDep.m_dependencyPath.c_str()).toUtf8().constData();
bool isExactDependency = IsExactDependency(path);
@@ -81,7 +81,7 @@ namespace AssetProcessor
dependencyContainer.push_back(placeholderDependency);
}
if(!m_stateData->UpdateProductDependencies(dependencyContainer))
if (!m_stateData->UpdateProductDependencies(dependencyContainer))
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Failed to save unresolved dependencies to database for product %d (%s)",
productEntry.m_productID, productEntry.m_productName.c_str());
@@ -111,8 +111,8 @@ namespace AssetProcessor
const DependencyProductMap& excludedPathDependencyIds = handleProductDependencies ? exclusionMaps.m_productPathDependencyIds : exclusionMaps.m_sourcePathDependencyIds;
const DependencyProductMap& excludedWildcardPathDependencyIds = handleProductDependencies ? exclusionMaps.m_wildcardProductPathDependencyIds : exclusionMaps.m_wildcardSourcePathDependencyIds;
// strip path of /<platform>/<project>/
AZStd::string strippedPath = handleProductDependencies ? StripPlatformAndProject(assetName) : sourceEntry.m_sourceName;
// strip asset platform from path
AZStd::string strippedPath = handleProductDependencies ? AssetUtilities::StripAssetPlatform(assetName).toUtf8().constData() : sourceEntry.m_sourceName;
SanitizeForDatabase(strippedPath);
auto unresolvedIter = excludedPathDependencyIds.find(ExcludedDependenciesSymbol + strippedPath);
@@ -137,13 +137,6 @@ namespace AssetProcessor
}
}
AZStd::string PathDependencyManager::StripPlatformAndProject(AZStd::string_view productName)
{
auto nextSlash = productName.find('/'); // platform/
nextSlash = productName.find('/', nextSlash + 1) + 1; // project/
return productName.substr(nextSlash, productName.size() - nextSlash);
}
PathDependencyManager::DependencyProductMap& PathDependencyManager::SelectMap(MapSet& mapSet, bool wildcard, AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType type)
{
const bool isSource = type == AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry::DependencyType::ProductDep_SourceFile;
@@ -193,7 +186,7 @@ namespace AssetProcessor
void PathDependencyManager::NotifyResolvedDependencies(const AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer& dependencyContainer) const
{
if(!m_dependencyResolvedCallback)
if (!m_dependencyResolvedCallback)
{
return;
}
@@ -226,7 +219,7 @@ namespace AssetProcessor
const bool isExactDependency = IsExactDependency(productDependencyDatabaseEntry.m_unresolvedPath);
AZ::s64 dependencyId = isExactDependency ? productDependencyDatabaseEntry.m_productDependencyID : AzToolsFramework::AssetDatabase::InvalidEntryId;
if(isSourceDependency && !isExactDependency && matchedPath == sourceNameWithScanFolder)
if (isSourceDependency && !isExactDependency && matchedPath == sourceNameWithScanFolder)
{
// Since we did a search for the source 2 different ways, filter one out
// Scanfolder-prefixes are only for exact dependencies
@@ -239,14 +232,14 @@ namespace AssetProcessor
AZStd::vector<AZStd::pair<DependencyProductIdInfo, bool>> exclusions; // bool = is exact dependency
GetMatchedExclusions(sourceEntry, matchedProduct, exclusions, productDependencyDatabaseEntry.m_dependencyType, exclusionMaps);
if(!exclusions.empty())
if (!exclusions.empty())
{
bool isExclusionForThisProduct = false;
bool isExclusionExact = false;
for (const auto& exclusionPair : exclusions)
{
if(exclusionPair.first.m_productId == productDependencyDatabaseEntry.m_productPK && exclusionPair.first.m_platform == productDependencyDatabaseEntry.m_platform)
if (exclusionPair.first.m_productId == productDependencyDatabaseEntry.m_productPK && exclusionPair.first.m_platform == productDependencyDatabaseEntry.m_platform)
{
isExclusionExact = exclusionPair.second;
isExclusionForThisProduct = true;
@@ -254,7 +247,7 @@ namespace AssetProcessor
}
}
if(isExclusionForThisProduct)
if (isExclusionForThisProduct)
{
if (isExactDependency && isExclusionExact)
{
@@ -314,8 +307,8 @@ namespace AssetProcessor
{
const AZStd::string& productName = productEntry.m_productName;
// strip path of /<platform>/<project>/
AZStd::string strippedPath = StripPlatformAndProject(productName);
// strip path of the <platform>/
AZStd::string strippedPath = AssetUtilities::StripAssetPlatform(productName).toUtf8().constData();
SanitizeForDatabase(strippedPath);
searchPaths.push_back(strippedPath);
@@ -347,7 +340,7 @@ namespace AssetProcessor
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer matchedProducts;
// Figure out the list of products to work with, for a source match, use all the products, otherwise just use the matched products
if(isSourceDependency)
if (isSourceDependency)
{
matchedProducts = products;
}
@@ -357,11 +350,11 @@ namespace AssetProcessor
{
const AZStd::string& productName = productEntry.m_productName;
// strip path of /<platform>/<project>/
AZStd::string strippedPath = StripPlatformAndProject(productName);
// strip path of the leading asset platform /<platform>
AZStd::string strippedPath = AssetUtilities::StripAssetPlatform(productName).toUtf8().constData();
SanitizeForDatabase(strippedPath);
if(strippedPath == matchedPath)
if (strippedPath == matchedPath)
{
matchedProducts.push_back(productEntry);
}
@@ -373,7 +366,7 @@ namespace AssetProcessor
}
// Save everything to the db
if(!m_stateData->UpdateProductDependencies(dependencyContainer))
if (!m_stateData->UpdateProductDependencies(dependencyContainer))
{
AZ_Error("PathDependencyManager", false, "Failed to update product dependencies");
}
@@ -386,7 +379,7 @@ namespace AssetProcessor
void CleanupPathDependency(AssetBuilderSDK::ProductPathDependency& pathDependency)
{
if(pathDependency.m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::SourceFile)
if (pathDependency.m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::SourceFile)
{
// Nothing to cleanup if the dependency type was already pointing at source.
return;
@@ -411,7 +404,7 @@ namespace AssetProcessor
{
const AZ::Data::ProductDependencyInfo::ProductDependencyFlags productDependencyFlags =
AZ::Data::ProductDependencyInfo::CreateFlags(AZ::Data::AssetLoadBehavior::NoLoad);
const QString gameName = AssetUtilities::ComputeGameName();
AZStd::vector<AssetBuilderSDK::ProductDependency> excludedDeps;
// Check the path dependency set and find any conflict (include and exclude the same path dependency)
@@ -458,9 +451,8 @@ namespace AssetProcessor
{
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer productInfoContainer;
QString productNameWithPlatform = QString("%1%2%3").arg(platform.c_str(), AZ_CORRECT_DATABASE_SEPARATOR_STRING, dependencyPathSearch.c_str());
QString productNameWithPlatformAndGameName = QString("%1%2%3%2%4").arg(platform.c_str(), AZ_CORRECT_DATABASE_SEPARATOR_STRING, gameName, dependencyPathSearch.c_str());
if (AzFramework::StringFunc::Equal(productNameWithPlatformAndGameName.toUtf8().data(), productName.c_str()))
if (AzFramework::StringFunc::Equal(productNameWithPlatform.toUtf8().data(), productName.c_str()))
{
AZ_Warning(AssetProcessor::ConsoleChannel, false,
"Invalid dependency: Product Asset ( %s ) has listed itself as one of its own Product Dependencies.",
@@ -471,18 +463,15 @@ namespace AssetProcessor
if (isExactDependency)
{
m_stateData->GetProductsByProductName(productNameWithPlatformAndGameName, productInfoContainer);
// Not all products will be in the game subfolder.
// Items in dev, like bootstrap.cfg, end up in just the root platform folder.
// These two checks search for products in both location.
// Example: If a path dependency was just "bootstrap.cfg" in SamplesProject on PC, this would search both
// "cache/SamplesProject/pc/bootstrap.cfg" and "cache/SamplesProject/pc/SamplesProject/bootstrap.cfg".
// Search for products in the cache platform folder
// Example: If a path dependency is "test1.asset" in SamplesProject on PC, this would search
// "SamplesProject/Cache/pc/test1.asset"
m_stateData->GetProductsByProductName(productNameWithPlatform, productInfoContainer);
}
else
{
m_stateData->GetProductsLikeProductName(productNameWithPlatformAndGameName, AzToolsFramework::AssetDatabase::AssetDatabaseConnection::LikeType::Raw, productInfoContainer);
m_stateData->GetProductsLikeProductName(productNameWithPlatform, AzToolsFramework::AssetDatabase::AssetDatabaseConnection::LikeType::Raw, productInfoContainer);
}
// See if path matches any product files
@@ -76,9 +76,6 @@ namespace AssetProcessor
/// Returns false if a path contains wildcards, true otherwise
static bool IsExactDependency(AZStd::string_view path);
/// Removes /platform/project/ from the start of a product path
static AZStd::string StripPlatformAndProject(AZStd::string_view relativeProductPath);
/// Prefixes the scanFolderId to the relativePath
AZStd::string ToScanFolderPrefixedPath(int scanFolderId, const char* relativePath) const;
@@ -67,8 +67,7 @@ namespace AssetProcessor
if (AssetUtilities::ComputeAssetRoot(assetRoot))
{
azstrcpy(m_absoluteDevFolderPath, AZ_MAX_PATH_LEN, assetRoot.absolutePath().toUtf8().constData());
QString absoluteDevGameFolderPath = assetRoot.absoluteFilePath(AssetUtilities::ComputeGameName());
azstrcpy(m_absoluteDevGameFolderPath, AZ_MAX_PATH_LEN, absoluteDevGameFolderPath.toUtf8().constData());
azstrcpy(m_absoluteDevGameFolderPath, AZ_MAX_PATH_LEN, AssetUtilities::ComputeProjectPath().toUtf8().constData());
}
using namespace AZStd::placeholders;
@@ -1067,7 +1066,7 @@ namespace AssetProcessor
AZStd::vector<AZStd::vector<AZ::u32> > newLegacySubIDs; // each product has a vector of legacy subids;
for (const AssetBuilderSDK::JobProduct& product : processedAsset.m_response.m_outputProducts)
{
// prior products, if present, will be in the form "platform/game/subfolders/productfile", convert
// prior products, if present, will be in the form "platform/subfolders/productfile", convert
// our new products to the same thing by removing the cache root
QString newProductName = product.m_productFileName.c_str();
newProductName = AssetUtilities::NormalizeFilePath(newProductName);
@@ -1093,7 +1092,8 @@ namespace AssetProcessor
//This is the legacy product guid, its only use is for backward compatibility as before the asset id's guid was created off of the relative product name.
// Right now when we query for an asset guid we first match on the source guid which is correct and secondarily match on the product guid. Eventually this will go away.
newProductName = newProductName.right(newProductName.length() - newProductName.indexOf('/') - 1); // remove PLATFORM and an extra slash
newProductName = newProductName.right(newProductName.length() - newProductName.indexOf('/') - 1); // remove GAMENAME and an extra slash
// Strip the <asset_platform> from the front of a relative product path
newProductName = AssetUtilities::StripAssetPlatform(newProductName.toUtf8().constData());
newProduct.m_legacyGuid = AZ::Uuid::CreateName(newProductName.toUtf8().constData());
//push back the new product into the new products list
@@ -1115,7 +1115,7 @@ namespace AssetProcessor
// we need to delete these product files from the disk as they no longer exist and inform everyone we did so
for (const auto& priorProduct : priorProducts)
{
// product name will be in the form "platform/game/relativeProductPath"
// product name will be in the form "platform/relativeProductPath"
// and will always already be a lowercase string, because its relative to the cache.
QString productName = priorProduct.m_productName.c_str();
@@ -1123,10 +1123,8 @@ namespace AssetProcessor
// this is case sensitive since it refers to a real location on disk.
QString fullProductPath = m_cacheRootDir.absoluteFilePath(productName);
// relative file path is gotten by removing the platform and game from the product name
QString relativeProductPath = productName;
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // remove PLATFORM and an extra slash
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // remove GAMENAME and an extra slash
// Strip the <asset_platform> from the front of a relative product path
QString relativeProductPath = AssetUtilities::StripAssetPlatform(priorProduct.m_productName);
AZ::Data::AssetId assetId(source.m_sourceGuid, priorProduct.m_subID);
@@ -1302,16 +1300,15 @@ namespace AssetProcessor
AzToolsFramework::AssetDatabase::ProductDatabaseEntry& newProduct = pair.first;
AZStd::vector<AZ::u32>& subIds = newLegacySubIDs[productIdx];
// product name will be in the form "platform/game/relativeProductPath"
// product name will be in the form "platform/relativeProductPath"
QString productName = QString::fromUtf8(newProduct.m_productName.c_str());
// the full file path is gotten by adding the product name to the cache root
QString fullProductPath = m_cacheRootDir.absoluteFilePath(productName);
// relative file path is gotten by removing the platform and game from the product name
QString relativeProductPath = productName;
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // remove PLATFORM and an extra slash
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // remove GAMENAME and an extra slash
// Strip the <asset_platform> from the front of a relative product path
QString relativeProductPath = AssetUtilities::StripAssetPlatform(productName.toUtf8().constData());
AssetNotificationMessage message(relativeProductPath.toUtf8().constData(), AssetNotificationMessage::AssetChanged, newProduct.m_assetType, processedAsset.m_entry.m_platformInfo.m_identifier.c_str());
AZ::Data::AssetId assetId(source.m_sourceGuid, newProduct.m_subID);
@@ -1572,10 +1569,10 @@ namespace AssetProcessor
// this might be interesting, but only if its a known product!
// the dictionary in statedata stores only the relative path, not the platform.
// which means right now we have, for example
// d:/game/root/Cache/SamplesProject/IOS/SamplesProject/textures/favorite.tga
// ^^^^^^^^^^^^ engine root
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cache root
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ platform root
// d:/SamplesProject/Cache/ios/textures/favorite.tga
// ^^^^^^^^^ projectroot
// ^^^^^^^^^^^^^^^^^^^^^ cache root
// ^^^^^^^^^^^^^^^^^^^^^^^^^ platform root
{
QMutexLocker locker(&m_processingJobMutex);
auto found = m_processingProductInfoList.find(fullProductFile.toUtf8().constData());
@@ -1596,7 +1593,7 @@ namespace AssetProcessor
QString relativeProductFile = m_cacheRootDir.relativeFilePath(fullProductFile);
//platform
QString platform = relativeProductFile;// currently <platform>/<gamename>/<relative_asset_path>
QString platform = relativeProductFile;// currently <platform>/<relative_asset_path>
platform = platform.left(platform.indexOf('/')); // also consume the extra slash - remove PLATFORM
//we are going to force the processor to re process the source file associated with this product
@@ -1652,11 +1649,8 @@ namespace AssetProcessor
}
}
// currently <platform>/<gamename>/<relative_asset_path>
// remove PLATFORM and GAMENAME so that we only have the relative asset path which should match the db
QString relativePath(relativeProductFile);
relativePath = relativePath.right(relativePath.length() - relativePath.indexOf('/') - 1); // also consume the extra slash - remove PLATFORM
relativePath = relativePath.right(relativePath.length() - relativePath.indexOf('/') - 1); // also consume the extra slash - remove GAMENAME
// Strip the <asset_platform> from the front of a relative product path
QString relativePath = AssetUtilities::StripAssetPlatform(relativeProductFile.toUtf8().constData());
//set the fingerprint on the job that made this product
for (auto& job : jobs)
@@ -1687,9 +1681,9 @@ namespace AssetProcessor
{
bool successfullyRemoved = true;
// delete the products.
// products have names like "pc/SamplesProject/textures/blah.dds" and do include platform roots!
// products have names like "pc/textures/blah.dds" and do include platform roots!
// this means the actual full path is something like
// [cache root] / [platform] / [product name]
// [cache root] / [platform]
for (const auto& product : products)
{
//get the source for this product
@@ -1700,9 +1694,7 @@ namespace AssetProcessor
}
QString fullProductPath = m_cacheRootDir.absoluteFilePath(product.m_productName.c_str());
QString relativeProductPath(product.m_productName.c_str());
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // also consume the extra slash - remove PLATFORM
relativeProductPath = relativeProductPath.right(relativeProductPath.length() - relativeProductPath.indexOf('/') - 1); // also consume the extra slash - remove GAMENAME
QString relativeProductPath(AssetUtilities::StripAssetPlatform(product.m_productName));
QFileInfo productFileInfo(fullProductPath);
if (productFileInfo.exists())
{
@@ -2144,15 +2136,7 @@ namespace AssetProcessor
pathRel = QString();
}
if (jobDetails.m_scanFolder->IsRoot())
{
// stuff which is found in the root continues to go to the root, rather than GAMENAME folder...
lowerCasePath += pathRel;
}
else
{
lowerCasePath += "/" + AssetUtilities::ComputeGameName() + pathRel;
}
lowerCasePath += pathRel;
lowerCasePath = lowerCasePath.toLower();
jobDetails.m_destinationPath = m_cacheRootDir.absoluteFilePath(lowerCasePath);
@@ -3471,8 +3455,16 @@ namespace AssetProcessor
AssetProcessor::BuilderConfigurationRequestBus::Broadcast(&AssetProcessor::BuilderConfigurationRequests::UpdateJobDescriptor, jobDescriptor.m_jobKey, jobDescriptor);
const AssetBuilderSDK::PlatformInfo* const infoForPlatform = m_platformConfig->GetPlatformByIdentifier(jobDescriptor.GetPlatformIdentifier().c_str());
AZ_Assert(infoForPlatform, "Somehow, a platform for a job was created in createjobs which cannot be found in the list of enabled platforms.");
if (infoForPlatform)
if (!infoForPlatform)
{
AZ_Warning(AssetProcessor::ConsoleChannel, infoForPlatform,
"CODE BUG: Builder %s emitted jobs for a platform that isn't enabled (%s). This job will be "
"discarded. Builders should check the input list of platforms and only emit jobs for platforms "
"in that list", builderInfo.m_name.c_str(), jobDescriptor.GetPlatformIdentifier().c_str());
continue;
}
{
JobDetails newJob;
newJob.m_assetBuilderDesc = builderInfo;
@@ -4614,7 +4606,7 @@ namespace AssetProcessor
QString metaDataFileName;
QDir assetRoot;
AssetUtilities::ComputeAssetRoot(assetRoot);
QString gameName = AssetUtilities::ComputeGameName();
QString projectPath = AssetUtilities::ComputeProjectPath();
QString fullPathToFile(absolutePathToFileToCheck);
if (!m_cachedMetaFilesExistMap)
@@ -4624,7 +4616,7 @@ namespace AssetProcessor
for (int idx = 0; idx < m_platformConfig->MetaDataFileTypesCount(); idx++)
{
QPair<QString, QString> metaDataFileType = m_platformConfig->GetMetaDataFileTypeAt(idx);
QString fullMetaPath = assetRoot.filePath(gameName + "/" + metaDataFileType.first);
QString fullMetaPath = QDir(projectPath).filePath(metaDataFileType.first);
if (QFileInfo::exists(fullMetaPath))
{
m_metaFilesWhichActuallyExistOnDisk.insert(metaDataFileType.first);
@@ -4644,7 +4636,7 @@ namespace AssetProcessor
if (m_metaFilesWhichActuallyExistOnDisk.find(metaDataFileType.first) != m_metaFilesWhichActuallyExistOnDisk.end())
{
QString fullMetaPath = assetRoot.filePath(gameName + "/" + metaDataFileType.first);
QString fullMetaPath = QDir(projectPath).filePath(metaDataFileType.first);
metaDataFileName = fullMetaPath;
}
else
@@ -21,6 +21,9 @@
#include "native/utilities/assetUtils.h"
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/IO/LocalFileIO.h>
using namespace AZ::IO;
@@ -114,20 +117,20 @@ void FileServer::ConnectionAdded(unsigned int connId, Connection* connection)
{
projectCacheRoot = QDir(projectCacheRoot.absoluteFilePath(assetPlatform));
}
fileIO->SetAlias("@root@", projectCacheRoot.absolutePath().toUtf8().data());
const char* projectCachePath = projectCacheRoot.absolutePath().toUtf8().data();
fileIO->SetAlias("@assets@", projectCachePath);
fileIO->SetAlias("@root@", projectCachePath);
QString userDir = projectCacheRoot.absoluteFilePath("user");
userDir = QDir::toNativeSeparators(userDir);
fileIO->SetAlias("@user@", userDir.toUtf8().data());
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
AZ::IO::Path projectUserPath;
settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath);
fileIO->SetAlias("@user@", projectUserPath.c_str());
QString logDir = QDir(userDir).absoluteFilePath("log");
logDir = QDir::toNativeSeparators(logDir);
fileIO->SetAlias("@log@", logDir.toUtf8().data());
AZ::IO::Path logUserPath = projectUserPath / "log";
fileIO->SetAlias("@log@", logUserPath.c_str());
}
QString gameName = AssetUtilities::ComputeGameName();
QString gameDir = projectCacheRoot.absoluteFilePath(gameName);
gameDir = QDir::toNativeSeparators(gameDir);
fileIO->SetAlias("@assets@", gameDir.toUtf8().data());
// note that the cache folder is auto-created only upon first use of VFS.
}
@@ -145,13 +148,20 @@ void FileServer::EnsureCacheFolderExists(int connId)
{
return;
}
if (fileIO->GetAlias("@cache@"))
if (fileIO->GetAlias("@usercache@"))
{
// already created.
return;
}
QString cacheDir = QDir(fileIO->GetAlias("@user@")).absoluteFilePath("cache");
AZ::IO::FixedMaxPath cacheUserPath;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry->Get(cacheUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
{
cacheUserPath /= "Cache";
}
auto cacheDir = QString::fromUtf8(cacheUserPath.c_str(), aznumeric_cast<int>(cacheUserPath.Native().size()));
cacheDir = QDir::toNativeSeparators(cacheDir);
// the Cache-dir is special in that we don't allow sharing of cache dirs for multiple running
// apps of the same platform at the same time.
@@ -207,7 +217,7 @@ void FileServer::EnsureCacheFolderExists(int connId)
}
#endif
fileIO->SetAlias("@cache@", cacheDir.toUtf8().data());
fileIO->SetAlias("@usercache@", cacheDir.toUtf8().data());
}
void FileServer::ConnectionRemoved(unsigned int connId)
@@ -944,7 +954,7 @@ void FileServer::ProcessFileTreeRequest(unsigned int connId, unsigned int, unsig
}
auto fileIO = m_fileIOs[connId];
FileTreeResponse::FileList files;
FileTreeResponse::FolderList folders;
@@ -954,10 +964,10 @@ void FileServer::ProcessFileTreeRequest(unsigned int connId, unsigned int, unsig
folders.push_back("@assets@");
untestedFolders.push_back("@assets@");
}
if (fileIO->IsDirectory("@cache@"))
if (fileIO->IsDirectory("@usercache@"))
{
folders.push_back("@cache@");
untestedFolders.push_back("@cache@");
folders.push_back("@usercache@");
untestedFolders.push_back("@usercache@");
}
if (fileIO->IsDirectory("@user@"))
{
@@ -976,7 +986,7 @@ void FileServer::ProcessFileTreeRequest(unsigned int connId, unsigned int, unsig
}
AZ::IO::Result res = ResultCode::Success;
while (untestedFolders.size() && res == ResultCode::Success)
{
AZ::OSString folderName = untestedFolders.back();
@@ -1006,9 +1016,9 @@ void FileServer::ProcessFileTreeRequest(unsigned int connId, unsigned int, unsig
}
uint32_t resultCode = static_cast<uint32_t>(res.GetResultCode());
FileTreeResponse response(resultCode, files, folders);
Send(connId, serial, response);
}
@@ -14,9 +14,11 @@
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <native/InternalBuilders/SettingsRegistryBuilder.h>
#include <native/utilities/PlatformConfiguration.h>
namespace AssetProcessor
{
@@ -210,6 +212,8 @@ namespace AssetProcessor
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
AZStd::vector<AZStd::string> excludes = ReadExcludesFromRegistry();
// Exclude the AssetProcessor settings from the game regsitry
excludes.emplace_back(AssetProcessor::AssetProcessorSettingsKey);
AZStd::vector<char> scratchBuffer;
scratchBuffer.reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer;
@@ -228,19 +232,16 @@ namespace AssetProcessor
};
// Add the project specific specializations
if (auto settingsRegistry = AZ::Interface<AZ::SettingsRegistryInterface>::Get(); settingsRegistry)
auto projectName = AZ::Utils::GetProjectName();
if (!projectName.empty())
{
auto projectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/sys_game_folder", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
if (AZ::SettingsRegistryInterface::FixedValueString projectName; settingsRegistry->Get(projectName, projectKey))
for (AZ::SettingsRegistryInterface::Specializations& specialization : specializations)
{
for (AZ::SettingsRegistryInterface::Specializations& specialization : specializations)
{
specialization.Append(projectName);
// The Game Launcher normally has a build target name of <ProjectName>Launcher
// Add that as a specialization to pick up the gem dependencies files that are specialized
// on a the Game Launcher target if the asset platform isn't "server"
specialization.Append(projectName + launcherType);
}
specialization.Append(projectName);
// The Game Launcher normally has a build target name of <ProjectName>Launcher
// Add that as a specialization to pick up the gem dependencies files that are specialized
// on a the Game Launcher target if the asset platform isn't "server"
specialization.Append(projectName + launcherType);
}
}
@@ -255,8 +256,9 @@ namespace AssetProcessor
for (AZStd::string_view platform : platformCodes)
{
AZ::u32 productSubID = static_cast<AZ::u32>(AZStd::hash<AZStd::string_view>{}(platform)); // Deliberately ignoring half the bits.
for (size_t i = 0; i < AZ_ARRAY_SIZE(specializations); ++i)
for (size_t i = 0; i < AZStd::size(specializations); ++i)
{
const AZ::SettingsRegistryInterface::Specializations& specialization = specializations[i];
if (m_isShuttingDown)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
@@ -269,12 +271,12 @@ namespace AssetProcessor
if (auto settingsRegistry = AZ::Interface<AZ::SettingsRegistryInterface>::Get(); settingsRegistry != nullptr)
{
AZStd::array settingsToCopy{
AZStd::string::format("%s/sys_game_folder", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey),
AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey),
AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_BinaryFolder},
AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder},
AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_SourceGameFolder},
AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath},
AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder},
AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder},
AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_CacheGameFolder}
};
for (const auto& settingsKey : settingsToCopy)
@@ -288,14 +290,14 @@ namespace AssetProcessor
}
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(registry);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, platform, specializations[i], &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, platform, specializations[i], &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, platform, specializations[i], &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, platform, specialization, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, platform, specialization, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, platform, specialization, &scratchBuffer);
// Merge the Developer User settings registry only in non-release builds
if (!specializations->Contains("release"))
if (!specialization.Contains("release"))
{
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_DevRegistry(registry, platform, specializations[i], &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_UserRegistry(registry, platform, specialization, &scratchBuffer);
}
AZ::ComponentApplicationBus::Broadcast([&registry](AZ::ComponentApplicationRequests* appRequests)
@@ -314,7 +316,7 @@ namespace AssetProcessor
return;
}
outputPath += specializations[i].GetSpecialization(0); // Append configuration
outputPath += specialization.GetSpecialization(0); // Append configuration
outputPath += '.';
outputPath += platform;
outputPath += ".setreg";
@@ -506,7 +506,7 @@ void ConnectionManager::AddAddressToAllowedList(QString address)
UpdateAllowedListFromBootStrap();
while (m_allowedListAddresses.removeOne(address)) {}
m_allowedListAddresses << address;
AssetUtilities::WriteAllowedlistToBootstrap(m_allowedListAddresses);
AssetUtilities::WriteAllowedlistToSettingsRegistry(m_allowedListAddresses);
Q_EMIT SyncAllowedListAndRejectedList(m_allowedListAddresses, m_rejectedAddresses);
}
@@ -514,7 +514,7 @@ void ConnectionManager::RemoveAddressFromAllowedList(QString address)
{
UpdateAllowedListFromBootStrap();
while (m_allowedListAddresses.removeOne(address)) {}
AssetUtilities::WriteAllowedlistToBootstrap(m_allowedListAddresses);
AssetUtilities::WriteAllowedlistToSettingsRegistry(m_allowedListAddresses);
Q_EMIT SyncAllowedListAndRejectedList(m_allowedListAddresses, m_rejectedAddresses);
}
@@ -235,9 +235,9 @@ bool ConnectionWorker::NegotiateDirect(bool initiate)
using namespace AzFramework::AssetSystem;
AZStd::string azBranchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, azBranchToken);
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, azBranchToken);
QString branchToken(azBranchToken.c_str());
QString projectName = AssetUtilities::ComputeGameName();
QString projectName = AssetUtilities::ComputeProjectName();
NegotiationMessage myInfo;
@@ -316,7 +316,7 @@ bool ConnectionWorker::NegotiateDirect(bool initiate)
QString incomingBranchToken(engineInfo.m_negotiationInfoMap[NegotiationInfo_BranchIndentifier].c_str());
if (QString::compare(incomingBranchToken, branchToken, Qt::CaseInsensitive) != 0)
{
//if we are here it means that the editor/game which is negotiating is running on a different branch
// if we are here it means that the editor/game which is negotiating is running on a different branch
// note that it could have just read nothing from the engine or a repeat packet, in that case, discard it silently and try again.
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "ConnectionWorker::NegotiateDirect: branch token mismatch from %s - %p - %s vs %s\n", engineInfo.m_identifier.c_str(), this, incomingBranchToken.toUtf8().data(), branchToken.toUtf8().data());
AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBus::Events::NegotiationFailed);
@@ -325,7 +325,7 @@ bool ConnectionWorker::NegotiateDirect(bool initiate)
}
QString incomingProjectName(engineInfo.m_negotiationInfoMap[NegotiationInfo_ProjectName].c_str());
// Do a case-insensitive compare for the project name because some (case-sensitive) platforms will blower-case the incoming project name
// Do a case-insensitive compare for the project name because some (case-sensitive) platforms will lower-case the incoming project name
if(QString::compare(incomingProjectName, projectName, Qt::CaseInsensitive) != 0)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "ConnectionWorker::NegotiateDirect: project name mismatch from %s - %p - %s vs %s\n", engineInfo.m_identifier.c_str(), this, incomingProjectName.toUtf8().constData(), projectName.toUtf8().constData());
@@ -24,8 +24,8 @@
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Application/Application.h>
#include <AzToolsFramework/Process/ProcessCommunicator.h>
#include <AzToolsFramework/Process/ProcessWatcher.h>
#include <AzFramework/Process/ProcessCommunicator.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
@@ -164,7 +164,7 @@ namespace AssetProcessor
// build the command line:
QString commandString = NativeLegacyRCCompiler::BuildCommand(inputFile, watchFolder, platformIdentifier, params, dest);
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
// while it might be tempting to set the executable in processLaunchInfo.m_processExecutableString, it turns out that RC.EXE
// won't work if you do that because it assumes the first command line param is the exe name, which is not the case if you do it that way...
@@ -173,12 +173,12 @@ namespace AssetProcessor
processLaunchInfo.m_commandlineParameters = QString(formatter).arg(m_rcExecutableFullPath).arg(commandString).toUtf8().data();
processLaunchInfo.m_showWindow = false;
processLaunchInfo.m_workingDirectory = m_systemRoot.absolutePath().toUtf8().data();
processLaunchInfo.m_processPriority = AzToolsFramework::PROCESSPRIORITY_IDLE;
processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_IDLE;
AZ_TracePrintf("RC Builder", "Executing RC.EXE: '%s' ...\n", processLaunchInfo.m_commandlineParameters.c_str());
AZ_TracePrintf("Rc Builder", "Executing RC.EXE with working directory: '%s' ...\n", processLaunchInfo.m_workingDirectory.c_str());
AzToolsFramework::ProcessWatcher* watcher = AzToolsFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzToolsFramework::COMMUNICATOR_TYPE_STDINOUT);
AzFramework::ProcessWatcher* watcher = AzFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT);
if (!watcher)
{
@@ -256,18 +256,16 @@ namespace AssetProcessor
QString cmdLine;
if (!dest.isEmpty())
{
QString gameName = AssetUtilities::ComputeGameName();
QString projectName = AssetUtilities::ComputeProjectName();
QString projectPath = AssetUtilities::ComputeProjectPath();
int portNumber = 0;
ApplicationServerBus::BroadcastResult(portNumber, &ApplicationServerBus::Events::GetServerListeningPort);
QDir assetRoot;
AssetUtilities::ComputeAssetRoot(assetRoot);
QString gameRoot = assetRoot.absoluteFilePath(AssetUtilities::ComputeGameName());
AZStd::string appBranchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, appBranchToken);
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, appBranchToken);
cmdLine = QString("\"%1\" /p=%2 %3 /unattended=true /gameroot=\"%4\" /watchfolder=\"%6\" /targetroot=\"%5\" /logprefix=\"%5/\" /port=%7 /gamesubdirectory=\"%8\" /branchtoken=\"%9\"");
cmdLine = cmdLine.arg(inputFile, platformIdentifier, params, gameRoot, dest, watchFolder).arg(portNumber).arg(gameName).arg(appBranchToken.c_str());
cmdLine = cmdLine.arg(inputFile, platformIdentifier, params, projectPath, dest, watchFolder).arg(portNumber).arg(projectName).arg(appBranchToken.c_str());
}
else
{
@@ -12,6 +12,7 @@
#include <AzCore/base.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <QCoreApplication>
@@ -44,7 +45,7 @@ namespace AssetProcessor
public:
AssetCatalogForUnitTest(QObject* parent, AssetProcessor::PlatformConfiguration* platformConfiguration)
: AssetCatalog(parent, platformConfiguration) {}
// prevent automatic save on shutdown, no point in doing that in unit test mode, just wastes time.
virtual ~AssetCatalogForUnitTest()
{
@@ -68,7 +69,7 @@ namespace AssetProcessor
: public ScopedAllocatorSetupFixture
{
protected:
// store all data we create here so that it can be destroyed on shutdown before we remove allocators
struct DataMembers
{
@@ -89,7 +90,7 @@ namespace AssetProcessor
int argc = 0;
DataMembers() : coreApp(argc, nullptr)
{
}
};
@@ -108,23 +109,28 @@ namespace AssetProcessor
m_systemEntity = m_app->Create(desc);
m_data = azcreate(DataMembers, ());
AssetUtilities::ComputeAssetRoot(m_data->m_priorAssetRoot);
AssetUtilities::ResetAssetRoot();
// the canonicalization of the path here is to get around the fact that on some platforms
// the "temporary" folder location could be junctioned into some other folder and getting "QDir::current()"
// and other similar functions may actually return a different string but still be referring to the same folder
// and other similar functions may actually return a different string but still be referring to the same folder
m_data->m_temporarySourceDir = QDir(m_data->m_temporaryDir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(m_data->m_temporarySourceDir.canonicalPath());
m_data->m_temporarySourceDir = QDir(canonicalTempDirPath);
m_data->m_scopedDir.Setup(m_data->m_temporarySourceDir.path());
m_data->m_gameName = AssetUtilities::ComputeGameName("SamplesProject"); // uses the above file.
m_data->m_gameName = AssetUtilities::ComputeProjectName("SamplesProject"); // uses the above file.
AssetUtilities::ResetAssetRoot();
QDir newRoot; // throwaway dummy var - we just want to invoke the below function
AssetUtilities::ComputeAssetRoot(newRoot, &m_data->m_temporarySourceDir);
auto settingsRegistry = AZ::SettingsRegistry::Get();
auto cacheRootKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_cache_path";
settingsRegistry->Set(cacheRootKey, m_data->m_temporarySourceDir.absoluteFilePath("Cache").toUtf8().constData());
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
AssetUtilities::ComputeProjectCacheRoot(m_data->m_cacheRootDir);
QString normalizedCacheRoot = AssetUtilities::NormalizeDirectoryPath(m_data->m_cacheRootDir.absolutePath());
m_data->m_cacheRootDir = QDir(normalizedCacheRoot);
@@ -181,7 +187,7 @@ namespace AssetProcessor
m_data->m_databaseLocationListener.BusConnect();
m_data->m_dbConn.OpenDatabase();
BuildConfig(m_data->m_temporarySourceDir, &(m_data->m_dbConn), m_data->m_config);
m_data->m_assetCatalog.reset(new AssetCatalogForUnitTest(nullptr, &(m_data->m_config)));
}
@@ -331,7 +337,7 @@ namespace AssetProcessor
{
bool relPathfound = false;
AZStd::string relPath;
AZStd::string fullPath(fileToCheck.toStdString().c_str());
AZStd::string fullPath(fileToCheck.toUtf8().constData());
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(relPathfound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetRelativeProductPathFromFullSourceOrProductPath, fullPath, relPath);
@@ -356,7 +362,7 @@ namespace AssetProcessor
{
bool fullPathfound = false;
AZStd::string fullPath;
AZStd::string relPath(fileToCheck.toStdString().c_str());
AZStd::string relPath(fileToCheck.toUtf8().constData());
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathfound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetFullSourcePathFromRelativeProductPath, relPath, fullPath);
@@ -419,7 +425,7 @@ namespace AssetProcessor
ASSERT_EQ(m_data->m_absorber.m_numAssertsAbsorbed, 2);
// reset the absorber before we leave this assert-test, so that it doesn't cause failure of the test itself
m_data->m_absorber.Clear();
ASSERT_TRUE(TestGetRelativeProductPath("", false, { "" }));
ASSERT_TRUE(TestGetFullSourcePath("", m_data->m_temporarySourceDir, false, ""));
}
@@ -444,75 +450,76 @@ namespace AssetProcessor
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_WithGameName_ReturnsFileInGameFolder)
{
// feed it a product path with gamename and a platform name, returns it without gamename
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName + "/aaa/basefile.txt");
// feed it a product path with a platform name, returns it
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/aaa/basefile.txt");
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_WithoutGameName_ReturnsFileInRootFolder)
{
// feed it a product path without gamename, just the file name since its supposed to be a root file
// feed it a product path, just the file name since its supposed to be a root file
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/basefile.txt");
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "basefile.txt" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_BadCasingInPlatform_ReturnsRelativePath)
{
// feed it a product path with gamename but poor casing (test 1: the pc platform is not matching case)
QString fileToCheck = m_data->m_cacheRootDir.filePath("Pc/" + m_data->m_gameName + "/aaa/basefile.txt");
// feed it a product path but with poor casing (test 1: the pc platform is not matching case)
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("Pc") + "/aaa/basefile.txt");
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_BadCasingInGameName_ReturnsRelativePath)
{
//feed it a product path with gamename but poor casing (test 2: the gameName is not matching case)
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName.toUpper() + "/aaa/basefile.txt");
//feed it a product path but with poor casing (test 2: the gameName is not matching case)
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/aaa/basefile.txt");
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_FolderName_ReturnsFolderNameOnly)
{
// feed it a product path that resolves to a directory name instead of a file. GameName is 'incorrect' (upper)
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName.toUpper() + "/aaa");
// feed it a product path that resolves to a directory name instead of a file.
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/aaa");
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_FolderNameExtraSlash_ReturnsFolderNameOnlyNoExtraSlash)
{
// make sure it doesn't keep any trailing slashes. GameName is 'incorrect' (upper)
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName.toUpper() + "/aaa/"); // extra trailing slash
// make sure it doesn't keep any trailing slashes.
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/aaa/"); // extra trailing slash
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa" })); // the API should never result in a trailing slash
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_FolderNameExtraWrongWaySlash_ReturnsFolderNameOnlyNoExtraWrongSlash)
{
// make sure it doesn't keep any trailing slashes. GameName is 'incorrect' (upper)
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName.toUpper() + "/aaa\\"); // extra trailing wrongway slash
// make sure it doesn't keep any trailing slashes.
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/aaa\\"); // extra trailing wrongway slash
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa" })); // the API should never result in a trailing slash
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_RelativeDirectoryNameWhichDoesNotExist_ReturnsFolderNameOnly)
{
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName.toLower() + "/nonexistantfolder"); // extra trailing wrongway slash
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "nonexistantfolder" }));
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/nonexistantfolder"); // extra trailing wrongway slash
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "nonexistantfolder" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_RelativeDirectoryNameWhichDoesNotExistWithExtraSlash_ReturnsFolderNameOnly)
{
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName.toLower() + "/nonexistantfolder/"); // extra trailing wrongway slash
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/nonexistantfolder/"); // extra trailing wrongway slash
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "nonexistantfolder" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_RelativeDirectoryNameWhichDoesNotExistWithExtraWrongWaySlash_ReturnsFolderNameOnly)
{
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc\\" + m_data->m_gameName.toLower() + "\\nonexistantfolder\\"); // extra trailing wrongway slash
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "\\nonexistantfolder\\"); // extra trailing wrongway slash
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "nonexistantfolder" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_RelativePathToSourceFile_ReturnsProductFilePath)
{
QString fileToCheck = m_data->m_temporarySourceDir.absoluteFilePath("subfolder3/BaseFile.txt");
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "basefilez.arc2", "basefileaz.azm2", "basefile.arc2", "basefile.azm2" }));
ASSERT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "basefilez.arc2", "basefileaz.azm2",
"basefile.arc2", "basefile.azm2" }));
}
TEST_F(AssetCatalogTestWithProducts, GetRelativeProductPathFromFullSourceOrProductPath_RelativePathToSourceFile_BadCasing_ReturnsProductFilePath)
@@ -533,9 +540,9 @@ namespace AssetProcessor
// ----- Test the ProcessGetFullAssetPath function on product files
{
QStringList pcouts;
pcouts.push_back(m_data->m_cacheRootDir.filePath(QString("pc/") + m_data->m_gameName + "/subfolder3/randomfileoutput.random"));
pcouts.push_back(m_data->m_cacheRootDir.filePath(QString("pc/") + m_data->m_gameName + "/subfolder3/randomfileoutput.random1"));
pcouts.push_back(m_data->m_cacheRootDir.filePath(QString("pc/") + m_data->m_gameName + "/subfolder3/randomfileoutput.random2"));
pcouts.push_back(m_data->m_cacheRootDir.filePath(QString("pc") + "/subfolder3/randomfileoutput.random"));
pcouts.push_back(m_data->m_cacheRootDir.filePath(QString("pc") + "/subfolder3/randomfileoutput.random1"));
pcouts.push_back(m_data->m_cacheRootDir.filePath(QString("pc") + "/subfolder3/randomfileoutput.random2"));
AZ::s64 jobId;
ASSERT_TRUE(AddSourceAndJob("subfolder3", "somerandomfile.random", &(m_data->m_dbConn), jobId));
@@ -591,32 +598,32 @@ namespace AssetProcessor
QString fileToCheck = "@somerandomalias@/subfolder3/randomfileoutput.random1";
EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, m_data->m_temporarySourceDir, true, "subfolder3/somerandomfile.random"));
}
TEST_F(AssetCatalogTest_GetFullSourcePath, InvalidAliasMissingSeperator_ReturnsAbsolutePathToSource)
{
//feed it a path with some random alias and asset id but no separator
QString fileToCheck = "@somerandomalias@subfolder3/randomfileoutput.random1";
EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, m_data->m_temporarySourceDir, true, "subfolder3/somerandomfile.random"));
}
TEST_F(AssetCatalogTest_GetFullSourcePath, InvalidSourcePathContainingCacheAlias_ReturnsAbsolutePathToSource)
{
//feed it a path with alias and input name
QString fileToCheck = "@assets@/somerandomfile.random";
EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, m_data->m_temporarySourceDir, true, "subfolder3/somerandomfile.random"));
}
TEST_F(AssetCatalogTest_GetFullSourcePath, AbsolutePathToCache_ReturnsAbsolutePathToSource)
{
//feed it an absolute path with cacheroot
QString fileToCheck = m_data->m_cacheRootDir.filePath("pc/" + m_data->m_gameName.toLower() + "/subfolder3/randomfileoutput.random1");
QString fileToCheck = m_data->m_cacheRootDir.filePath(QString("pc") + "/subfolder3/randomfileoutput.random1");
EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, m_data->m_temporarySourceDir, true, "subfolder3/somerandomfile.random"));
}
TEST_F(AssetCatalogTest_GetFullSourcePath, ProductNameIncludingPlatformAndGameName_ReturnsAbsolutePathToSource)
{
//feed it a productName directly
QString fileToCheck = "pc/" + m_data->m_gameName + "/subfolder3/randomfileoutput.random1";
QString fileToCheck = QString("pc") + "/subfolder3/randomfileoutput.random1";
EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, m_data->m_temporarySourceDir, true, "subfolder3/somerandomfile.random"));
}
@@ -624,7 +631,7 @@ namespace AssetProcessor
: public AssetCatalogTest
{
public:
struct AssetCatalogTest_AssetInfo_DataMembers
{
AssetId m_assetA = AssetId(Uuid::CreateRandom(), 0);
@@ -650,7 +657,7 @@ namespace AssetProcessor
AzFramework::StringFunc::Path::Join(m_customDataMembers->m_subfolder1AbsolutePath.c_str(), m_customDataMembers->m_assetASourceRelPath.c_str(), m_customDataMembers->m_assetAFullPath);
CreateDummyFile(QString::fromUtf8(m_customDataMembers->m_assetAFullPath.c_str()), m_customDataMembers->m_assetTestString.c_str());
AzFramework::StringFunc::Path::Join(m_data->m_cacheRootDir.absolutePath().toUtf8().constData(), m_customDataMembers->m_assetAProductRelPath.c_str(), m_customDataMembers->m_assetAProductFullPath);
CreateDummyFile(QString::fromUtf8(m_customDataMembers->m_assetAProductFullPath.c_str()), m_customDataMembers->m_productTestString.c_str());
}
@@ -724,7 +731,7 @@ namespace AssetProcessor
return true;
};
void TearDown() override
void TearDown() override
{
azdestroy(m_customDataMembers);
AssetCatalogTest::TearDown();
@@ -848,7 +855,7 @@ namespace AssetProcessor
using namespace AzFramework::AssetSystem;
PlatformConfiguration config;
config.EnablePlatform(AssetBuilderSDK::PlatformInfo("pc", { "test" }));
{
@@ -1060,7 +1067,7 @@ namespace AssetProcessor
productDependency.m_dependencySourceGuid = m_sourceFileWithDifferentProductsPerPlatform;
productDependency.m_unresolvedPath = AZStd::string();
QString platformGameDir = QDir(cacheRoot.absoluteFilePath(productDependency.m_platform.c_str())).filePath(AssetUtilities::ComputeGameName().toLower());
QString platformGameDir = QDir(cacheRoot.absoluteFilePath(productDependency.m_platform.c_str())).filePath(AssetUtilities::ComputeProjectName().toLower());
QString assetCatalogFile = QDir(platformGameDir).filePath("assetcatalog.xml");
QFileInfo fileInfo(assetCatalogFile);
@@ -1071,7 +1078,7 @@ namespace AssetProcessor
// process all events
QCoreApplication::processEvents(QEventLoop::AllEvents);
// This ensures that no save catalog event was queued when we resolve dependency
// This ensures that no save catalog event was queued when we resolve dependency
EXPECT_FALSE(fileInfo.exists());
}
@@ -106,7 +106,7 @@ namespace AssetProcessorMessagesTests
m_batchApplicationManager->BeforeRun();
// Override Game Name to be "SamplesProject"
AssetUtilities::ComputeGameName("SamplesProject", true);
AssetUtilities::ComputeProjectName("SamplesProject", true);
m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration();
m_batchApplicationManager->InitAssetProcessorManager();
@@ -144,7 +144,7 @@ namespace AssetProcessorMessagesTests
RunNetworkRequest([]()
{
AZStd::string appBranchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, appBranchToken);
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, appBranchToken);
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
connectionSettings.m_assetProcessorIp = "127.0.0.1";
@@ -181,9 +181,11 @@ namespace UnitTests
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("dev/testfolder/file.foo")));
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("dev/testfolder/File.bar")));
AZ::IO::FileIOBase::SetInstance(nullptr); // The API requires the old instance to be destroyed first
AZ::IO::FileIOBase::SetInstance(new AZ::IO::LocalFileIO());
if (AZ::IO::FileIOBase::GetInstance() == nullptr)
{
m_localFileIo = AZStd::make_unique<AZ::IO::LocalFileIO>();
AZ::IO::FileIOBase::SetInstance(m_localFileIo.get());
}
m_data->m_reporter = AZStd::make_unique<SourceFileRelocator>(m_data->m_connection, &m_data->m_platformConfig);
@@ -204,6 +206,12 @@ namespace UnitTests
void TearDown() override
{
if (AZ::IO::FileIOBase::GetInstance() == m_localFileIo.get())
{
AZ::IO::FileIOBase::SetInstance(nullptr);
}
m_localFileIo.reset();
AZ::JobContext::SetGlobalContext(nullptr);
delete m_data->m_jobContext;
delete m_data->m_jobManager;
@@ -405,6 +413,7 @@ namespace UnitTests
// we store the above data in a unique_ptr so that its memory can be cleared during TearDown() in one call, before we destroy the memory
// allocator, reducing the chance of missing or forgetting to destroy one in the future.
AZStd::unique_ptr<StaticData> m_data;
AZStd::unique_ptr<AZ::IO::LocalFileIO> m_localFileIo;
};
TEST_F(SourceFileRelocatorTest, GetSources_SingleFile_Succeeds)
@@ -12,6 +12,7 @@
#include "AssetProcessorManagerTest.h"
#include "native/AssetManager/PathDependencyManager.h"
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzToolsFramework/ToolsFileUtils/ToolsFileUtils.h>
#include <AzTest/AzTest.h>
@@ -41,7 +42,7 @@ public:
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDifferentTypes_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse_BasicTest);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_BeforeComputingDirtiness_AllDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty);
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty);
@@ -206,6 +207,12 @@ void AssetProcessorManagerTest::SetUp()
m_scopeDir->Setup(m_tempDir.path());
QDir tempPath(m_tempDir.path());
auto registry = AZ::SettingsRegistry::Get();
auto cacheRootKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_cache_path";
registry->Set(cacheRootKey, tempPath.absoluteFilePath("Cache").toUtf8().constData());
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
m_data->m_databaseLocationListener.BusConnect();
// in other unit tests we may open the database called ":memory:" to use an in-memory database instead of one on disk.
@@ -220,7 +227,7 @@ void AssetProcessorManagerTest::SetUp()
SetArgReferee<0>(m_data->m_databaseLocation),
Return(true)));
m_gameName = AssetUtilities::ComputeGameName("SamplesProject", true);
m_gameName = AssetUtilities::ComputeProjectName("SamplesProject", true);
AssetUtilities::ResetAssetRoot();
QDir newRoot;
@@ -230,7 +237,6 @@ void AssetProcessorManagerTest::SetUp()
AssetUtilities::ComputeProjectCacheRoot(cacheRoot);
QString normalizedCacheRoot = AssetUtilities::NormalizeDirectoryPath(cacheRoot.absolutePath());
QString normalizedDirPathCheck = AssetUtilities::NormalizeDirectoryPath(QDir::current().absoluteFilePath("Cache/" + m_gameName));
m_normalizedCacheRootDir.setPath(normalizedCacheRoot);
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder1/assetProcessorManagerTest.txt"));
@@ -357,7 +363,7 @@ TEST_F(AssetProcessorManagerTest, UnitTestForGettingJobInfoBySourceUUIDSuccess)
TEST_F(AssetProcessorManagerTest, WarningsAndErrorsReported_SuccessfullySavedToDatabase)
{
// This tests the JobDiagnosticTracker: Warnings/errors reported to it should be recorded in the database when AssetProcessed is fired and able to be retrieved when querying job status
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
@@ -471,7 +477,7 @@ TEST_F(AssetProcessorManagerTest, UnitTestForOutPutPrefix)
}
});
m_isIdling = false;
// tell the APM about the files:
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, tempPath.absoluteFilePath("subfolder2/test.txt")));
@@ -626,10 +632,10 @@ TEST_F(AssetProcessorManagerTest, BuilderDirtiness_EmptyDatabase_AllDirty)
MockBuilderResponder mockBuilderResponder;
mockBuilderResponder.BusConnect();
mockBuilderResponder.AddBuilder("builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint1");
mockBuilderResponder.AddBuilder("builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint2");
m_assetProcessorManager->ComputeBuilderDirty();
EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
@@ -666,7 +672,7 @@ TEST_F(AssetProcessorManagerTest, BuilderDirtiness_SameAsLastTime_NoneDirty)
mockBuilderResponder.BusDisconnect();
}
// when a new builder appears, the new builder should be dirty,
// when a new builder appears, the new builder should be dirty,
TEST_F(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirty)
{
using namespace AzToolsFramework::AssetSystem;
@@ -677,13 +683,13 @@ TEST_F(AssetProcessorManagerTest, BuilderDirtiness_MoreThanLastTime_NewOneIsDirt
mockBuilderResponder.BusConnect();
mockBuilderResponder.AddBuilder("builder1", { AssetBuilderSDK::AssetBuilderPattern("*.egg", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint1");
m_assetProcessorManager->ComputeBuilderDirty();
mockBuilderResponder.AddBuilder("builder2", { AssetBuilderSDK::AssetBuilderPattern("*.foo", AssetBuilderPattern::Wildcard) }, AZ::Uuid::CreateRandom(), 1, "fingerprint2");
m_assetProcessorManager->ComputeBuilderDirty();
// one new builder should have been dirty:
EXPECT_TRUE(m_assetProcessorManager->m_anyBuilderChange);
EXPECT_TRUE(m_assetProcessorManager->m_buildersAddedOrRemoved);
@@ -892,7 +898,7 @@ TEST_F(AssetProcessorManagerTest, BuilderDirtiness_NewAnalysisFingerprint_IsNotA
TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTest)
{
using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
// A depends on B, which depends on both C and D
QDir tempPath(m_tempDir.path());
@@ -941,7 +947,7 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_BasicTe
dependencies.clear();
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("b.txt", dependencies, SourceFileDependencyEntry::DEP_SourceToSource, false);
EXPECT_EQ(dependencies.size(), 3); // b depends on c, and d
EXPECT_EQ(dependencies.size(), 3); // b depends on c, and d
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
@@ -961,7 +967,7 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDif
{
// test to make sure that different TYPES of dependencies work as expected.
using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
QDir tempPath(m_tempDir.path());
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("subfolder1/a.txt"), QString("tempdata\n"));
@@ -998,7 +1004,7 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDif
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("a.txt", dependencies, SourceFileDependencyEntry::DEP_SourceToSource, false);
// note that a depends on b, c, and d - with the latter two being indirect.
// however, since b's dependency on C is via JOB, and we're asking for SOURCE only, we should not see C.
EXPECT_EQ(dependencies.size(), 3);
EXPECT_EQ(dependencies.size(), 3);
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
@@ -1007,7 +1013,7 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_WithDif
dependencies.clear();
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("b.txt", dependencies, SourceFileDependencyEntry::DEP_JobToJob, false);
// b depends on c, and d - but we're asking for job dependencies only, so we should not get anything except C and B
EXPECT_EQ(dependencies.size(), 2);
EXPECT_EQ(dependencies.size(), 2);
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
@@ -1063,13 +1069,13 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse
AssetProcessor::SourceFilesForFingerprintingContainer dependencies;
// sanity: what Depends on a? the only result should be a itself.
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("a.txt", dependencies, SourceFileDependencyEntry::DEP_SourceToSource, true /*reverse*/);
EXPECT_EQ(dependencies.size(), 1);
EXPECT_EQ(dependencies.size(), 1);
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
dependencies.clear();
// what depends on d? b and a should (indirectly)
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("d.txt", dependencies, SourceFileDependencyEntry::DEP_SourceToSource, true);
EXPECT_EQ(dependencies.size(), 3);
EXPECT_EQ(dependencies.size(), 3);
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
@@ -1077,7 +1083,7 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse
// what depends on c? b and a should.
dependencies.clear();
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("c.txt", dependencies, SourceFileDependencyEntry::DEP_SourceToSource, true);
EXPECT_EQ(dependencies.size(), 3); // b depends on c, and d
EXPECT_EQ(dependencies.size(), 3); // b depends on c, and d
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/b.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
@@ -1092,7 +1098,7 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Reverse
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/c.txt").toUtf8().constData()), dependencies.end());
}
// since we need these files to still produce a 0-based fingerprint, we need them to
// since we need these files to still produce a 0-based fingerprint, we need them to
// still do a best guess at absolute path, when they are missing.
TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_MissingFiles_ReturnsNoPathWithPlaceholders)
{
@@ -1131,13 +1137,13 @@ TEST_F(AssetProcessorManagerTest, QueryAbsolutePathDependenciesRecursive_Missing
AssetProcessor::SourceFilesForFingerprintingContainer dependencies;
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("a.txt", dependencies, SourceFileDependencyEntry::DEP_SourceToSource, false);
EXPECT_EQ(dependencies.size(), 2); // a depends on b, c, and d - with the latter two being indirect.
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/a.txt").toUtf8().constData()), dependencies.end());
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
dependencies.clear();
m_assetProcessorManager->QueryAbsolutePathDependenciesRecursive("b.txt", dependencies, SourceFileDependencyEntry::DEP_SourceToSource, false);
EXPECT_EQ(dependencies.size(), 1); // b depends on c, and d
EXPECT_EQ(dependencies.size(), 1); // b depends on c, and d
EXPECT_NE(dependencies.find(tempPath.absoluteFilePath("subfolder1/d.txt").toUtf8().constData()), dependencies.end());
// eliminate b --> c
@@ -1161,7 +1167,7 @@ TEST_F(AssetProcessorManagerTest, BuilderSDK_API_CreateJobs_HasValidParameters_W
m_isIdling = false;
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
// wait for AP to become idle.
ASSERT_TRUE(BlockUntilIdle(5000));
@@ -1189,10 +1195,10 @@ TEST_F(AssetProcessorManagerTest, BuilderSDK_API_CreateJobs_HasValidParameters_W
UnitTestUtils::CreateDummyFile(absPath);
m_mockApplicationManager->ResetMockBuilderCreateJobCalls();
m_isIdling = false;
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absPath));
ASSERT_TRUE(BlockUntilIdle(5000));
ASSERT_EQ(m_mockApplicationManager->GetMockBuilderCreateJobCalls(), 1);
@@ -1461,14 +1467,14 @@ bool PathDependencyTest::ProcessAsset(TestAsset& asset, const OutputAssetSet& ou
{
ProcessJobResponse processJobResponse;
processJobResponse.m_resultCode = ProcessJobResult_Success;
for (const char* outputExtension : outputSet)
{
if(jobSet >= capturedDetails.size() || capturedDetails[jobSet].m_destinationPath.isEmpty())
{
return false;
}
QString outputAssetPath = QDir(capturedDetails[jobSet].m_destinationPath).absoluteFilePath(QString(asset.m_name.c_str()) + outputExtension);
UnitTestUtils::CreateDummyFile(outputAssetPath, "this is a test output asset");
@@ -1508,7 +1514,7 @@ bool SearchDependencies(AzToolsFramework::AssetDatabase::ProductDependencyDataba
void VerifyDependencies(AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer, AZStd::initializer_list<AZ::Data::AssetId> assetIds, AZStd::initializer_list<const char*> unresolvedPaths = {})
{
EXPECT_EQ(dependencyContainer.size(), assetIds.size() + unresolvedPaths.size());
for (const AZ::Data::AssetId& assetId : assetIds)
{
bool found = false;
@@ -1635,19 +1641,19 @@ TEST_F(PathDependencyTest, NoLongerProcessedFile_IsRemoved)
{
details = message;
});
QDir tempPath(m_tempDir.path());
QString absPath(tempPath.absoluteFilePath("subfolder1/test1.txt"));
TestAsset testAsset("test1");
ASSERT_TRUE(ProcessAsset(testAsset, { {".asset1"} }));
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
m_sharedConnection->GetProductsBySourceName("test1.txt", products);
ASSERT_EQ(products.size(), 1);
ASSERT_TRUE(QFile::exists(m_normalizedCacheRootDir.absoluteFilePath("pc/samplesproject/test1.asset1").toUtf8().constData()));
ASSERT_TRUE(QFile::exists(m_normalizedCacheRootDir.absoluteFilePath("pc/test1.asset1").toUtf8().constData()));
m_mockApplicationManager->UnRegisterAllBuilders();
@@ -1723,15 +1729,15 @@ TEST_F(PathDependencyTest, AssetProcessed_Impl_DeferredPathResolution)
AZStd::vector<TestAsset> dependencySources = { "dep1", "dep2" };
// Start with mixed casing.
ProductPathDependencySet dependencies = { {"Dep1.txt", AssetBuilderSDK::ProductPathDependencyType::SourceFile}, {"DEP2.asset2", AssetBuilderSDK::ProductPathDependencyType::ProductFile}, {"dep2.asset3", AssetBuilderSDK::ProductPathDependencyType::ProductFile} }; // Test depending on a source asset, and on a subset of product assets
TestAsset mainFile("test_text");
ASSERT_TRUE(ProcessAsset(mainFile, { { ".asset" }, {} }, dependencies));
// ---------- Verify that we have unresolved path in ProductDependencies table ----------
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
ASSERT_EQ(dependencyContainer.size(), dependencies.size());
// All dependencies are stored lowercase in the database. Make the expected dependencies lowercase here to match that.
for(auto& dependency : dependencies)
{
@@ -1752,12 +1758,12 @@ TEST_F(PathDependencyTest, AssetProcessed_Impl_DeferredPathResolution)
{
ASSERT_TRUE(ProcessAsset(dependency, { { ".asset1", ".asset2" }, { ".asset3" } }, {}));
}
// ---------- Verify that path has been found and resolved ----------
dependencyContainer.clear();
ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
VerifyDependencies(dependencyContainer,
VerifyDependencies(dependencyContainer,
{
dependencySources[0].m_products[0],
dependencySources[0].m_products[1],
@@ -1780,19 +1786,19 @@ TEST_F(PathDependencyTest, AssetProcessed_Impl_DeferredPathResolutionAlreadyReso
// create dependees
TestAsset dep1("dep1");
TestAsset dep2("deP2"); // random casing to make sure the search is case-insensitive
ASSERT_TRUE(ProcessAsset(dep1, { {".asset1"}, {".asset2"} }));
ASSERT_TRUE(ProcessAsset(dep2, { {".asset1", ".asset2"}, {".asset3"} }));
// -------- Make main test asset, with dependencies on products we just created -----
TestAsset primaryFile("test_text");
ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} }, { {"dep1.txt", ProductPathDependencyType::SourceFile}, {"DEP2.asset2", ProductPathDependencyType::ProductFile}, {"Dep2.asset3", ProductPathDependencyType::ProductFile} }));
// ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
VerifyDependencies(dependencyContainer,
VerifyDependencies(dependencyContainer,
{
dep1.m_products[0],
dep1.m_products[1],
@@ -1887,13 +1893,13 @@ TEST_F(PathDependencyTest, WildcardDependencies_Existing_ResolveCorrectly)
bool result = ProcessAsset(dep1, { {".asset1"}, {".asset2"} });
ASSERT_TRUE(result) << "Failed to Process Assets";
result = ProcessAsset(dep2, { {".asset1", ".asset2"}, {".asset3"} });
ASSERT_TRUE(result) << "Failed to Process Assets";
result = ProcessAsset(dep3, { {".asset1", ".asset2"}, {".asset3"} });
ASSERT_TRUE(result) << "Failed to Process Assets";
result = ProcessAsset(dep4, { {".asset1"}, {".asset3"} }); // This product will match on both dependencies, this will check to make sure we don't get duplicates
ASSERT_TRUE(result) << "Failed to Process Assets";
@@ -1906,7 +1912,7 @@ TEST_F(PathDependencyTest, WildcardDependencies_Existing_ResolveCorrectly)
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
result = m_sharedConnection->GetProductDependencies(dependencyContainer);
ASSERT_TRUE(result)<< "Failed to Get Product Dependencies";
VerifyDependencies(dependencyContainer,
{
dep1.m_products[0],
@@ -2054,14 +2060,14 @@ TEST_F(PathDependencyTest, WildcardDependencies_Deferred_ResolveCorrectly)
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
VerifyDependencies(dependencyContainer,
VerifyDependencies(dependencyContainer,
{
dep1.m_products[0],
dep1.m_products[1],
dep2.m_products[2],
dep3.m_products[2],
dep4.m_products[0]
},
},
{ "*p1.txt", "*.asset3" }
);
}
@@ -2204,14 +2210,14 @@ void PathDependencyTest::RunWildcardTest(bool useCorrectDatabaseSeparator, Asset
ASSERT_TRUE(ProcessAsset(matchingDepDeeperFolderMixedSlashes, { {".asset"}, {} })) << "Failed to Process " << matchingDepDeeperFolderMixedSlashes.m_name.c_str();
ASSERT_TRUE(ProcessAsset(notMatchingDepInSubfolder, { {".asset"}, {} })) << "Failed to Process " << notMatchingDepInSubfolder.m_name.c_str();
}
// -------- Make main test asset, with dependencies on products we just created -----
TestAsset primaryFile("test_text");
const char* databaseSeparator = useCorrectDatabaseSeparator ? AZ_CORRECT_DATABASE_SEPARATOR_STRING : AZ_WRONG_DATABASE_SEPARATOR_STRING;
AZStd::string extension = (pathDependencyType == ProductPathDependencyType::SourceFile) ? "txt" : "asset";
AZStd::string wildcardString = AZStd::string::format("*testFolder%s*.%s", databaseSeparator, extension.c_str());
ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} }, { {wildcardString.c_str(), pathDependencyType}, })) << "Failed to Process " << primaryFile.m_name.c_str();
if (!buildDependenciesFirst)
@@ -2354,7 +2360,7 @@ TEST_F(PathDependencyTest, AbsoluteDependencies_Deferred_ResolveCorrectly)
// -------- Make main test asset, with dependencies on products that don't exist yet -----
TestAsset primaryFile("test_text");
ASSERT_TRUE(ProcessAsset(primaryFile, { { ".asset" }, {} },
{
{
{absPathDep1.toUtf8().constData(), ProductPathDependencyType::SourceFile},
{absPathDep2.toUtf8().constData(), ProductPathDependencyType::SourceFile},
{absPathDep3.toUtf8().constData(), ProductPathDependencyType::SourceFile},
@@ -2418,7 +2424,7 @@ TEST_F(PathDependencyTest, ChangeDependencies_Existing_ResolveCorrectly)
// Update again with different dependencies
ASSERT_TRUE(ProcessAsset(primaryFile, { {".asset"} , {} }, { {absPath.toUtf8().constData(), ProductPathDependencyType::SourceFile} }));
// ---------- Verify that the dependency was recorded, and did not keep the path after resolution ----------
dependencyContainer.clear();
ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
@@ -2582,7 +2588,7 @@ TEST_F(PathDependencyTest, SourceFileDependencyWithPrefix_Deferred_ResolvesCorre
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer dependencyContainer;
ASSERT_TRUE(m_sharedConnection->GetProductDependencies(dependencyContainer));
VerifyDependencies(dependencyContainer,
VerifyDependencies(dependencyContainer,
{
dep2.m_products[0],
dep2.m_products[1],
@@ -2834,7 +2840,7 @@ TEST_F(AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependencie
// and correctly stored the results into the dependency table.
//-------------------------------- EVALUATION PHASE -------------------------
// at this point, the AP will have filed the asset away in its database and we can now validate that it actually
// at this point, the AP will have filed the asset away in its database and we can now validate that it actually
// did it correctly.
// We expect to see two dependencies in the dependency table, each with the correct dependency, no duplicates, no lost data.
AssetDatabaseConnection* sharedConnection = m_assetProcessorManager->m_stateData.get();
@@ -2856,7 +2862,7 @@ TEST_F(AssetProcessorManagerTest, AssetProcessedImpl_DifferentProductDependencie
// this also asserts uniqueness.
ASSERT_EQ(countFound, 2);
ASSERT_EQ(capturedTableEntries.size(), countFound); // if they were not unique asset IDs, they would have collapsed on top of each other.
// make sure both assetIds are present:
ASSERT_NE(capturedTableEntries.find(expectedIdOfProductA), capturedTableEntries.end());
ASSERT_NE(capturedTableEntries.find(expectedIdOfProductB), capturedTableEntries.end());
@@ -2927,7 +2933,7 @@ TEST_F(AssetProcessorManagerTest, AssessDeletedFile_OnJobInFlight_IsIgnored)
m_isIdling = false;
m_assetProcessorManager->AssetProcessed(capturedDetails.m_jobEntry, response);
ASSERT_TRUE(BlockUntilIdle(5000));
// at this point, everything should be up to date and ready for the test - there should be one source in the database
// with numOutputsToSimulate products.
// now, we simulate a job running to process the asset again, by modifying the timestamp on the file to be at least one second later.
@@ -2959,7 +2965,7 @@ TEST_F(AssetProcessorManagerTest, AssessDeletedFile_OnJobInFlight_IsIgnored)
ASSERT_FALSE(capturedDetails.m_destinationPath.isEmpty());
// ----------------------------- TEST BEGINS HERE -----------------------------
// simulte a very slow computer processing the file one output at a time and feeding file change notifies:
// FROM THIS POINT ON we should see no new job create / cancellation or anything since we're just going to be messing with the cache.
bool gotUnexpectedAssetToProcess = false;
connection = QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::AssetToProcess, [&gotUnexpectedAssetToProcess](JobDetails /*jobDetails*/)
@@ -2994,7 +3000,7 @@ TEST_F(AssetProcessorManagerTest, AssessDeletedFile_OnJobInFlight_IsIgnored)
QString fileNameToGenerate = QString("test%1.txt").arg(outputIdx);
QString filePathToGenerate = QDir(capturedDetails.m_destinationPath).absoluteFilePath(fileNameToGenerate);
JobProduct product(filePathToGenerate.toUtf8().constData(), AZ::Uuid::CreateRandom(), static_cast<AZ::u32>(outputIdx));
response.m_outputProducts.push_back(product);
@@ -3005,7 +3011,7 @@ TEST_F(AssetProcessorManagerTest, AssessDeletedFile_OnJobInFlight_IsIgnored)
// simulate the file watcher showing the deletion occuring:
notifyAPM("AssessDeletedFile", filePathToGenerate, shouldBlockAndWaitThisTime);
UnitTestUtils::CreateDummyFile(filePathToGenerate, "an output");
// let the APM go for a significant amount of time so that it simulates a slow thread copying a large file with lots of events about it pouring in.
for (int repeatLoop = 0; repeatLoop < 100; ++repeatLoop)
{
@@ -3020,7 +3026,7 @@ TEST_F(AssetProcessorManagerTest, AssessDeletedFile_OnJobInFlight_IsIgnored)
QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, QString(filePathToGenerate)));
QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents, 1);
ASSERT_FALSE(gotUnexpectedAssetToProcess);
// now tell it to stop ignoring the cache delete and let it do the next one.
EBUS_EVENT(AssetProcessor::ProcessingJobInfoBus, EndCacheFileUpdate, filePathToGenerate.toUtf8().data(), false);
@@ -3075,7 +3081,7 @@ TEST_F(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_BasicTest
// each file we will take a different approach to publishing: rel path, and UUID:
job.m_sourceFileDependencies.push_back(AZStd::make_pair<AZ::Uuid, AssetBuilderSDK::SourceFileDependency>(dummyBuilderUUID, { "a.txt", AZ::Uuid::CreateNull() }));
job.m_sourceFileDependencies.push_back(AZStd::make_pair<AZ::Uuid, AssetBuilderSDK::SourceFileDependency>(dummyBuilderUUID, { "", uuidOfB }));
// it is currently assumed that the only fields that we care about in JobDetails is the builder busId and the job dependencies themselves:
JobDetails newDetails;
newDetails.m_assetBuilderDesc.m_busId = dummyBuilderUUID;
@@ -3267,7 +3273,7 @@ TEST_F(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFi
// this indirectly verifies the QueryAbsolutePathDependenciesRecursive function also but it has its own dedicated tests, above.
AssetProcessor::SourceFilesForFingerprintingContainer deps;
m_assetProcessorManager.get()->QueryAbsolutePathDependenciesRecursive(QString::fromUtf8("assetProcessorManagerTest.txt"), deps, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceToSource, false);
// we should find all of the deps, but not the placeholders.
EXPECT_EQ(deps.size(), 2);
@@ -3453,7 +3459,7 @@ TEST_F(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_MissingFi
EXPECT_EQ(deps.size(), 2);
EXPECT_NE(deps.find(absPath.toUtf8().constData()), deps.end());
EXPECT_NE(deps.find(dependsOnFile1_Job.toUtf8().constData()), deps.end()); // c
// in addition, we expect to have the original file that depends on B appear in the analysis queue, since something it depends on appeared:
QString normalizedSourcePath = AssetUtilities::NormalizeFilePath(absPath);
EXPECT_TRUE(m_assetProcessorManager->m_alreadyActiveFiles.contains(normalizedSourcePath));
@@ -4407,7 +4413,7 @@ TEST_F(AssetProcessorManagerTest, SourceFileProcessFailure_ClearsFingerprint)
ASSERT_TRUE(found);
ASSERT_NE(source.m_analysisFingerprint, "");
// Modify the file and run it through AP again, but this time signal a failure
{
@@ -4655,12 +4661,12 @@ TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesFor
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
// Enable es3 platform after the initial SetUp has already processed the files for pc
QDir tempPath(m_tempDir.path());
AssetBuilderSDK::PlatformInfo es3Platform("es3", { "host", "renderer" });
m_config->EnablePlatform(es3Platform, true);
// There's no way to remove scanfolders and adding a new one after enabling the platform will cause the pc assets to build as well, which we don't want
// Instead we'll just const cast the vector and modify the enabled platforms for the scanfolder
auto& platforms = const_cast<AZStd::vector<AssetBuilderSDK::PlatformInfo>&>(m_config->GetScanFolderAt(0).GetPlatforms());
@@ -4743,7 +4749,7 @@ TEST_F(ModtimeScanningTest, ModtimeSkipping_ModifyFile)
using namespace AzToolsFramework::AssetSystem;
SetFileContents(m_data->m_absolutePath[1].toUtf8().constData(), "hello world");
// Enable the features we're testing
m_assetProcessorManager->m_allowModtimeSkippingFeature = true;
AssetUtilities::SetUseFileHashOverride(true, true);
@@ -4861,7 +4867,7 @@ TEST_F(ModtimeScanningTest, ModtimeSkipping_DeleteFile)
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
} while (m_data->m_deletedSources.size() < m_data->m_relativePathFromWatchFolder[0].size() && timer.elapsed() < 5000);
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, 0);
ASSERT_EQ(m_data->m_processResults.size(), 0);
ASSERT_THAT(m_data->m_deletedSources, testing::ElementsAre(m_data->m_relativePathFromWatchFolder[0]));
@@ -4885,7 +4891,7 @@ TEST_F(ModtimeScanningTest, ReprocessRequest_SourceWithDependency_BothWillProces
using SourceFileDependencyEntry = AzToolsFramework::AssetDatabase::SourceFileDependencyEntry;
SourceFileDependencyEntry newEntry1;
SourceFileDependencyEntry newEntry1;
newEntry1.m_sourceDependencyID = AzToolsFramework::AssetDatabase::InvalidEntryId;
newEntry1.m_builderGuid = AZ::Uuid::CreateRandom();
newEntry1.m_source = m_data->m_absolutePath[0].toUtf8().constData();
@@ -4953,7 +4959,7 @@ void MockBuilderInfoHandler::CreateJobs(const AssetBuilderSDK::CreateJobsRequest
if (!m_jobDependencyFilePath.isEmpty())
{
jobDescriptor.m_jobDependencyList.push_back(AssetBuilderSDK::JobDependency("Mock Job", "pc", AssetBuilderSDK::JobDependencyType::Order,
jobDescriptor.m_jobDependencyList.push_back(AssetBuilderSDK::JobDependency("Mock Job", "pc", AssetBuilderSDK::JobDependencyType::Order,
AssetBuilderSDK::SourceFileDependency(m_jobDependencyFilePath.toUtf8().constData(), AZ::Uuid::CreateNull())));
}
@@ -5127,7 +5133,7 @@ TEST_F(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardM
m_assetProcessorManager.get()->m_stateData->QueryDependsOnSourceBySourceDependency("wildcardTest.txt", nullptr, AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_SourceLikeMatch, callbackFunction);
EXPECT_EQ(wildcardDeps.size(), 2);
// The database should have the wildcard record and the individual dependency on b and c at this point, now we add new files
// The database should have the wildcard record and the individual dependency on b and c at this point, now we add new files
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(dependsOnFileb1_Source, QString("tempdata\n")));
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(dependsOnFilec1_Job, QString("tempdata\n")));
@@ -5307,7 +5313,7 @@ TEST_F(DuplicateProductsTest, SameSource_MultipleBuilder_DuplicateProductJobs_Em
QDir tempPath(m_tempDir.path());
QString sourceFile;
AZStd::vector<JobDetails> jobDetails;
ProcessJobResponse response;
SetupDuplicateProductsTest(sourceFile, tempPath, productFile, jobDetails, response, false, "txt");
@@ -5397,7 +5403,7 @@ void JobDependencyTest::SetUp()
m_data->m_mockBuilderInfoHandler.BusConnect();
QDir tempPath(m_tempDir.path());
QString watchFolderPath = tempPath.absoluteFilePath("subfolder1");
const ScanFolderInfo* scanFolder = m_config->GetScanFolderByPath(watchFolderPath);
@@ -5460,7 +5466,7 @@ TEST_F(JobDependencyTest, JobDependency_ThatWasJustRun_IsFound)
TEST_F(JobDependencyTest, JobDependency_ThatHasNotRun_IsNotFound)
{
AZStd::vector<JobDetails> capturedDetails;
capturedDetails.clear();
m_data->m_mockBuilderInfoHandler.m_jobDependencyFilePath = "c.txt";
CaptureJobs(capturedDetails, "subfolder1/b.txt");
@@ -5654,7 +5660,7 @@ TEST_F(ChainJobDependencyTest, TestChainDependency_Multi)
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
} while (finishedJobs.size() < capturedDetails.size() && timer.elapsed() < 5000);
ASSERT_EQ(finishedJobs.size(), capturedDetails.size());
// Test that the jobs completed in the correct order (captureDetails has the correct ordering)
@@ -5775,7 +5781,7 @@ TEST_F(DeleteTest, DeleteFolderSharedAcrossTwoScanFolders_CorrectFileAndFolderAr
QDir tempPath(m_tempDir.path());
QString absPath(tempPath.absoluteFilePath("subfolder1/textures"));
QDir(absPath).removeRecursively();
AZStd::vector<AZStd::string> deletedFolders;
QObject::connect(m_assetProcessorManager.get(), &AssetProcessorManager::SourceFolderDeleted, [&deletedFolders](QString file)
{
@@ -15,7 +15,7 @@
#include <AzTest/AzTest.h>
const char TestAppRoot[] = ":/testdata";
const char TestAppRoot[] = "@exefolder@/testdata";
const char EmptyDummyProjectName[] = "EmptyDummyProject";
const char DummyProjectName[] = "DummyProject";
@@ -54,10 +54,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BadPlatform)
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_badplatform";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_badplatform");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
@@ -67,10 +69,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoPlatform)
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_noplatform";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_noplatform");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
@@ -79,10 +83,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoScanFolders)
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_noscans";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_noscans");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
@@ -91,10 +97,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BrokenRecognizers)
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_broken_recognizers";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_recognizers");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0);
}
@@ -103,10 +111,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Regular_Platforms)
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
// verify the data.
@@ -127,42 +137,43 @@ TEST_F(PlatformConfigurationUnitTests, TestReadScanFolderRoot_FromSettingsRegist
auto settingsRegistry = AZ::SettingsRegistry::Get();
ASSERT_NE(nullptr, settingsRegistry);
AZ::SettingsRegistryInterface::Specializations apSpecializations;
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(*settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, apSpecializations);
struct ScanFolderVisitor
: AZ::SettingsRegistryInterface::Visitor
QTemporaryDir tempEngineRoot;
QDir tempPath(tempEngineRoot.path());
QString testScanFolderSetregPath = tempPath.absoluteFilePath("test.setreg");
UnitTestUtils::CreateDummyFile(testScanFolderSetregPath, QString(R"({ "Amazon":
{
void Visit([[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZ::s64 value)
"AssetProcessor":
{
if (valueName == "recursive")
{
m_isRecursive = value != 0;
}
else if (valueName == "order")
{
m_scanOrder = value;
"Settings":
{
"ScanFolder SettingsRegistryTest":
{
"watch": "_TestPath",
"recursive": false,
"order": 20000
}
}
}
void Visit([[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value)
{
if (valueName == "watch")
{
m_watchPath = value;
}
}
AZ::SettingsRegistryInterface::FixedValueString m_watchPath;
bool m_isRecursive{};
int m_scanOrder{};
};
}
}\n)"));
ScanFolderVisitor scanFolderVisitor;
EXPECT_TRUE(settingsRegistry->Visit(scanFolderVisitor, "/Amazon/AssetProcessor/Settings/ScanFolder Root"));
EXPECT_TRUE(AssetProcessor::PlatformConfiguration::MergeConfigFileToSettingsRegistry(*settingsRegistry, testScanFolderSetregPath.toUtf8().data()));
AZStd::string watchPath;
bool recurseScanFolder{ true };
AZ::s64 scanOrder{};
EXPECT_TRUE(settingsRegistry->Get(watchPath, AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorSettingsKey)
+ "/ScanFolder SettingsRegistryTest/watch"));
EXPECT_TRUE(settingsRegistry->Get(recurseScanFolder, AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorSettingsKey)
+ "/ScanFolder SettingsRegistryTest/recursive"));
EXPECT_TRUE(settingsRegistry->Get(scanOrder, AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorSettingsKey)
+ "/ScanFolder SettingsRegistryTest/order"));
// These test values come from the <dev_root>/Engine/Registry/AssetProcessorPlatformConfig.setreg file
EXPECT_STREQ("@ROOT@", scanFolderVisitor.m_watchPath.c_str());
EXPECT_FALSE(scanFolderVisitor.m_isRecursive);
EXPECT_EQ(10000, scanFolderVisitor.m_scanOrder);
EXPECT_STREQ("_TestPath", watchPath.c_str());
EXPECT_FALSE(recurseScanFolder);
EXPECT_EQ(20000, scanOrder);
}
@@ -340,34 +351,36 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolder)
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
AssetUtilities::ComputeProjectName(EmptyDummyProjectName, true);
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
ASSERT_EQ(config.GetScanFolderCount(), 3); // the two, and then the one that has the same data as prior but different identifier.
QString scanName = AssetUtilities::ComputeGameName() + " Scan Folder";
QString scanName = AssetUtilities::ComputeProjectPath() + " Scan Folder";
ASSERT_EQ(config.GetScanFolderAt(0).GetDisplayName(), scanName);
ASSERT_EQ(config.GetScanFolderAt(0).GetOutputPrefix(), QString());
ASSERT_EQ(config.GetScanFolderAt(0).RecurseSubFolders(), true);
ASSERT_EQ(config.GetScanFolderAt(0).GetOrder(), 0);
// its important that this does NOT change and this makes sure the old way of doing it (case-sensitive name) persists
ASSERT_EQ(config.GetScanFolderAt(0).GetPortableKey(), QString("from-ini-file-Game"));
ASSERT_EQ(config.GetScanFolderAt(0).GetPortableKey(), QString("Game"));
ASSERT_EQ(config.GetScanFolderAt(1).GetDisplayName(), QString("FeatureTests"));
ASSERT_EQ(config.GetScanFolderAt(1).GetOutputPrefix(), QString("featuretestsoutputfolder")); // to prove its not related to display name
ASSERT_EQ(config.GetScanFolderAt(1).RecurseSubFolders(), false);
ASSERT_EQ(config.GetScanFolderAt(1).GetOrder(), 5000);
// this proves that the featuretests name is used instead of the output prefix
ASSERT_EQ(config.GetScanFolderAt(1).GetPortableKey(), QString("from-ini-file-FeatureTests"));
ASSERT_EQ(config.GetScanFolderAt(1).GetPortableKey(), QString("FeatureTests"));
ASSERT_EQ(config.GetScanFolderAt(2).GetDisplayName(), QString("FeatureTests2"));
ASSERT_EQ(config.GetScanFolderAt(2).GetOutputPrefix(), QString("featuretestsoutputfolder")); // to prove its not related to display name
ASSERT_EQ(config.GetScanFolderAt(2).RecurseSubFolders(), false);
ASSERT_EQ(config.GetScanFolderAt(2).GetOrder(), 6000);
// this proves that the featuretests name is used instead of the output prefix
ASSERT_EQ(config.GetScanFolderAt(2).GetPortableKey(), QString("from-ini-file-FeatureTests2"));
ASSERT_EQ(config.GetScanFolderAt(2).GetPortableKey(), QString("FeatureTests2"));
}
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolderPlatformSpecific)
@@ -375,10 +388,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolderP
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular_platform_scanfolder";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular_platform_scanfolder");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
ASSERT_EQ(config.GetScanFolderCount(), 5);
@@ -419,10 +434,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularExcludes)
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
ASSERT_TRUE(config.IsFileExcluded("blahblah/$tmp_01.test"));
@@ -446,10 +463,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Recognizers)
const char* platformWhichIsNotCurrentPlatform = "pc";
#endif
const char* configRoot = ":/testdata/config_regular";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
@@ -519,11 +538,13 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Overrides)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, DummyProjectName, false, false));
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), DummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
@@ -565,7 +586,7 @@ TEST_F(PlatformConfigurationUnitTests, Test_GemHandling)
QTemporaryDir tempEngineRoot;
QDir tempPath(tempEngineRoot.path());
AssetUtilities::ResetAssetRoot();
AssetUtilities::ComputeGameName("SamplesProject", true);
AssetUtilities::ComputeProjectName("SamplesProject", true);
QDir computedEngineRoot;
ASSERT_TRUE(AssetUtilities::ComputeAssetRoot(computedEngineRoot, &tempPath));
@@ -576,9 +597,11 @@ TEST_F(PlatformConfigurationUnitTests, Test_GemHandling)
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("Gems/LyShine/AssetProcessorGemConfig.ini"), ";nothing to see here"));
// note that it is expected that the gems system gives us absolute paths.
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> fakeGems;
fakeGems.push_back({ "LyShine", "Gems/LyShine", tempPath.absoluteFilePath("Gems/LyShine").toUtf8().constData(), "0fefab3f13364722b2eab3b96ce2bf20", true, false });// true = pretend this is a game gem.
fakeGems.push_back({ "LmbrCentral", "Gems/LmbrCentral/v2", tempPath.absoluteFilePath("Gems/LmbrCentral/v2").toUtf8().constData(), "ff06785f7145416b9d46fde39098cb0c", false, false });
AZStd::vector<AzFramework::GemInfo> fakeGems;
fakeGems.emplace_back("LyShine");// true = pretend this is a game gem.
fakeGems.back().m_absoluteSourcePaths.push_back(tempPath.absoluteFilePath("Gems/LyShine").toUtf8().constData());
fakeGems.emplace_back("LmbrCentral");
fakeGems.back().m_absoluteSourcePaths.push_back(tempPath.absoluteFilePath("Gems/LmbrCentral/v2").toUtf8().constData());
// reading gems via the Gems System is already to be tested in the actual Gems API tests.
// to avoid trying to load those DLLs we avoid calling the actual ReadGems function
@@ -593,8 +616,7 @@ TEST_F(PlatformConfigurationUnitTests, Test_GemHandling)
EXPECT_TRUE(config.GetScanFolderAt(0).GetOutputPrefix().isEmpty());
EXPECT_TRUE(config.GetScanFolderAt(0).RecurseSubFolders());
// the first one is a game gem, so its order should be above 1 but below 100.
EXPECT_GE(config.GetScanFolderAt(0).GetOrder(), 1);
EXPECT_LE(config.GetScanFolderAt(0).GetOrder(), 100);
EXPECT_GE(config.GetScanFolderAt(0).GetOrder(), 100);
EXPECT_EQ(0, config.GetScanFolderAt(0).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
// for each gem, there are currently 1 scan folder, the gem assets folder, with no output prefix
@@ -618,15 +640,17 @@ TEST_F(PlatformConfigurationUnitTests, Test_MetaFileTypes)
ASSERT_TRUE(QString::compare(config.GetMetaDataFileTypeAt(1).second, "zzzz", Qt::CaseInsensitive) == 0);
}
TEST_F(PlatformConfigurationUnitTests, ReadCheckSever_FromConfig_Valid)
TEST_F(PlatformConfigurationUnitTests, ReadCheckServer_FromConfig_Valid)
{
using namespace AzToolsFramework::AssetSystem;
using namespace AssetProcessor;
const char* configRoot = ":/testdata/config_regular";
const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot);
auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular");
ASSERT_TRUE(configRoot);
UnitTestPlatformConfiguration config;
m_absorber.Clear();
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot, TestAppRoot, EmptyDummyProjectName, false, false));
ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false));
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer();
@@ -642,7 +666,11 @@ TEST_F(PlatformConfigurationUnitTests, PlatformConfigFile_IsPresent_Found)
QTemporaryDir tempEngineRoot;
QDir tempPath(tempEngineRoot.path());
AssetUtilities::ResetAssetRoot();
AssetUtilities::ComputeGameName("SamplesProject", true);
AssetUtilities::ComputeProjectName("SamplesProject", true);
auto settingsRegistry = AZ::SettingsRegistry::Get();
ASSERT_NE(nullptr, settingsRegistry);
settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, tempPath.absolutePath().toUtf8().constData());
QDir computedEngineRoot;
ASSERT_TRUE(AssetUtilities::ComputeAssetRoot(computedEngineRoot, &tempPath));
@@ -655,7 +683,7 @@ TEST_F(PlatformConfigurationUnitTests, PlatformConfigFile_IsPresent_Found)
platformConfigPath.append("TestPlatform/");
platformConfigPath.append(AssetProcessor::AssetProcessorPlatformConfigFileName);
QStringList platformConfigList;
AZStd::vector<AZ::IO::Path> platformConfigList;
ASSERT_FALSE(config.AddPlatformConfigFilePaths(platformConfigList));
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath(platformConfigPath), ";nothing to see here"));
ASSERT_TRUE(config.AddPlatformConfigFilePaths(platformConfigList));
@@ -13,6 +13,8 @@
#include <QHash>
#include "native/tests/AssetProcessorTest.h"
#include <native/utilities/PlatformConfiguration.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/parallel/thread.h>
#include <AzTest/AzTest.h>
@@ -519,11 +521,18 @@ TEST_F(AssetUtilitiesTest, GetServerAddress_ReadFromConfig_Valid)
QTemporaryDir tempDir;
QDir tempPath(tempDir.path());
QString assetServerAddress("T:/AssetServerCacheDummyFolder");
UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("AssetProcessorPlatformConfig.ini"), QString("[Server]\ncacheServerAddress=%1\n").arg(assetServerAddress));
QString assetProcesorPlatformConfigPath = tempPath.absoluteFilePath("AssetProcessorPlatformConfig.ini");
UnitTestUtils::CreateDummyFile(assetProcesorPlatformConfigPath, QString("[Server]\ncacheServerAddress=%1\n").arg(assetServerAddress));
AssetUtilities::ResetAssetRoot();
QDir newRoot;
AssetUtilities::ComputeEngineRoot(newRoot, &tempPath);
auto settingsRegistry = AZ::SettingsRegistry::Get();
AZ::SettingsRegistryMergeUtils::ConfigParserSettings configParserSettings;
configParserSettings.m_registryRootPointerPath = AssetProcessor::AssetProcessorSettingsKey;
AssetProcessor::PlatformConfiguration::MergeConfigFileToSettingsRegistry(*settingsRegistry,
assetProcesorPlatformConfigPath.toUtf8().data());
QString assetServerAddressReturned = AssetUtilities::ServerAddress();
EXPECT_STREQ(assetServerAddressReturned.toUtf8().data(), assetServerAddress.toUtf8().data());
}
@@ -136,7 +136,7 @@ void MainWindow::Activate()
ui->projectLabel->setText(QStringLiteral("%1: %2")
.arg(tr("Project"))
.arg(m_guiApplicationManager->GetGameName()));
.arg(QDir{m_guiApplicationManager->GetProjectPath()}.absolutePath()));
ui->rootLabel->setText(QStringLiteral("%1: %2")
.arg(tr("Root"))
@@ -1,655 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#if defined(UNIT_TEST)
#include <AzCore/base.h>
#include "AssetCatalogUnitTests.h"
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/IO/FileIO.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Asset/AssetProcessorMessages.h>
#include <AzToolsFramework/Asset/AssetProcessorMessages.h>
#include <native/AssetManager/assetProcessorManager.h>
#include <native/utilities/PlatformConfiguration.h>
#include <native/utilities/AssetBuilderInfo.h>
#include <native/AssetManager/assetScanFolderInfo.h>
#include <native/utilities/assetUtils.h>
#include <native/resourcecompiler/RCBuilder.h>
#include <native/assetprocessor.h>
#include <QTemporaryDir>
#include <QString>
#include <QCoreApplication>
#include <QSet>
#include <QList>
#include <QTime>
#include <QThread>
#include <QPair>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
namespace AssetProcessor
{
using namespace UnitTestUtils;
using namespace AzFramework::AssetSystem;
using namespace AzToolsFramework::AssetSystem;
using namespace AzToolsFramework::AssetDatabase;
REGISTER_UNIT_TEST(AssetCatalogUnitTests)
REGISTER_UNIT_TEST(AssetCatalogUnitTests_AssetInfo)
namespace
{
// a utility class to redirect the location the database is stored to a different location so that we don't
// touch real data during unit tests.
class FakeDatabaseLocationListener
: protected AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler
{
public:
FakeDatabaseLocationListener(const char* desiredLocation, const char* assetPath)
: m_location(desiredLocation)
, m_assetPath(assetPath)
{
AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler::BusConnect();
}
~FakeDatabaseLocationListener()
{
AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Handler::BusDisconnect();
}
protected:
// IMPLEMENTATION OF -------------- AzToolsFramework::AssetDatabase::AssetDatabaseRequests::Bus::Listener
bool GetAssetDatabaseLocation(AZStd::string& location) override
{
location = m_location;
return true;
}
// ------------------------------------------------------------
private:
AZStd::string m_location;
AZStd::string m_assetPath;
};
// Adds a scan folder to the config and to the database
void AddScanFolder(const ScanFolderInfo& scanFolderInfo, PlatformConfiguration& config, AssetDatabaseConnection* dbConn)
{
config.AddScanFolder(scanFolderInfo);
ScanFolderDatabaseEntry newScanFolder(
scanFolderInfo.ScanPath().toStdString().c_str(),
scanFolderInfo.GetDisplayName().toStdString().c_str(),
scanFolderInfo.GetPortableKey().toStdString().c_str(),
scanFolderInfo.GetOutputPrefix().toStdString().c_str(),
scanFolderInfo.IsRoot());
dbConn->SetScanFolder(newScanFolder);
}
void BuildConfig(const QDir& tempPath, AssetDatabaseConnection* dbConn, PlatformConfiguration& config)
{
config.EnablePlatform({ "pc" ,{ "desktop", "renderer" } }, true);
config.EnablePlatform({ "es3" ,{ "mobile", "renderer" } }, true);
config.EnablePlatform({ "fandango" ,{ "console", "renderer" } }, false);
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms;
config.PopulatePlatformsForScanFolder(platforms);
// PATH DisplayName PortKey outputfolder root recurse platforms order
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder4"), "subfolder4", "subfolder4", "", false, false, platforms, -6), config, dbConn); // subfolder 4 overrides subfolder3
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder3"), "subfolder3", "subfolder3", "", false, false, platforms, -5), config, dbConn); // subfolder 3 overrides subfolder2
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder2"), "subfolder2", "subfolder2", "", false, true, platforms, -2), config, dbConn); // subfolder 2 overrides subfolder1
AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder1"), "subfolder1", "subfolder1", "editor", false, true, platforms, -1), config, dbConn); // subfolder1 overrides root
AddScanFolder(ScanFolderInfo(tempPath.absolutePath(), "temp", "tempfolder", "", true, false, platforms, 0), config, dbConn); // add the root
config.AddMetaDataType("exportsettings", QString());
AZ::Uuid buildIDRcLegacy;
BUILDER_ID_RC.GetUuid(buildIDRcLegacy);
AssetRecognizer rec;
AssetPlatformSpec specpc;
AssetPlatformSpec speces3;
speces3.m_extraRCParams = "somerandomparam";
rec.m_name = "random files";
rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.random", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
rec.m_platformSpecs.insert("pc", specpc);
config.AddRecognizer(rec);
specpc.m_extraRCParams = ""; // blank must work
speces3.m_extraRCParams = "testextraparams";
const char* builderTxt1Name = "txt files";
rec.m_name = builderTxt1Name;
rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.txt", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
rec.m_platformSpecs.insert("pc", specpc);
rec.m_platformSpecs.insert("es3", speces3);
config.AddRecognizer(rec);
// Ignore recognizer
AssetPlatformSpec ignore_spec;
ignore_spec.m_extraRCParams = "skip";
AssetRecognizer ignore_rec;
ignore_rec.m_name = "ignore files";
ignore_rec.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher("*.ignore", AssetBuilderSDK::AssetBuilderPattern::Wildcard);
ignore_rec.m_platformSpecs.insert("pc", specpc);
ignore_rec.m_platformSpecs.insert("es3", ignore_spec);
config.AddRecognizer(ignore_rec);
ExcludeAssetRecognizer excludeRecogniser;
excludeRecogniser.m_name = "backup";
excludeRecogniser.m_patternMatcher = AssetBuilderSDK::FilePatternMatcher(".*\\/savebackup\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex);
config.AddExcludeRecognizer(excludeRecogniser);
}
// Adds a source file and job entry to the database, jobId is output
bool AddSourceAndJob(const char* scanFolder, const char* sourceRelPath, AssetDatabaseConnection* dbConn, AZ::s64& jobId, AZ::Uuid assetId = AZ::Uuid::CreateRandom())
{
ScanFolderDatabaseEntry scanFolderEntry;
bool result = dbConn->GetScanFolderByPortableKey(scanFolder, scanFolderEntry);
if(!result)
{
return false;
}
SourceDatabaseEntry sourceEntry(scanFolderEntry.m_scanFolderID, sourceRelPath, assetId, "fingerprint");
dbConn->SetSource(sourceEntry);
JobDatabaseEntry jobEntry(sourceEntry.m_sourceID, "test", 1234, "pc", assetId, AzToolsFramework::AssetSystem::JobStatus::Completed, 12345);
dbConn->SetJob(jobEntry);
jobId = jobEntry.m_jobID;
return true;
};
// Calls the GetRelativeProductPathFromFullSourceOrProductPath function and checks the return results, returning true if it matches both of the expected results
bool TestGetRelativeProductPath(const QString fileToCheck, bool expectedToFind, AZStd::initializer_list<const char*> expectedPaths)
{
bool relPathfound = false;
AZStd::string relPath;
AZStd::string fullPath(fileToCheck.toStdString().c_str());
EBUS_EVENT_RESULT(relPathfound, AzToolsFramework::AssetSystemRequestBus, GetRelativeProductPathFromFullSourceOrProductPath, fullPath, relPath);
if (relPathfound != expectedToFind)
{
return false;
}
for (auto& path : expectedPaths)
{
if (relPath == path)
{
return true;
}
}
return false;
}
// Calls the GetFullSourcePathFromRelativeProductPath function and checks the return results, returning true if it matches both of the expected results
bool TestGetFullSourcePath(const QString& fileToCheck, const QDir& tempPath, bool expectToFind, const char* expectedPath)
{
bool fullPathfound = false;
AZStd::string fullPath;
AZStd::string relPath(fileToCheck.toStdString().c_str());
EBUS_EVENT_RESULT(fullPathfound, AzToolsFramework::AssetSystemRequestBus, GetFullSourcePathFromRelativeProductPath, relPath, fullPath);
if (fullPathfound != expectToFind)
{
return false;
}
QString output(fullPath.c_str());
output.remove(0, tempPath.path().length() + 1); //adding one for the native separator
return (output == expectedPath);
}
} // end anon namespace
void AssetCatalogUnitTests::StartTest()
{
QDir oldRoot;
AssetUtilities::ComputeAssetRoot(oldRoot);
AssetUtilities::ResetAssetRoot();
// the canonicalization of the path here is to get around the fact that on some platforms
// the "temporary" folder location could be junctioned into some other folder and getting "QDir::current()"
// and other similar functions may actually return a different string but still be referring to the same folder
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
NetworkRequestID requestId(1, 1);
FakeDatabaseLocationListener listener(tempPath.filePath("statedatabase.sqlite").toUtf8().constData(), "displayString");
AZStd::unique_ptr<AssetDatabaseConnection> dbConn = AZStd::make_unique<AssetDatabaseConnection>();
dbConn->OpenDatabase();
CreateDummyFile(tempPath.absoluteFilePath("bootstrap.cfg"), QString("sys_game_folder=SamplesProject\n"));
// system is already actually initialized, along with gEnv, so this will always return that game name.
QString gameName = AssetUtilities::ComputeGameName();
// update the engine root
AssetUtilities::ResetAssetRoot();
QDir newRoot;
AssetUtilities::ComputeAssetRoot(newRoot, &tempPath);
UNIT_TEST_EXPECT_FALSE(gameName.isEmpty());
// should create cache folder in the root, and read everything from there.
QSet<QString> expectedFiles;
// set up some interesting files:
expectedFiles << tempPath.absoluteFilePath("rootfile2.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder1/rootfile1.txt"); // note: Must override the actual root file
expectedFiles << tempPath.absoluteFilePath("subfolder1/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder2/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/bbb/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/bbb/ccc/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/bbb/ccc/ddd/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder3/BaseFile.txt"); // note the case upper here
expectedFiles << tempPath.absoluteFilePath("subfolder8/a/b/c/test.txt");
// subfolder3 is not recursive so none of these should show up in any scan or override check
expectedFiles << tempPath.absoluteFilePath("subfolder3/aaa/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder3/aaa/bbb/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder3/aaa/bbb/ccc/basefile.txt");
expectedFiles << tempPath.absoluteFilePath("subfolder3/uniquefile.txt"); // only exists in subfolder3
expectedFiles << tempPath.absoluteFilePath("subfolder3/uniquefile.ignore"); // only exists in subfolder3
expectedFiles << tempPath.absoluteFilePath("subfolder3/rootfile3.txt"); // must override rootfile3 in root
expectedFiles << tempPath.absoluteFilePath("rootfile1.txt");
expectedFiles << tempPath.absoluteFilePath("rootfile3.txt");
expectedFiles << tempPath.absoluteFilePath("unrecognised.file"); // a file that should not be recognised
expectedFiles << tempPath.absoluteFilePath("unrecognised2.file"); // a file that should not be recognised
expectedFiles << tempPath.absoluteFilePath("subfolder1/test/test.format"); // a file that should be recognised
expectedFiles << tempPath.absoluteFilePath("test.format"); // a file that should NOT be recognised
expectedFiles << tempPath.absoluteFilePath("subfolder3/somefile.xxx");
expectedFiles << tempPath.absoluteFilePath("subfolder3/savebackup/test.txt");//file that should be excluded
expectedFiles << tempPath.absoluteFilePath("subfolder3/somerandomfile.random");
for (const QString& expect : expectedFiles)
{
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(expect));
AZ_TracePrintf(AssetProcessor::DebugChannel, "Created file %s with msecs %llu\n", expect.toUtf8().constData(),
QFileInfo(expect).lastModified().toMSecsSinceEpoch());
#if defined(AZ_PLATFORM_WINDOWS)
QThread::msleep(35); // give at least some milliseconds so that the files never share the same timestamp exactly
#else
// on platforms such as mac, the file time resolution is only a second :(
QThread::msleep(1001);
#endif
}
PlatformConfiguration config;
BuildConfig(tempPath, dbConn.get(), config);
AssetCatalog assetCatalog(nullptr, &config);
QDir cacheRoot;
UNIT_TEST_EXPECT_TRUE(AssetUtilities::ComputeProjectCacheRoot(cacheRoot));
QString normalizedCacheRoot = AssetUtilities::NormalizeDirectoryPath(cacheRoot.absolutePath());
// make sure it picked up the one in the current folder
QString normalizedDirPathCheck = AssetUtilities::NormalizeDirectoryPath(tempPath.absoluteFilePath("Cache/" + gameName));
UNIT_TEST_EXPECT_TRUE(normalizedCacheRoot == normalizedDirPathCheck);
QDir normalizedCacheRootDir(normalizedCacheRoot);
// ----- Test the get asset path functions, which given a full path to an asset, checks the mappings and turns it into an Asset ID ---
{
// sanity check - make sure it does not crash or misbehave when given empty names
QString fileToCheck = "";
{
UnitTestUtils::AssertAbsorber absorb;
// empty requests should generate an assert.
GetRelativeProductPathFromFullSourceOrProductPathRequest request(fileToCheck.toUtf8().constData());
UNIT_TEST_EXPECT_TRUE(absorb.m_numAssertsAbsorbed == 1);
GetFullSourcePathFromRelativeProductPathRequest sourceRequest(fileToCheck.toUtf8().constData());
UNIT_TEST_EXPECT_TRUE(absorb.m_numAssertsAbsorbed == 2);
}
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath("", false, {""}));
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath("", tempPath, false, ""));
// Add a source file with 4 products
{
AZ::s64 jobId;
bool result = AddSourceAndJob("subfolder3", "BaseFile.txt", dbConn.get(), jobId);
UNIT_TEST_EXPECT_TRUE(result);
AZ::u32 productSubId = 0;
for (auto& relativeProductPath : { "subfolder3/basefilez.arc2", "subfolder3/basefileaz.azm2", "subfolder3/basefile.arc2", "subfolder3/basefile.azm2" })
{
ProductDatabaseEntry newProduct(jobId, productSubId++, cacheRoot.relativeFilePath(relativeProductPath).toStdString().c_str(), AZ::Data::AssetType::CreateRandom());
dbConn->SetProduct(newProduct);
}
}
// GetRelativeProductPathFromFullSourceOrProductPath has 4 code paths:
// 1) Relative input paths are returned straight away
// 2) Paths inside the cache folder are transformed to product paths, no database involvement
// 3) Source files that have a product return the product path
// 4) Source files that don't have a product return the source file's relative path since that is the path a product might have
// Failure case
#if defined(AZ_PLATFORM_WINDOWS)
fileToCheck = "d:\\test.txt";
#else
fileToCheck = "/test.txt"; // rooted
#endif
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, false, { fileToCheck.toStdString().c_str() }));
// (Case 1) feed it a relative path
fileToCheck = "\test.txt";
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "\test.txt" }));
// (Case 2) feed it a product path with gamename
fileToCheck = normalizedCacheRootDir.filePath("pc/" + gameName + "/aaa/basefile.txt");
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
// (Case 2) feed it a product path without gamename
fileToCheck = normalizedCacheRootDir.filePath("pc/basefile.txt");
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "basefile.txt" }));
// (Case 2) feed it a product path with gamename but poor casing (test 1: the pc platform is not matching case)
fileToCheck = normalizedCacheRootDir.filePath("Pc/" + gameName + "/aaa/basefile.txt");
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
// (Case 2) feed it a product path with gamename but poor casing (test 2: the gameName is not matching case)
fileToCheck = normalizedCacheRootDir.filePath("pc/" + gameName.toUpper() + "/aaa/basefile.txt");
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
// (Case 2) feed it a product path that resolves to a directory name instead of a file.
fileToCheck = normalizedCacheRootDir.filePath("pc/" + gameName.toUpper() + "/aaa");
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa" }));
// Case 3
fileToCheck = tempPath.absoluteFilePath("subfolder3/BaseFile.txt");
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "basefilez.arc2", "basefileaz.azm2", "basefile.arc2", "basefile.azm2" }));
// Case 4
fileToCheck = tempPath.absoluteFilePath("subfolder2/aaa/basefile.txt");
UNIT_TEST_EXPECT_TRUE(TestGetRelativeProductPath(fileToCheck, true, { "aaa/basefile.txt" }));
// ----- Test the ProcessGetFullAssetPath function
{
QStringList pcouts;
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/subfolder3/randomfileoutput.random"));
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/subfolder3/randomfileoutput.random1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/subfolder3/randomfileoutput.random2"));
AZ::s64 jobId;
bool result = AddSourceAndJob("subfolder3", "somerandomfile.random", dbConn.get(), jobId);
UNIT_TEST_EXPECT_TRUE(result);
AZ::u32 productSubID = 0;
for (auto& product : pcouts)
{
ProductDatabaseEntry newProduct(jobId, productSubID++, cacheRoot.relativeFilePath(product).toStdString().c_str(), AZ::Data::AssetType::CreateRandom());
dbConn->SetProduct(newProduct);
}
}
//feed it an relative product, and expect a full, absolute source file path in return.
fileToCheck = "subfolder3/randomfileoutput.random1";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it another relative product
fileToCheck = "subfolder3/randomfileoutput.random2";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it the same relative product with different separators
fileToCheck = "subfolder3\\randomfileoutput.random2";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it a full path
fileToCheck = tempPath.filePath("somefolder/somefile.txt");
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "somefolder/somefile.txt"));
//feed it a path with alias and asset id
fileToCheck = "@assets@/subfolder3/randomfileoutput.random1";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it a path with some random alias and asset id
fileToCheck = "@somerandomalias@/subfolder3/randomfileoutput.random1";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it a path with some random alias and asset id but no separator
fileToCheck = "@somerandomalias@subfolder3/randomfileoutput.random1";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it a path with alias and input name
fileToCheck = "@assets@/somerandomfile.random";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it an absolute path with cacheroot
fileToCheck = normalizedCacheRootDir.filePath("pc/" + gameName + "/subfolder3/randomfileoutput.random1");
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
//feed it a productName directly
fileToCheck = "pc/" + gameName + "/subfolder3/randomfileoutput.random1";
UNIT_TEST_EXPECT_TRUE(TestGetFullSourcePath(fileToCheck, tempPath, true, "subfolder3/somerandomfile.random"));
}
Q_EMIT UnitTestPassed();
}
//////////////////////////////////////////////////////////////////////////
void AssetCatalogUnitTests_AssetInfo::StartTest()
{
using namespace AZ::Data;
using namespace AzToolsFramework;
// the canonicalization of the path here is to get around the fact that on some platforms
// the "temporary" folder location could be junctioned into some other folder and getting "QDir::current()"
// and other similar functions may actually return a different string but still be referring to the same folder
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
CreateDummyFile(tempPath.absoluteFilePath("bootstrap.cfg"), QString("sys_game_folder=SamplesProject\n"));
// system is already actually initialized, along with gEnv, so this will always return that game name.
QString gameName = AssetUtilities::ComputeGameName();
// update the engine root
AssetUtilities::ResetAssetRoot();
QDir newRoot;
AssetUtilities::ComputeAssetRoot(newRoot, &tempPath);
QDir cacheRoot;
AssetUtilities::ComputeProjectCacheRoot(cacheRoot);
AZStd::string cacheRootPath = cacheRoot.absolutePath().toStdString().c_str();
FakeDatabaseLocationListener listener(tempPath.filePath("statedatabase.sqlite").toUtf8().constData(), "displayString");
AZStd::unique_ptr<AssetDatabaseConnection> dbConn = AZStd::make_unique<AssetDatabaseConnection>();
dbConn->OpenDatabase();
PlatformConfiguration config;
BuildConfig(tempPath, dbConn.get(), config);
AssetCatalog assetCatalog(nullptr, &config);
//////////////////////////////////////////////////////////////////////////
AssetId assetA(AZ::Uuid::CreateRandom(), 0);
AZ::Uuid assetALegacyUuid = AZ::Uuid::CreateRandom();
AssetType assetAType = AssetType::CreateRandom();
AZStd::string assetAFileFilter = "*.source";
AZStd::string subfolder1AbsolutePath = tempPath.absoluteFilePath("subfolder1").toStdString().c_str();
AZStd::string assetASourceRelPath = "assetA.source";
AZStd::string assetASourceDatabasePath = "editor/assetA.source";
AZStd::string assetAProductRelPath = "editor/assetA.product";
AZStd::string assetAFullPath;
AzFramework::StringFunc::Path::Join(subfolder1AbsolutePath.c_str(), assetASourceRelPath.c_str(), assetAFullPath);
CreateDummyFile(QString::fromUtf8(assetAFullPath.c_str()), "Its the Asset A"); // 15 bytes of data
AZStd::string assetAProductFullPath;
AzFramework::StringFunc::Path::Join(cacheRootPath.c_str(), assetAProductRelPath.c_str(), assetAProductFullPath);
CreateDummyFile(QString::fromUtf8(assetAProductFullPath.c_str()), "Its a product A"); // 15 bytes of data
auto getAssetInfoById = [assetA, assetAType, subfolder1AbsolutePath](bool expectedResult, AZStd::string expectedRelPath, AZStd::string expectedRootPath, AssetType assetType) -> bool
{
bool result = false;
AssetInfo assetInfo;
AZStd::string rootPath;
AssetSystemRequestBus::BroadcastResult(result, &AssetSystem::AssetSystemRequest::GetAssetInfoById, assetA, assetType, assetInfo, rootPath);
if (result != expectedResult)
{
return false;
}
if (expectedResult)
{
return (assetInfo.m_assetId == assetA)
&& (assetInfo.m_assetType == assetAType)
&& (assetInfo.m_relativePath == expectedRelPath)
&& (assetInfo.m_sizeBytes == 15)
&& (rootPath == expectedRootPath);
}
return true;
};
auto getAssetInfoByIdPair = [&](bool expectedResult, AZStd::string expectedRelPath, AZStd::string expectedRootPath) -> bool
{
// First test without providing the assetType
bool result = getAssetInfoById(expectedResult, expectedRelPath, expectedRootPath, AssetType::CreateNull());
// If successful, test again, this time providing the assetType
if (result)
{
result = getAssetInfoById(expectedResult, expectedRelPath, expectedRootPath, assetAType);
}
return result;
};
auto getSourceInfoBySourcePath = [](bool expectedResult, AZStd::string sourcePath, AZ::Uuid expectedUuid, AZStd::string expectedRelPath, AZStd::string expectedRootPath, AZ::Data::AssetType expectedType = AZ::Data::s_invalidAssetType) -> bool
{
bool result = false;
AssetInfo assetInfo;
AZStd::string rootPath;
AssetSystemRequestBus::BroadcastResult(result, &AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, sourcePath.c_str(), assetInfo, rootPath);
if (result != expectedResult)
{
return false;
}
if (expectedResult)
{
return (assetInfo.m_assetId == expectedUuid)
&& (assetInfo.m_assetType == expectedType)
&& (assetInfo.m_relativePath == expectedRelPath)
&& (assetInfo.m_sizeBytes == 15)
&& (rootPath == expectedRootPath)
;
}
return true;
};
//Test 1: Asset not in database
UNIT_TEST_EXPECT_TRUE(getAssetInfoByIdPair(false, "", ""));
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(false, "", AZ::Uuid::CreateNull(), "", ""));
// Add asset to database
AZ::s64 jobId;
UNIT_TEST_EXPECT_TRUE(AddSourceAndJob("subfolder1", assetASourceDatabasePath.c_str(), dbConn.get(), jobId, assetA.m_guid));
ProductDatabaseEntry newProductEntry(jobId, 0, assetAProductRelPath.c_str(), assetAType);
dbConn->SetProduct(newProductEntry);
// Test 2: Asset in database, not registered as source asset
// note that when asking for products, a performance improvement causes the catalog to use its REGISTRY
// rather than the database to ask for products, so to set this up the registry must be present and must have the asset registered within it
AzFramework::AssetSystem::AssetNotificationMessage message(assetAProductRelPath.c_str(), AssetNotificationMessage::AssetChanged, assetAType);
message.m_sizeBytes = 15;
message.m_assetId = AZ::Data::AssetId(assetA.m_guid, 0);
assetCatalog.OnAssetMessage("pc", message);
// also of note: When looking up products, you don't get a root path since they are all in the cache.
// its important here that we specifically get an empty root path.
UNIT_TEST_EXPECT_TRUE(getAssetInfoByIdPair(true, assetAProductRelPath, ""));
// this call has to work with full and relative path.
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetASourceRelPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str()));
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetAFullPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str()));
dbConn->RemoveProductsByJobID(jobId);
// similar to the above, because its not the DB that is used for products, we have to inform the catalog that its gone
message.m_type = AssetNotificationMessage::AssetRemoved;
assetCatalog.OnAssetMessage("pc", message);
// Add to queue
assetCatalog.OnSourceQueued(assetA.m_guid, assetALegacyUuid, subfolder1AbsolutePath.c_str(), assetASourceRelPath.c_str());
//Test 3: Asset in queue, not registered as source asset
UNIT_TEST_EXPECT_TRUE(getAssetInfoByIdPair(false, "", ""));
// this call should STILL work even after the above call to "OnSourceQueued" since its explicitly asking for the source details.
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetASourceRelPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str()));
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetAFullPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str()));
// Register as source type
// note that once this call has been made, ALL REQUESTS for this type of asset should always include an appropriate type (non zero)
ToolsAssetSystemBus::Broadcast(&ToolsAssetSystemRequests::RegisterSourceAssetType, assetAType, assetAFileFilter.c_str());
//Test 4: Asset in queue, registered as source asset
UNIT_TEST_EXPECT_TRUE(getAssetInfoByIdPair(true, assetASourceRelPath, subfolder1AbsolutePath));
// these calls are identical to the two above, but should continue to work even though we have registered the asset type as a source asset type.
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetASourceRelPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str(), assetAType));
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetAFullPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str(), assetAType));
// Remove from queue
assetCatalog.OnSourceFinished(assetA.m_guid, assetALegacyUuid);
// Add asset to database
ProductDatabaseEntry assetAEntry(jobId, 0, assetAProductRelPath.c_str(), assetAType);
dbConn->SetProduct(assetAEntry);
//Test 5: Asset in database, registered as source asset
UNIT_TEST_EXPECT_TRUE(getAssetInfoByIdPair(true, assetASourceRelPath, subfolder1AbsolutePath));
// at this point the details about the asset in question is no longer in memory, only the database. However, these calls should continue find the
// information, because the system is supposed check both the database AND the in-memory queue in the to find the info being requested.
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetASourceRelPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str(), assetAType));
UNIT_TEST_EXPECT_TRUE(getSourceInfoBySourcePath(true, assetAFullPath.c_str(), assetA.m_guid, assetASourceRelPath.c_str(), subfolder1AbsolutePath.c_str(), assetAType));
Q_EMIT UnitTestPassed();
}
#include <native/unittests/moc_AssetCatalogUnitTests.cpp>
} // namespace AssetProcessor
#endif
@@ -15,7 +15,7 @@
#include "AssetProcessorManagerUnitTests.h"
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include "MockApplicationManager.h"
#include "native/FileWatcher/FileWatcher.h"
@@ -56,7 +56,7 @@ namespace AssetProcessor
using GetFullSourcePathFromRelativeProductPathResponse = AzFramework::AssetSystem::GetFullSourcePathFromRelativeProductPathResponse;
};
REGISTER_UNIT_TEST(AssetProcessorManagerUnitTests_ScanFolders)
@@ -138,7 +138,7 @@ namespace AssetProcessor
AssetUtilities::ComputeAssetRoot(oldRoot);
AssetUtilities::ResetAssetRoot();
FileWatcher fileWatcher;
UNIT_TEST_EXPECT_TRUE(QDir::temp().exists());
QString tmpDirPath = AssetUtilities::NormalizeDirectoryPath(QDir::tempPath());
@@ -153,8 +153,7 @@ namespace AssetProcessor
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
NetworkRequestID requestId(1, 1);
// system is already actually initialized, along with gEnv, so this will always return that game name.
QString gameName = AssetUtilities::ComputeGameName(AssetProcessorManagerTestGameProject);
QString gameName = AssetUtilities::ComputeProjectName(AssetProcessorManagerTestGameProject);
// update the engine root
AssetUtilities::ResetAssetRoot();
@@ -162,6 +161,13 @@ namespace AssetProcessor
AssetUtilities::ComputeAssetRoot(newRoot, &tempPath);
// create a dummy file in the cache folder, so the folder structure gets created
// Override the cache folder to be the within the temporary directory
auto projectCacheRootKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Set(projectCacheRootKey, tempPath.absoluteFilePath("Cache").toUtf8().data());
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
}
QDir projectCacheRoot;
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
CreateDummyFile(projectCacheRoot.absoluteFilePath("placeholder.txt"));
@@ -343,7 +349,7 @@ namespace AssetProcessor
// make sure it picked up the one in the cache and not for example the real working folder
QString normalizedDirPathCheck = AssetUtilities::NormalizeDirectoryPath(QDir(canonicalTempDirPath).absoluteFilePath("Cache/" + gameName));
QString normalizedDirPathCheck = AssetUtilities::NormalizeDirectoryPath(QDir(canonicalTempDirPath).absoluteFilePath("Cache"));
UNIT_TEST_EXPECT_TRUE(normalizedCacheRoot == normalizedDirPathCheck);
QDir normalizedCacheRootDir(normalizedCacheRoot);
@@ -441,7 +447,7 @@ namespace AssetProcessor
QString relativePathFromWatchFolder = "uniquefile.txt";
QString watchFolderPath = tempPath.absoluteFilePath("subfolder3");
QString absolutePath = AssetUtilities::NormalizeFilePath(watchFolderPath + "/" + relativePathFromWatchFolder);
QMetaObject::invokeMethod(&apm, "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
UNIT_TEST_EXPECT_TRUE(BlockUntil(idling, 5000));
@@ -469,7 +475,7 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_watchFolderPath == AssetUtilities::NormalizeFilePath(watchFolderPath));
UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_pathRelativeToWatchFolder == "uniquefile.txt");
UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_databaseSourceName == "uniquefile.txt");
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()) + "/" + gameName.toLower());
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()));
platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_destinationPath.startsWith(platformFolder));
UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_computedFingerprint != 0);
@@ -659,8 +665,8 @@ namespace AssetProcessor
QStringList es3outs;
es3outs.push_back(cacheRoot.filePath(QString("es3/") + gameName + "/basefile.arc1"));
es3outs.push_back(cacheRoot.filePath(QString("es3/") + gameName + "/basefile.arc2"));
es3outs.push_back(cacheRoot.filePath(QString("es3/basefile.arc1")));
es3outs.push_back(cacheRoot.filePath(QString("es3/basefile.arc2")));
// feed it the messages its waiting for (create the files)
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(es3outs[0], "products."));
@@ -790,7 +796,7 @@ namespace AssetProcessor
assetMessages.clear();
es3outs.clear();
es3outs.push_back(cacheRoot.filePath(QString("es3/") + gameName + "/basefile.azm"));
es3outs.push_back(cacheRoot.filePath(QString("es3/basefile.azm")));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(es3outs[0], "products."));
//Invoke Asset Processed for es3 platform , txt files2 job description
@@ -815,7 +821,7 @@ namespace AssetProcessor
assetMessages.clear();
QStringList pcouts;
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.arc1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/basefile.arc1")));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "products."));
response.m_outputProducts.clear();
@@ -843,7 +849,7 @@ namespace AssetProcessor
assetMessages.clear();
pcouts.clear();
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.azm"));
pcouts.push_back(cacheRoot.filePath(QString("pc/basefile.azm")));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "products."));
response.m_outputProducts.clear();
@@ -1012,7 +1018,7 @@ namespace AssetProcessor
{
QString processFile1 = processResults[checkIdx].m_jobEntry.GetAbsoluteSourcePath();
UNIT_TEST_EXPECT_TRUE(AssetUtilities::NormalizeFilePath(processFile1) == AssetUtilities::NormalizeFilePath(absolutePath));
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()) + "/" + gameName.toLower());
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()));
platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
processFile1 = processResults[checkIdx].m_destinationPath;
UNIT_TEST_EXPECT_TRUE(processFile1.startsWith(platformFolder));
@@ -1029,12 +1035,12 @@ namespace AssetProcessor
QStringList pcouts2;
es3outs.clear();
pcouts.clear();
es3outs.push_back(cacheRoot.filePath(QString("es3/") + gameName + "/basefilea.arc1"));
es3outs2.push_back(cacheRoot.filePath(QString("es3/") + gameName + "/basefilea.azm"));
es3outs.push_back(cacheRoot.filePath(QString("es3/basefilea.arc1")));
es3outs2.push_back(cacheRoot.filePath(QString("es3/basefilea.azm")));
// note that the ES3 outs have changed
// but the pc outs are still the same.
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.arc1"));
pcouts2.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.azm"));
pcouts.push_back(cacheRoot.filePath(QString("pc/basefile.arc1")));
pcouts2.push_back(cacheRoot.filePath(QString("pc/basefile.azm")));
// feed it the messages its waiting for (create the files)
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(es3outs[0], "newfile."));
@@ -1155,7 +1161,7 @@ namespace AssetProcessor
{
QString processFile1 = processResults[checkIdx].m_jobEntry.GetAbsoluteSourcePath();
UNIT_TEST_EXPECT_TRUE(AssetUtilities::NormalizeFilePath(processFile1) == AssetUtilities::NormalizeFilePath(absolutePath));
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()) + "/" + gameName.toLower());
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()));
platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
processFile1 = processResults[checkIdx].m_destinationPath;
UNIT_TEST_EXPECT_TRUE(processFile1.startsWith(platformFolder));
@@ -1261,7 +1267,7 @@ namespace AssetProcessor
// 4 * file claimed for the produce file to be able to update it safely.
// 4 * file released for the produce file so it's free for other tools to use it again.
UNIT_TEST_EXPECT_TRUE(payloadList.size() == 9);
unsigned int messageLoadCount = 0;
unsigned int messageLoadCount = 0;
for (auto payload : payloadList)
{
if (payload.first == SourceFileNotificationMessage::MessageType)
@@ -1487,9 +1493,9 @@ namespace AssetProcessor
pcouts.clear();
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/subfolder3/randomfileoutput.random"));
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/subfolder3/randomfileoutput.random1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/subfolder3/randomfileoutput.random2"));
pcouts.push_back(cacheRoot.filePath(QString("pc/subfolder3/randomfileoutput.random")));
pcouts.push_back(cacheRoot.filePath(QString("pc/subfolder3/randomfileoutput.random1")));
pcouts.push_back(cacheRoot.filePath(QString("pc/subfolder3/randomfileoutput.random2")));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "products."));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[1], "products."));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[2], "products."));
@@ -1535,12 +1541,12 @@ namespace AssetProcessor
es3outs2.clear();
pcouts.clear();
pcouts2.clear();
es3outs.push_back(cacheRoot.filePath(QString("es3/") + gameName + "/basefilez.arc2"));
es3outs2.push_back(cacheRoot.filePath(QString("es3/") + gameName + "/basefileaz.azm2"));
es3outs.push_back(cacheRoot.filePath(QString("es3/basefilez.arc2")));
es3outs2.push_back(cacheRoot.filePath(QString("es3/basefileaz.azm2")));
// note that the ES3 outs have changed
// but the pc outs are still the same.
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.arc2"));
pcouts2.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.azm2"));
pcouts.push_back(cacheRoot.filePath(QString("pc/basefile.arc2")));
pcouts2.push_back(cacheRoot.filePath(QString("pc/basefile.azm2")));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(es3outs[0], "newfile."));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "newfile."));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(es3outs2[0], "newfile."));
@@ -1629,7 +1635,7 @@ namespace AssetProcessor
{
QString processFile1 = processResults[checkIdx].m_jobEntry.GetAbsoluteSourcePath();
UNIT_TEST_EXPECT_TRUE(processFile1 == expectedReplacementInputFile);
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()) + "/" + gameName.toLower());
QString platformFolder = cacheRoot.filePath(QString::fromUtf8(processResults[checkIdx].m_jobEntry.m_platformInfo.m_identifier.c_str()));
platformFolder = AssetUtilities::NormalizeDirectoryPath(platformFolder);
processFile1 = processResults[checkIdx].m_destinationPath;
UNIT_TEST_EXPECT_TRUE(processFile1.startsWith(platformFolder));
@@ -1951,13 +1957,13 @@ namespace AssetProcessor
}
UNIT_TEST_EXPECT_TRUE(BlockUntil(idling, 5000));
// it now believes that there are a whole bunch of assets in subfolder1/done_renaming and they resulted in
// a whole bunch of files to have been created in the asset cache, listed in processresults, and they exist in outputscreated...
// rename the output folder:
QString originalCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/" + gameName.toLower() + "/done_renaming";
QString newCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/" + gameName.toLower() + "/renamed_again";
QString originalCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/done_renaming";
QString newCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/renamed_again";
UNIT_TEST_EXPECT_TRUE(renamer.rename(originalCacheFolderName, newCacheFolderName));
@@ -2017,8 +2023,8 @@ namespace AssetProcessor
// setup complete. now RENAME that folder.
originalCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/" + gameName.toLower() + "/rename_this_secondly";
newCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/" + gameName.toLower() + "/done_renaming_again";
originalCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/rename_this_secondly";
newCacheFolderName = normalizedCacheRootDir.absoluteFilePath("pc") + "/done_renaming_again";
UNIT_TEST_EXPECT_TRUE(renamer.rename(originalCacheFolderName, newCacheFolderName));
@@ -2305,8 +2311,7 @@ namespace AssetProcessor
UnitTestUtils::ScopedDir changeDir(dir.path());
QDir tempPath(dir.path());
// system is already actually initialized, along with gEnv, so this will always return that game name.
QString gameName = AssetUtilities::ComputeGameName(AssetProcessorManagerTestGameProject);
QString gameName = AssetUtilities::ComputeProjectName(AssetProcessorManagerTestGameProject);
// update the engine root
AssetUtilities::ResetAssetRoot();
@@ -2382,8 +2387,8 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_jobKey.compare(processResults[1].m_jobEntry.m_jobKey) != 0);
QStringList pcouts;
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.arc1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.arc2"));
pcouts.push_back(cacheRoot.filePath(QString("pc/basefile.arc1")));
pcouts.push_back(cacheRoot.filePath(QString("pc/basefile.arc2")));
// Create the product files for the first job
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "product1"));
@@ -2416,7 +2421,7 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(AssetUtilities::NormalizeFilePath(changedInputResults[0].first) == AssetUtilities::NormalizeFilePath(sourceFile));
pcouts.clear();
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/basefile.arc3"));
pcouts.push_back(cacheRoot.filePath(QString("pc/basefile.arc3")));
// Create the product files for the second job
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "product1"));
@@ -2515,9 +2520,7 @@ namespace AssetProcessor
UnitTestUtils::ScopedDir changeDir(dir.path());
QDir tempPath(dir.path());
// system is already actually initialized, along with gEnv, so this will always return that game name.
QString gameName = AssetUtilities::ComputeGameName(AssetProcessorManagerTestGameProject);
QString gameName = AssetUtilities::ComputeProjectName(AssetProcessorManagerTestGameProject);
// update the engine root
AssetUtilities::ResetAssetRoot();
@@ -2745,7 +2748,7 @@ namespace AssetProcessor
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
};
AssetProcessor::AssetBuilderInfoBus::Handler::BusConnect();
QDir oldRoot;
AssetUtilities::ComputeAssetRoot(oldRoot);
@@ -2757,8 +2760,7 @@ namespace AssetProcessor
UnitTestUtils::ScopedDir changeDir(dir.path());
QDir tempPath(dir.path());
// system is already actually initialized, along with gEnv, so this will always return that game name.
QString gameName = AssetUtilities::ComputeGameName(AssetProcessorManagerTestGameProject);
QString gameName = AssetUtilities::ComputeProjectName(AssetProcessorManagerTestGameProject);
// update the engine root
AssetUtilities::ResetAssetRoot();
@@ -2803,11 +2805,11 @@ namespace AssetProcessor
QDir cacheRoot;
UNIT_TEST_EXPECT_TRUE(AssetUtilities::ComputeProjectCacheRoot(cacheRoot));
QString productFileAPath = cacheRoot.filePath(QString("pc/") + gameName + "/FileAProduct.txt");
QString productFileBPath = cacheRoot.filePath(QString("pc/") + gameName + "/FileBProduct1.txt");
QString product2FileBPath = cacheRoot.filePath(QString("pc/") + gameName + "/FileBProduct2.txt");
QString productFileCPath = cacheRoot.filePath(QString("pc/") + gameName + "/FileCProduct.txt");
QString product2FileCPath = cacheRoot.filePath(QString("pc/") + gameName + "/FileCProduct2.txt");
QString productFileAPath = cacheRoot.filePath(QString("pc/FileAProduct.txt"));
QString productFileBPath = cacheRoot.filePath(QString("pc/FileBProduct1.txt"));
QString product2FileBPath = cacheRoot.filePath(QString("pc/FileBProduct2.txt"));
QString productFileCPath = cacheRoot.filePath(QString("pc/FileCProduct.txt"));
QString product2FileCPath = cacheRoot.filePath(QString("pc/FileCProduct2.txt"));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(sourceFileAPath, ""));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(sourceFileBPath, ""));
@@ -2817,7 +2819,7 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(product2FileBPath, "product"));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(productFileCPath, "product"));
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(product2FileCPath, "product"));
// Analyze FileA
QMetaObject::invokeMethod(&apm, "AssessAddedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
@@ -2927,7 +2929,7 @@ namespace AssetProcessor
{
// Ensure that we are processing the right FileB job
UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy") == 0);
response.m_outputProducts.clear();
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(product2FileBPath.toUtf8().constData()));
QMetaObject::invokeMethod(&apm, "AssetProcessed", Qt::QueuedConnection, Q_ARG(JobEntry, jobDetail.m_jobEntry), Q_ARG(AssetBuilderSDK::ProcessJobResponse, response));
@@ -2981,7 +2983,7 @@ namespace AssetProcessor
processResults.clear();
// Modify fingerprint of Job("FileA", "xxx", "pc") and analyze FileA again,
changeJobAFingerprint = false; // This will revert back the changes in the extra info used for fingerprinting of this job
changeJobAFingerprint = false; // This will revert back the changes in the extra info used for fingerprinting of this job
QMetaObject::invokeMethod(&apm, "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
@@ -3001,7 +3003,7 @@ namespace AssetProcessor
{
// Verify FileB jobinfo
UNIT_TEST_EXPECT_TRUE(QString(jobDetail.m_jobEntry.m_jobKey).compare("yyy") == 0);
}
else if (QString(jobDetail.m_jobEntry.m_pathRelativeToWatchFolder).endsWith("FileC.txt"))
{
@@ -3011,14 +3013,14 @@ namespace AssetProcessor
}
// Since one of the FileC job("FileC.txt","zzz") have emitted a job dependency on a FileB job("FileB.txt", "yyy")
// which also have a job dependency on a FileA job("FileA.txt", "xxx") therefore deleting File A source file should
// Since one of the FileC job("FileC.txt","zzz") have emitted a job dependency on a FileB job("FileB.txt", "yyy")
// which also have a job dependency on a FileA job("FileA.txt", "xxx") therefore deleting File A source file should
// cause both jobs (File B and File C) to be processed again.
processResults.clear();
QFile::remove(sourceFileAPath);
QMetaObject::invokeMethod(&apm, "AssessDeletedFile", Qt::QueuedConnection, Q_ARG(QString, sourceFileAPath));
UNIT_TEST_EXPECT_TRUE(BlockUntil(idling, 5000));
@@ -3053,7 +3055,7 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(BlockUntil(idling, 5000));
UNIT_TEST_EXPECT_TRUE(processResults.size() == 3);
for (JobDetails& jobDetail : processResults)
@@ -3091,15 +3093,14 @@ namespace AssetProcessor
// the canonicalization of the path here is to get around the fact that on some platforms
// the "temporary" folder location could be junctioned into some other folder and getting "QDir::current()"
// and other similar functions may actually return a different string but still be referring to the same folder
// and other similar functions may actually return a different string but still be referring to the same folder
QTemporaryDir dir;
QDir tempPath(dir.path());
QString canonicalTempDirPath = AssetUtilities::NormalizeDirectoryPath(tempPath.canonicalPath());
UnitTestUtils::ScopedDir changeDir(canonicalTempDirPath);
tempPath = QDir(canonicalTempDirPath);
// system is already actually initialized, along with gEnv, so this will always return that game name.
QString gameName = AssetUtilities::ComputeGameName(AssetProcessorManagerTestGameProject);
QString gameName = AssetUtilities::ComputeProjectName(AssetProcessorManagerTestGameProject);
// update the engine root
AssetUtilities::ResetAssetRoot();
@@ -3116,7 +3117,7 @@ namespace AssetProcessor
// note: the crux of this test is that we ar redirecting output into the cache at a different location instead of default.
// so our scan folder has a "redirected" folder.
config.AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder1"), "subfolder1", "subfolder1", "redirected", false, true, platforms, -1));
config.AddScanFolder(ScanFolderInfo(tempPath.filePath("subfolder1"), "subfolder1", "subfolder1", "redirected", false, true, platforms, -1));
AssetProcessorManager_Test apm(&config);
@@ -3173,7 +3174,7 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_watchFolderPath == tempPath.filePath("subfolder1"));
QStringList pcouts;
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/redirected/basefile.arc1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/redirected/basefile.arc1")));
// Create the product files for the first job
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "product1"));
@@ -3230,7 +3231,7 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_databaseSourceName == "redirected/basefile.foo");
UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_watchFolderPath == tempPath.absoluteFilePath("subfolder1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/redirected/basefile.arc1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/redirected/basefile.arc1")));
// Create the product files for the first job
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "product1"));
@@ -3260,7 +3261,7 @@ namespace AssetProcessor
assetMessages.clear();
changedInputResults.clear();
QString deletedProductPath = cacheRoot.filePath(QString("pc/") + gameName + "/redirected/basefile.arc1");
QString deletedProductPath = cacheRoot.filePath(QString("pc/redirected/basefile.arc1"));
QFile::remove(deletedProductPath);
@@ -3279,7 +3280,7 @@ namespace AssetProcessor
UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_databaseSourceName == "redirected/basefile.foo");
UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_watchFolderPath == tempPath.absoluteFilePath("subfolder1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/") + gameName + "/redirected/basefile.arc1"));
pcouts.push_back(cacheRoot.filePath(QString("pc/redirected/basefile.arc1")));
// Create the product files for the first job
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(pcouts[0], "product1"));
@@ -78,9 +78,8 @@ void AssetProcessorServerUnitTest::RunFirstPartOfUnitTestsForAssetProcessorServe
void AssetProcessorServerUnitTest::RunAssetProcessorConnectionStressTest(bool failNegotiation)
{
AZStd::string azAppRoot = AZStd::string(QDir::current().absolutePath().toUtf8().constData());
AZStd::string azBranchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, azBranchToken);
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, azBranchToken);
QString branchToken(azBranchToken.c_str());
@@ -99,7 +98,7 @@ void AssetProcessorServerUnitTest::RunAssetProcessorConnectionStressTest(bool fa
for (int idx = 0; idx < NUMBER_OF_TRIES; ++idx)
{
AzFramework::AssetSystem::AssetProcessorConnection connection;
connection.Configure(branchToken.toUtf8().data(), "pc", "UNITTEST", AssetUtilities::ComputeGameName().toUtf8().constData()); // UNITTEST identifier will skip the processID validation during negotiation
connection.Configure(branchToken.toUtf8().data(), "pc", "UNITTEST", AssetUtilities::ComputeProjectName().toUtf8().constData()); // UNITTEST identifier will skip the processID validation during negotiation
connection.Connect("127.0.0.1", FEATURE_TEST_LISTEN_PORT);
while (!connection.IsConnected() && !connection.NegotiationFailed())
{
@@ -268,7 +268,7 @@ namespace UnitTestUtils
m_localFileIO->SetAlias("@assets@", (newDir + QString("/ALIAS/assets")).toUtf8().constData());
m_localFileIO->SetAlias("@log@", (newDir + QString("/ALIAS/logs")).toUtf8().constData());
m_localFileIO->SetAlias("@cache@", (newDir + QString("/ALIAS/cache")).toUtf8().constData());
m_localFileIO->SetAlias("@usercache@", (newDir + QString("/ALIAS/cache")).toUtf8().constData());
m_localFileIO->SetAlias("@user@", (newDir + QString("/ALIAS/user")).toUtf8().constData());
m_localFileIO->SetAlias("@root@", (newDir + QString("/ALIAS/root")).toUtf8().constData());
}
@@ -35,7 +35,7 @@ using namespace AssetProcessor;
namespace AssetProcessor
{
const char* const TEST_BOOTSTRAP_DATA =
"sys_game_folder = TestProject \r\n\
"project_path = TestProject \r\n\
assets = pc \r\n\
-- ip and port of the asset processor.Only if you need to change defaults \r\n\
-- remote_ip = 127.0.0.1 \r\n\
@@ -13,6 +13,7 @@
#include "native/utilities/ApplicationManager.h"
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
@@ -83,7 +84,7 @@ namespace AssetProcessor
// we are in a job thread - return early to make it so that the global log file does not get this message
// there will also be a log listener in the actual job log thread which will get the message too, and that one
// will write it to the individual log.
return;
return;
}
AzFramework::LogComponent::OutputMessage(severity, window, message);
@@ -119,29 +120,14 @@ AssetProcessorAZApplication::AssetProcessorAZApplication(int* argc, char*** argv
*AZ::SettingsRegistry::Get(), AssetProcessorBuildTarget::GetBuildTargetName());
// Adding the PreModuleLoad event to the AssetProcessor application for logging when a gem loads
m_preModuleLoadHandler = AZ::ModuleManagerRequests::PreModuleLoadEvent::Handler{ []([[maybe_unused]] AZStd::string_view modulePath)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Loading (Gem) Module '%.*s'...\n", aznumeric_cast<int>(modulePath.size()), modulePath.data());
} };
m_preModuleLoadHandler = AZ::ModuleManagerRequests::PreModuleLoadEvent::Handler{
[]([[maybe_unused]] AZStd::string_view modulePath)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Loading (Gem) Module '%.*s'...\n", aznumeric_cast<int>(modulePath.size()), modulePath.data());
}
};
m_preModuleLoadHandler.Connect(m_moduleManager->m_preModuleLoadEvent);
// Override the /Amazon/AzCore/Bootstrap/sys_game_folder entry in the Settings Registry using the -gamefolder parameter
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (m_commandLine.GetNumSwitchValues("gamefolder") > 0)
{
const AZStd::string& gameFolderOverride = m_commandLine.GetSwitchValue("gamefolder", 0);
auto gameFolderCommandLineOverride = AZStd::string::format("--regset=%s/sys_game_folder=%s", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey,
gameFolderOverride.c_str());
AZ::CommandLine::ParamContainer commandLineArgs;
m_commandLine.Dump(commandLineArgs);
commandLineArgs.emplace_back(gameFolderCommandLineOverride);
m_commandLine.Parse(commandLineArgs);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*settingsRegistry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
}
}
AZ::ComponentTypeList AssetProcessorAZApplication::GetRequiredSystemComponents() const
@@ -155,7 +141,7 @@ AZ::ComponentTypeList AssetProcessorAZApplication::GetRequiredSystemComponents()
|| *iter == AZ::Uuid("{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}") // ScriptDebugAgent
|| *iter == AZ::Uuid("{CAF3A025-FAC9-4537-B99E-0A800A9326DF}") // InputSystemComponent
|| *iter == azrtti_typeid<AssetProcessor::ToolsAssetCatalogComponent>()
)
)
{
// AP does not require the above components to be active
iter = components.erase(iter);
@@ -274,7 +260,7 @@ void ApplicationManager::GetExternalBuilderFileList(QStringList& externalBuilder
if (externalBuilderModules.empty())
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "AssetProcessor was unable to locate any builders\n");
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "AssetProcessor was unable to locate any external builders\n");
}
}
@@ -284,9 +270,16 @@ QDir ApplicationManager::GetSystemRoot() const
{
return m_systemRoot;
}
QString ApplicationManager::GetGameName() const
QString ApplicationManager::GetProjectPath() const
{
return m_gameName;
auto projectPath = AZ::Utils::GetProjectPath();
if (!projectPath.empty())
{
return QString::fromUtf8(projectPath.c_str(), aznumeric_cast<int>(projectPath.size()));
}
AZ_Warning("AssetUtils", false, "Unable to obtain the Project Path from the settings registry.");
return {};
}
QCoreApplication* ApplicationManager::GetQtApplication()
@@ -451,7 +444,7 @@ void ApplicationManager::PopulateApplicationDependencies()
m_filesOfInterest.push_back(applicationPath);
// add some known-dependent files (this can be removed when they are no longer a dependency)
// Note that its not necessary for any of these files to actually exist. It is considered a "change" if they
// Note that its not necessary for any of these files to actually exist. It is considered a "change" if they
// change their file modtime, or if they go from existing to not existing, or if they go from not existing, to existing.
// any of those should cause AP to drop.
for (const QString& pathName : { "CrySystem",
@@ -475,11 +468,10 @@ void ApplicationManager::PopulateApplicationDependencies()
QDir assetRoot;
AssetUtilities::ComputeAssetRoot(assetRoot);
QString globalConfigPath = assetRoot.filePath("AssetProcessorPlatformConfig.ini");
QString globalConfigPath = assetRoot.filePath("AssetProcessorPlatformConfig.setreg");
m_filesOfInterest.push_back(globalConfigPath);
QString gameName = AssetUtilities::ComputeGameName();
QString gamePlatformConfigPath = assetRoot.filePath(gameName + "/AssetProcessorGamePlatformConfig.ini");
QString gamePlatformConfigPath = QDir(AssetUtilities::ComputeProjectPath()).filePath("AssetProcessorGamePlatformConfig.setreg");
m_filesOfInterest.push_back(gamePlatformConfigPath);
// add app modules
@@ -510,43 +502,14 @@ void ApplicationManager::PopulateApplicationDependencies()
}
}
bool ApplicationManager::StartAZFramework(QString appRootOverride)
bool ApplicationManager::StartAZFramework()
{
AzFramework::Application::Descriptor appDescriptor;
AZ::ComponentApplication::StartupParameters params;
QString gameName = AssetUtilities::ComputeGameName();
QString projectName = AssetUtilities::ComputeProjectName();
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
// Add the supplied gameName as specialization key in the registry
if (!gameName.isEmpty())
{
auto gameNameSpecialization = QString("%1/%2").arg(AZ::SettingsRegistryMergeUtils::SpecializationsRootKey).arg(gameName);
QByteArray specializationByteArray = gameNameSpecialization.toUtf8();
registry.Set(AZStd::string_view(specializationByteArray.data(), specializationByteArray.size()), true);
}
else
{
// Add the project name as a registry specialization
auto projectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/sys_game_folder", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
if (AZ::SettingsRegistryInterface::FixedValueString bootstrapProjectName; registry.Get(bootstrapProjectName, projectKey) && !bootstrapProjectName.empty())
{
registry.Set(AZ::SettingsRegistryInterface::FixedValueString::format("%s/%s",
AZ::SettingsRegistryMergeUtils::SpecializationsRootKey, bootstrapProjectName.c_str()),
true);
}
}
// The application will live in a bin folder one level up from the app root.
static char s_storageForRootPath[AZ_MAX_PATH_LEN] = { 0 };
if (!appRootOverride.isEmpty())
{
azstrcpy(s_storageForRootPath, AZ_MAX_PATH_LEN, appRootOverride.toUtf8().data());
params.m_appRootOverride = s_storageForRootPath;
}
else
{
params.m_appRootOverride = nullptr;
}
// Prevent loading of gems in the Create method of the ComponentApplication
params.m_loadDynamicModules = false;
@@ -556,31 +519,27 @@ bool ApplicationManager::StartAZFramework(QString appRootOverride)
AZ::Debug::Trace::HandleExceptions(true);
m_frameworkApp.Start(appDescriptor, params);
//Registering all the Components
m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
Reflect();
QDir engineRoot;
AssetUtilities::ComputeEngineRoot(engineRoot);
AZ::IO::FileIOBase::GetInstance()->SetAlias("@devroot@", engineRoot.absolutePath().toUtf8().data());
const AzFramework::CommandLine* commandLine = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
if (commandLine && commandLine->HasSwitch("logDir"))
{
AZ::IO::FileIOBase::GetInstance()->SetAlias("@log@", commandLine->GetSwitchValue("logDir", 0).c_str());
}
else
else if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
char executableDirectory[AZ_MAX_PATH_LEN];
if (AZ::Utils::GetExecutableDirectory(executableDirectory, AZStd::size(executableDirectory)) == AZ::Utils::ExecutablePathResult::Success)
{
AZ::IO::FileIOBase::GetInstance()->SetAlias("@log@", executableDirectory);
}
AZ::IO::Path projectUserPath;
settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath);
AZ::IO::Path logUserPath = projectUserPath / "log";
auto fileIo = AZ::IO::FileIOBase::GetInstance();
fileIo->SetAlias("@log@", logUserPath.c_str());
}
m_entity = aznew AZ::Entity("Application Entity");
if (m_entity)
@@ -622,7 +581,6 @@ bool ApplicationManager::ActivateModules()
AZ_Error(AssetProcessor::ConsoleChannel, false, "Cannot compute the asset root folder. Is AssetProcessor being run from the appropriate folder?");
return false;
}
assetRoot.cd(AssetUtilities::ComputeGameName());
m_frameworkApp.LoadDynamicModules();
return true;
@@ -633,89 +591,13 @@ void ApplicationManager::addRunningThread(AssetProcessor::ThreadWorker* thread)
m_runningThreads.push_back(thread);
}
QString ApplicationManager::ParseOptionAppRootArgument()
{
AZ_Assert(m_qApp!=nullptr,"m_qApp not initialized. QT application must be created before this call.")
// Parse any parameters.
static const char* app_root_parameter = "app-root";
static const char* app_root_parameter_desc = "Optional external path outside of the current engine to set as the application root.";
QCommandLineOption appRootPathOption(QString(app_root_parameter), tr(app_root_parameter_desc), QString("path"));
QCommandLineParser parser;
parser.setApplicationDescription("Asset Processor");
parser.addOption(appRootPathOption);
parser.parse(m_qApp->arguments());
QString appRootArgValue = parser.value(appRootPathOption);
appRootArgValue.remove(QChar('\"'));
return appRootArgValue.trimmed();
}
bool ApplicationManager::ValidateExternalAppRoot(QString appRootPath) const
{
static const char* bootstrap_cfg_name = "bootstrap.cfg";
QDir testAppRootPath(appRootPath);
// Make sure the path exists
if (!testAppRootPath.exists())
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. Directory does not exist.\n", appRootPath.toUtf8().data());
return false;
}
// Make sure the path contains bootstrap.cfg
if (!testAppRootPath.exists(bootstrap_cfg_name))
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. Directory does not contain %s.\n", appRootPath.toUtf8().data(), bootstrap_cfg_name);
return false;
}
// Make sure we can read the 'sys_game_folder' settings from bootstrap.cfg
QSettings settings(testAppRootPath.absoluteFilePath(bootstrap_cfg_name), QSettings::Format::IniFormat);
static const char* sysGameFolderKeyName = "sys_game_folder";
auto sysGameFolderSettings = settings.value(sysGameFolderKeyName);
if (!sysGameFolderSettings.isValid() || sysGameFolderSettings.isNull())
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. %s in the path is not valid.\n", appRootPath.toUtf8().data(), bootstrap_cfg_name);
return false;
}
// Make sure the 'sys_game_folder' value in the external bootstrap.cfg points to a valid subfolder in that path
QString sysGameFolder = sysGameFolderSettings.toString();
QDir gameFolderPath(appRootPath);
if (!gameFolderPath.cd(sysGameFolder))
{
AZ_Warning(AssetProcessor::ConsoleChannel, testAppRootPath.exists(), "Invalid Application Root path override (--app-root): %s. Configured Game folder %s in the path is not valid.\n", appRootPath.toUtf8().data(), sysGameFolder.toUtf8().data());
return false;
}
return true;
}
ApplicationManager::BeforeRunStatus ApplicationManager::BeforeRun()
{
// Create the Qt Application
CreateQtApplication();
// Calculate the override app root path if provided and validate it before passing it along
QString overrideAppRootPath = ParseOptionAppRootArgument();
if (!overrideAppRootPath.isEmpty())
{
if (ValidateExternalAppRoot(overrideAppRootPath))
{
QDir overrideAppRoot(overrideAppRootPath);
QDir resultAppRoot;
AssetUtilities::ComputeAssetRoot(resultAppRoot, &overrideAppRoot);
}
else
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Invalid override app root folder '%s'.", overrideAppRootPath.toUtf8().data());
return ApplicationManager::BeforeRunStatus::Status_Failure;
}
}
if (!StartAZFramework(overrideAppRootPath))
if (!StartAZFramework())
{
return ApplicationManager::BeforeRunStatus::Status_Failure;
}
@@ -742,14 +624,13 @@ bool ApplicationManager::Activate()
return false;
}
m_gameName = AssetUtilities::ComputeGameName();
if (m_gameName.isEmpty())
auto projectName = AssetUtilities::ComputeProjectName();
if (projectName.isEmpty())
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Unable to detect name of current game project. Is bootstrap.cfg appropriately configured?");
return false;
}
// the following controls what registry keys (or on mac or linux what entries in home folder) are used
// so they should not be translated!
qApp->setOrganizationName(GetOrganizationName());
@@ -101,7 +101,7 @@ public:
QCoreApplication* GetQtApplication();
QDir GetSystemRoot() const;
QString GetGameName() const;
QString GetProjectPath() const;
void RegisterComponentDescriptor(const AZ::ComponentDescriptor* descriptor);
@@ -163,9 +163,7 @@ protected:
virtual const char* GetLogBaseName() = 0;
virtual RegistryCheckInstructions PopupRegistryProblemsMessage(QString warningText) = 0;
private:
bool StartAZFramework(QString appRootOverride);
bool ValidateExternalAppRoot(QString appRootPath) const;
QString ParseOptionAppRootArgument();
bool StartAZFramework();
// QuitPair - Object pointer and "is ready" boolean pair.
typedef QPair<QObject*, bool> QuitPair;
@@ -178,7 +176,6 @@ private:
bool m_needRestart = false;
bool m_queuedCheckQuit = false;
QDir m_systemRoot;
QString m_gameName;
AZ::Entity* m_entity = nullptr;
};
@@ -337,13 +337,13 @@ bool ApplicationManagerBase::InitPlatformConfiguration()
m_platformConfiguration = new AssetProcessor::PlatformConfiguration();
QDir assetRoot;
AssetUtilities::ComputeAssetRoot(assetRoot);
return m_platformConfiguration->InitializeFromConfigFiles(GetSystemRoot().absolutePath(), assetRoot.absolutePath(), GetGameName());
return m_platformConfiguration->InitializeFromConfigFiles(GetSystemRoot().absolutePath(), assetRoot.absolutePath(), GetProjectPath());
}
bool ApplicationManagerBase::InitBuilderConfiguration()
{
m_builderConfig = AZStd::make_unique<AssetProcessor::BuilderConfigurationManager>();
QString configFile = GetSystemRoot().absoluteFilePath(GetGameName() + "/" + AssetProcessor::BuilderConfigFile);
QString configFile = QDir(GetProjectPath()).absoluteFilePath(AssetProcessor::BuilderConfigFile);
if (!QFile::exists(configFile))
{
@@ -1193,7 +1193,8 @@ bool ApplicationManagerBase::Activate()
return false;
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "AssetProcessor will process assets from gameproject %s.\n", AssetUtilities::ComputeGameName().toUtf8().data());
AZ_TracePrintf(AssetProcessor::ConsoleChannel,
"AssetProcessor will process assets from project root %s.\n", AssetUtilities::ComputeProjectPath().toUtf8().data());
// Shutdown if the disk has less than 128MB of free space
if (!CheckSufficientDiskSpace(projectCache.absolutePath(), 128 * 1024 * 1024, true))
@@ -1219,7 +1220,7 @@ bool ApplicationManagerBase::Activate()
if (!InitPlatformConfiguration())
{
AZ_Error("AssetProcessor", false, "Failed to Initialize from AssetProcessorPlatformConfig.ini - check the log files in the logs/ subfolder for more information.");
AZ_Error("AssetProcessor", false, "Failed to Initialize from AssetProcessorPlatformConfig.setreg - check the log files in the logs/ subfolder for more information.");
return false;
}
@@ -1401,7 +1402,7 @@ bool ApplicationManagerBase::InitializeExternalBuilders()
return true;
}
bool ApplicationManagerBase::WaitForBuilderExit(AzToolsFramework::ProcessWatcher* processWatcher, AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds)
bool ApplicationManagerBase::WaitForBuilderExit(AzFramework::ProcessWatcher* processWatcher, AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds)
{
AZ::u32 exitCode = 0;
bool finishedOK = false;
@@ -177,7 +177,7 @@ protected:
AssetProcessor::AssetCatalog* GetAssetCatalog() const { return m_assetCatalog; }
static bool WaitForBuilderExit(AzToolsFramework::ProcessWatcher* processWatcher, AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds);
static bool WaitForBuilderExit(AzFramework::ProcessWatcher* processWatcher, AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds);
ApplicationServer* m_applicationServer = nullptr;
ConnectionManager* m_connectionManager = nullptr;
@@ -142,10 +142,6 @@ namespace AssetProcessor
bool Builder::Start()
{
// Get the app root to locate the builders
AZStd::string appRootString;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRootString, &AzFramework::ApplicationRequests::GetAppRoot);
// Get the current BinXXX folder based on the current running AP
QString applicationDir = QCoreApplication::instance()->applicationDirPath();
@@ -190,24 +186,26 @@ namespace AssetProcessor
QDir projectCacheRoot;
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
QString gameName = AssetUtilities::ComputeGameName();
AZStd::string appRootString;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRootString, &AzFramework::ApplicationRequests::GetAppRoot);
QDir appRoot(QString(appRootString.c_str()));
QString gameRoot = appRoot.absoluteFilePath(gameName);
QString gameName = AssetUtilities::ComputeProjectName();
QString projectPath = AssetUtilities::ComputeProjectPath();
QDir engineRoot;
AssetUtilities::ComputeEngineRoot(engineRoot);
int portNumber = 0;
ApplicationServerBus::BroadcastResult(portNumber, &ApplicationServerBus::Events::GetServerListeningPort);
AZStd::string params;
#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
params = AZStd::string::format(R"(-task=%s -id="%s" -gamename="%s" -gamecache="%s" -gameroot="%s" -port %d)",
task, builderGuid.c_str(), gameName.toUtf8().constData(), projectCacheRoot.absolutePath().toUtf8().constData(), gameRoot.toUtf8().constData(), portNumber);
#else
params = AZStd::string::format(R"(-task=%s -id="%s" -gamename="\"%s\"" -gamecache="\"%s\"" -gameroot="\"%s\"" -port %d)",
task, builderGuid.c_str(), gameName.toUtf8().constData(), projectCacheRoot.absolutePath().toUtf8().constData(), gameRoot.toUtf8().constData(), portNumber);
#endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
params = AZStd::string::format(
R"(-task=%s -id="%s" -project-name="%s" -project-cache-path="%s" -project-path="%s" -engine-path="%s" -port %d)", task,
builderGuid.c_str(), gameName.toUtf8().constData(), projectCacheRoot.absolutePath().toUtf8().constData(),
projectPath.toUtf8().constData(), engineRoot.absolutePath().toUtf8().constData(), portNumber);
#else
params = AZStd::string::format(
R"(-task=%s -id="%s" -project-name="\"%s\"" -project-cache-path="\"%s\"" -project-path="\"%s\"" -engine-path="\"%s\"" -port %d)",
task, builderGuid.c_str(), gameName.toUtf8().constData(), projectCacheRoot.absolutePath().toUtf8().constData(),
projectPath.toUtf8().constData(), engineRoot.absolutePath().toUtf8().constData(), portNumber);
#endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
if (moduleFilePath && moduleFilePath[0])
{
@@ -230,17 +228,17 @@ namespace AssetProcessor
return params;
}
AZStd::unique_ptr<AzToolsFramework::ProcessWatcher> Builder::LaunchProcess(const char* fullExePath, const AZStd::string& params) const
AZStd::unique_ptr<AzFramework::ProcessWatcher> Builder::LaunchProcess(const char* fullExePath, const AZStd::string& params) const
{
AzToolsFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_processExecutableString = fullExePath;
processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" %s", fullExePath, params.c_str());
processLaunchInfo.m_showWindow = false;
processLaunchInfo.m_processPriority = AzToolsFramework::ProcessPriority::PROCESSPRIORITY_IDLE;
processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_IDLE;
AZ_TracePrintf(AssetProcessor::DebugChannel, "Executing AssetBuilder with parameters: %s\n", processLaunchInfo.m_commandlineParameters.c_str());
auto processWatcher = AZStd::unique_ptr<AzToolsFramework::ProcessWatcher>(AzToolsFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzToolsFramework::COMMUNICATOR_TYPE_STDINOUT));
auto processWatcher = AZStd::unique_ptr<AzFramework::ProcessWatcher>(AzFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT));
AZ_Error(AssetProcessor::ConsoleChannel, processWatcher, "Failed to start %s", fullExePath);
@@ -13,7 +13,7 @@
#include <AzCore/std/string/string.h>
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzToolsFramework/Process/ProcessWatcher.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <QString>
@@ -108,7 +108,7 @@ namespace AssetProcessor
void SetConnection(AZ::u32 connId);
AZStd::string BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const;
AZStd::unique_ptr<AzToolsFramework::ProcessWatcher> LaunchProcess(const char* fullExePath, const AZStd::string& params) const;
AZStd::unique_ptr<AzFramework::ProcessWatcher> LaunchProcess(const char* fullExePath, const AZStd::string& params) const;
//! Waits for the builder exe to send the job response and pumps stdout/err
BuilderRunJobOutcome WaitForBuilderResponse(AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds, AZStd::binary_semaphore* waitEvent) const;
@@ -128,7 +128,7 @@ namespace AssetProcessor
AZStd::binary_semaphore m_connectionEvent;
//! Optional process watcher
AZStd::unique_ptr<AzToolsFramework::ProcessWatcher> m_processWatcher = nullptr;
AZStd::unique_ptr<AzFramework::ProcessWatcher> m_processWatcher = nullptr;
//! Optional communicator, only available if we have a process watcher
AZStd::unique_ptr<CommunicatorTracePrinter> m_tracePrinter = nullptr;
@@ -12,7 +12,7 @@
#include "CommunicatorTracePrinter.h"
CommunicatorTracePrinter::CommunicatorTracePrinter(AzToolsFramework::ProcessCommunicator* communicator, const char* window) :
CommunicatorTracePrinter::CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window) :
m_communicator(communicator),
m_window(window)
{
@@ -12,14 +12,14 @@
#pragma once
#include <AzToolsFramework/Process/ProcessCommunicator.h>
#include <AzFramework/Process/ProcessCommunicator.h>
//! CommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system
//! Importantly, it does not do any blocking operations.
class CommunicatorTracePrinter
{
public:
CommunicatorTracePrinter(AzToolsFramework::ProcessCommunicator* communicator, const char* window);
CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window);
~CommunicatorTracePrinter();
// call this periodically to drain the buffers and write them.
@@ -32,7 +32,7 @@ public:
private:
AZStd::string m_window;
AzToolsFramework::ProcessCommunicator* m_communicator;
AzFramework::ProcessCommunicator* m_communicator;
char m_streamBuffer[128];
AZStd::string m_stringBeingConcatenated;
AZStd::string m_errorStringBeingConcatenated;
@@ -130,37 +130,36 @@ ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun()
m_qtFileWatcher.addPath(assetDbPath);
// if our Gems file changes, make sure we watch that, too.
QString gameName = AssetUtilities::ComputeGameName();
QString gemsConfigFile = devRoot.filePath(gameName + "/gems.json");
m_qtFileWatcher.addPath(gemsConfigFile);
QString projectPath = AssetUtilities::ComputeProjectPath();
QObject::connect(&m_qtFileWatcher, &QFileSystemWatcher::fileChanged, this, &GUIApplicationManager::FileChanged);
QObject::connect(&m_qtFileWatcher, &QFileSystemWatcher::directoryChanged, this, &GUIApplicationManager::DirectoryChanged);
// Register a notifier for when the sys_game_folder property changes within the SettingsRegistry
// Register a notifier for when the project_path property changes within the SettingsRegistry
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
auto onBootStrapGameFolderChanged = [this, cachedGameName = gameName](AZStd::string_view path, AZ::SettingsRegistryInterface::Type type)
// Needs to be updated to project_path.
auto OnProjectPathChanged = [this, cachedProjectPath = projectPath](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
constexpr auto projectKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
+ "/sys_game_folder";
if (projectKey == path && type == AZ::SettingsRegistryInterface::Type::String)
constexpr auto projectPathKey =
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
+ "/project_path";
if (projectPathKey == path)
{
auto registry = AZ::SettingsRegistry::Get();
AZ::SettingsRegistryInterface::FixedValueString newGameName;
if (registry->Get(newGameName, path))
AZ::SettingsRegistryInterface::FixedValueString newProjectPath;
if (auto registry = AZ::SettingsRegistry::Get(); registry && registry->Get(newProjectPath, path))
{
// we only have to quit if the actual project name has changed, not if just the bootstrap has changed.
if (cachedGameName.compare(newGameName.c_str()) != 0)
// we only have to quit if the project path has changed, not if just the bootstrap has changed.
if (cachedProjectPath.compare(newProjectPath.c_str()) != 0)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Bootstrap.cfg Game Name changed from %s to %s. Quitting\n", cachedGameName.toUtf8().constData(), newGameName.c_str());
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "bootstrap.cfg Project Path changed from %s to %s. Quitting\n",
cachedProjectPath.toUtf8().constData(), newProjectPath.c_str());
QMetaObject::invokeMethod(this, "QuitRequested", Qt::QueuedConnection);
}
}
}
};
m_bootstrapGameFolderChangedHandler = settingsRegistry->RegisterNotifier(AZStd::move(onBootStrapGameFolderChanged));
m_bootstrapGameFolderChangedHandler = settingsRegistry->RegisterNotifier(AZStd::move(OnProjectPathChanged));
}
return ApplicationManager::BeforeRunStatus::Status_Success;
@@ -189,8 +188,13 @@ bool GUIApplicationManager::Run()
qRegisterMetaType<AZ::u32>("AZ::u32");
qRegisterMetaType<AZ::Uuid>("AZ::Uuid");
AZ::IO::FixedMaxPath engineRootPath;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
AzQtComponents::StyleManager* styleManager = new AzQtComponents::StyleManager(qApp);
styleManager->Initialize(qApp);
styleManager->initialize(qApp, engineRootPath);
QDir engineRoot;
AssetUtilities::ComputeAssetRoot(engineRoot);
@@ -198,7 +202,8 @@ bool GUIApplicationManager::Run()
AzQtComponents::StyleManager::addSearchPaths(
QStringLiteral("style"),
engineRoot.filePath(QStringLiteral("Code/Tools/AssetProcessor/native/ui/style")),
QStringLiteral(":/AssetProcessor/style"));
QStringLiteral(":/AssetProcessor/style"),
engineRootPath);
m_mainWindow = new MainWindow(this);
auto wrapper = new AzQtComponents::WindowDecorationWrapper(
@@ -485,7 +490,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
QVariant settingValue = loader.value("Game Projects/enabled_game_projects");
QStringList compiledProjects = settingValue.toStringList();
if(compiledProjects.isEmpty())
if (compiledProjects.isEmpty())
{
QByteArray byteArray;
QFile jsonFile;
@@ -497,12 +502,12 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
QJsonObject settingsObject = QJsonDocument::fromJson(byteArray).object();
QJsonArray projectsArray = settingsObject["Game Projects"].toArray();
if(!projectsArray.isEmpty())
if (!projectsArray.isEmpty())
{
auto projectObject = projectsArray[0].toObject();
QString projects = projectObject["default_value"].toString();
if(!projects.isEmpty())
if (!projects.isEmpty())
{
compiledProjects = projects.split(',');
usingDefaults = true;
@@ -515,13 +520,13 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
compiledProjects[i] = compiledProjects[i].trimmed();
}
QString enabledProject = AssetUtilities::ComputeGameName();
QString enabledProject = AssetUtilities::ComputeProjectName();
if(!compiledProjects.contains(enabledProject))
if (!compiledProjects.contains(enabledProject))
{
QString projectSourceLine;
if(usingDefaults)
if (usingDefaults)
{
projectSourceLine = QString("The currently compiled projects according to the defaults in %1 are '%2'").arg(defaultSettingsFile);
}
@@ -531,7 +536,6 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
}
projectSourceLine = projectSourceLine.arg(compiledProjects.join(", "));
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
"The enabled game project is not in the list of compiled projects.\n"
"Please configure the enabled project to be compiled and rebuild or change the enabled project.\n"
@@ -539,13 +543,12 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
"%2\n"
"Full error text:\n"
"%3"
).arg(enabledProject).arg(projectSourceLine).arg(message).arg(AssetUtilities::GameFolderOverrideParameter);
).arg(enabledProject).arg(projectSourceLine).arg(message).arg(AssetUtilities::ProjectPathOverrideParameter);
}
}
if(friendlyErrorMessage.isEmpty())
if (friendlyErrorMessage.isEmpty())
{
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
"This can happen when new gems are added to a project, but those gems need to be built in order to function.\n"
"This can also happen when switching to a different project, one which uses gems which are not yet built.\n"
@@ -631,34 +634,18 @@ void GUIApplicationManager::CreateQtApplication()
m_qApp = new QApplication(*m_frameworkApp.GetArgC(), *m_frameworkApp.GetArgV());
}
void GUIApplicationManager::DirectoryChanged(QString path)
void GUIApplicationManager::DirectoryChanged([[maybe_unused]] QString path)
{
AZ_UNUSED(path);
QDir devRoot = ApplicationManager::GetSystemRoot();
QString cacheRoot = devRoot.filePath("Cache");
if (!QDir(cacheRoot).exists())
QDir projectCacheRoot;
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
if (!projectCacheRoot.exists() || !projectCacheRoot.exists("assetdb.sqlite"))
{
//Cache directory is removed we need to restart
// If either the Cache directory or database file has been removed, we need to restart
QTimer::singleShot(200, this, [this]()
{
QMetaObject::invokeMethod(this, "Restart", Qt::QueuedConnection);
});
}
else
{
QDir projectCacheRoot;
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
QString assetDbPath = projectCacheRoot.filePath("assetdb.sqlite");
if (!QFile::exists(assetDbPath))
{
// even if cache directory exists but the the database file is missing we need to restart
QTimer::singleShot(200, this, [this]()
{
QMetaObject::invokeMethod(this, "Restart", Qt::QueuedConnection);
});
}
}
}
void GUIApplicationManager::FileChanged(QString path)
@@ -696,70 +683,6 @@ void GUIApplicationManager::FileChanged(QString path)
});
}
}
else if (AssetUtilities::NormalizeFilePath(path).endsWith("gems.json", Qt::CaseInsensitive))
{
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> oldGemsList = GetPlatformConfiguration()->GetGemsInformation();
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> newGemsList;
QDir assetRoot;
AssetUtilities::ComputeAssetRoot(assetRoot);
AzToolsFramework::AssetUtils::GetGemsInfo(GetSystemRoot().absolutePath().toUtf8().constData(), assetRoot.absolutePath().toUtf8().constData(), GetGameName().toUtf8().constData(), newGemsList);
for (auto oldGemIter = oldGemsList.begin(); oldGemIter != oldGemsList.end();)
{
bool gemMatch = false;
for (auto newGemIter = newGemsList.begin(); newGemIter != newGemsList.end();)
{
if (AzFramework::StringFunc::Equal(oldGemIter->m_identifier.c_str(), newGemIter->m_identifier.c_str()))
{
gemMatch = true;
newGemIter = newGemsList.erase(newGemIter);
break;
}
newGemIter++;
}
if (gemMatch)
{
oldGemIter = oldGemsList.erase(oldGemIter);
}
else
{
oldGemIter++;
}
}
// oldGemslist should contain the list of gems that got removed and newGemsList should contain the list of gems that were added to the project
// if the project requires to be built again then we will quit otherwise we can restart
bool exitApp = false;
for (const AzToolsFramework::AssetUtils::GemInfo& gemInfo : newGemsList)
{
if (!gemInfo.m_assetOnly)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Gem %s was added to the project and require building. Quitting\n", gemInfo.m_gemName.c_str());
exitApp = true;
}
}
if (exitApp)
{
QuitRequested();
}
else
{
if (oldGemsList.size() || newGemsList.size())
{
if (oldGemsList.size())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Gem(s) were removed from the project. Restarting\n");
}
else if (newGemsList.size())
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Assets only gem(s) were added to the project. Restarting\n");
}
QMetaObject::invokeMethod(this, "Restart", Qt::QueuedConnection);
}
}
}
}
bool GUIApplicationManager::InitApplicationServer()
@@ -20,43 +20,48 @@ AZ_POP_DISABLE_WARNING
#include "native/AssetDatabase/AssetDatabase.h"
#include "native/assetprocessor.h"
#include <AzCore/Component/TickBus.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/FileTag/FileTagBus.h>
#include <AzCore/std/string/wildcard.h>
#include <AzCore/XML/rapidxml.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/FileTag/FileTag.h>
#include <AzFramework/FileTag/FileTagBus.h>
#include <AzFramework/IO/LocalFileIO.h>
namespace AssetProcessor
{
const char* EngineFolder = "Engine";
AZStd::string GetXMLDependenciesFile(const AZStd::string& fullPath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AZStd::string& tokenName)
AZStd::string GetXMLDependenciesFile(const AZStd::string& fullPath, const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AZStd::string& tokenName)
{
AZStd::string xmlDependenciesFileFullPath;
AZ::IO::Path xmlDependenciesFileFullPath;
tokenName = EngineFolder;
for (const AzToolsFramework::AssetUtils::GemInfo& gemElement : gemInfoList)
for (const AzFramework::GemInfo& gemElement : gemInfoList)
{
if (AzFramework::StringFunc::StartsWith(fullPath.c_str(), gemElement.m_absoluteFilePath.c_str()) || AzFramework::StringFunc::Equal(gemElement.m_absoluteFilePath.c_str(), fullPath.c_str()))
for (const AZ::IO::Path& absoluteSourcePath : gemElement.m_absoluteSourcePaths)
{
AZStd::string fileName = AZStd::string::format("%s_Dependencies.xml", gemElement.m_gemName.c_str());
AzFramework::StringFunc::Path::ConstructFull(gemElement.m_absoluteFilePath.c_str(), AzToolsFramework::AssetUtils::GemInfo::GetGemAssetFolder().c_str(), fileName.c_str() , "xml", xmlDependenciesFileFullPath);
if (AZ::IO::FileIOBase::GetInstance()->Exists(xmlDependenciesFileFullPath.c_str()))
if (AZ::StringFunc::StartsWith(fullPath, absoluteSourcePath.Native()) || AZ::StringFunc::Equal(absoluteSourcePath.Native(), fullPath))
{
tokenName = gemElement.m_gemName;
return xmlDependenciesFileFullPath;
xmlDependenciesFileFullPath /= AzFramework::GemInfo::GetGemAssetFolder();
xmlDependenciesFileFullPath /= AZStd::string::format("%s_Dependencies.xml", gemElement.m_gemName.c_str());;
if (AZ::IO::FileIOBase::GetInstance()->Exists(xmlDependenciesFileFullPath.c_str()))
{
tokenName = gemElement.m_gemName;
return xmlDependenciesFileFullPath.Native();
}
}
}
}
// if we are here than either the %gemName%_Dependencies.xml file does not exists or the user inputted path is not inside a gems folder,
// in both the cases we will return the engine dependencies file
const char* devRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devroot@");
AzFramework::StringFunc::Path::ConstructFull(devRoot, EngineFolder, "Engine_Dependencies.xml", "xml", xmlDependenciesFileFullPath);
xmlDependenciesFileFullPath = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devroot@");
xmlDependenciesFileFullPath /= EngineFolder;
xmlDependenciesFileFullPath /= "Engine_Dependencies.xml";
return xmlDependenciesFileFullPath;
return xmlDependenciesFileFullPath.Native();
}
const int MissingDependencyScanner::DefaultMaxScanIteration = 800;
@@ -731,7 +736,7 @@ namespace AssetProcessor
}
}
bool MissingDependencyScanner::PopulateRulesForScanFolder(const AZStd::string& scanFolderPath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AZStd::string& dependencyTokenName)
bool MissingDependencyScanner::PopulateRulesForScanFolder(const AZStd::string& scanFolderPath, const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AZStd::string& dependencyTokenName)
{
AZStd::string xmlDependenciesFullFilePath = GetXMLDependenciesFile(scanFolderPath, gemInfoList, dependencyTokenName);
if (xmlDependenciesFullFilePath.empty())
@@ -131,7 +131,7 @@ namespace AssetProcessor
void RegisterSpecializedScanner(AZStd::shared_ptr<SpecializedDependencyScanner> scanner);
bool PopulateRulesForScanFolder(const AZStd::string& scanFolderPath, const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList, AZStd::string& dependencyTokenName);
bool PopulateRulesForScanFolder(const AZStd::string& scanFolderPath, const AZStd::vector<AzFramework::GemInfo>& gemInfoList, AZStd::string& dependencyTokenName);
protected:
bool RunScan(
File diff suppressed because it is too large Load Diff
@@ -22,6 +22,7 @@
#include <QVector>
#include <QSet>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/string/string.h>
#include <native/utilities/assetUtils.h>
#include <native/AssetManager/assetScanFolderInfo.h>
@@ -29,10 +30,15 @@
#include <AzToolsFramework/Asset/AssetUtils.h>
#endif
class QSettings;
namespace AZ
{
class SettingsRegistryInterface;
}
namespace AssetProcessor
{
inline constexpr const char* AssetProcessorSettingsKey{ "/Amazon/AssetProcessor/Settings" };
class PlatformConfiguration;
class ScanFolderInfo;
extern const char AssetConfigPlatformDir[];
@@ -105,6 +111,73 @@ namespace AssetProcessor
virtual const ExcludeRecognizerContainer& GetExcludeAssetRecognizerContainer() const = 0;
};
//! Visitor for reading the "/Amazon/AssetProcessor/Settings/ScanFolder *" entries from the Settings Registry
//! Expects the key to path to the visitor to be "/Amazon/AssetProcessor/Settings"
struct ScanFolderVisitor
: AZ::SettingsRegistryInterface::Visitor
{
AZ::SettingsRegistryInterface::VisitResponse Traverse(AZStd::string_view jsonPath, AZStd::string_view valueName,
AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type) override;
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZ::s64 value) override;
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value) override;
struct ScanFolderInfo
{
AZStd::string m_scanFolderIdentifier;
AZStd::string m_scanFolderDisplayName;
AZ::IO::Path m_watchPath{ AZ::IO::PosixPathSeparator };
AZStd::vector<AZStd::string> m_includeIdentifiers;
AZStd::vector<AZStd::string> m_excludeIdentifiers;
AZStd::string m_outputPrefix;
int m_scanOrder{};
bool m_isRecursive{};
};
AZStd::vector<ScanFolderInfo> m_scanFolderInfos;
private:
AZStd::stack<AZStd::string> m_scanFolderStack;
};
struct ExcludeVisitor
: AZ::SettingsRegistryInterface::Visitor
{
AZ::SettingsRegistryInterface::VisitResponse Traverse(AZStd::string_view jsonPath, AZStd::string_view valueName,
AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type) override;
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value) override;
AZStd::vector<ExcludeAssetRecognizer> m_excludeAssetRecognizers;
private:
AZStd::stack<AZStd::string> m_excludeNameStack;
};
struct RCVisitor
: AZ::SettingsRegistryInterface::Visitor
{
RCVisitor(const AZ::SettingsRegistryInterface& settingsRegistry, const AZStd::vector<AssetBuilderSDK::PlatformInfo>& enabledPlatforms)
: m_registry(settingsRegistry)
, m_enabledPlatforms(enabledPlatforms)
{
}
AZ::SettingsRegistryInterface::VisitResponse Traverse(AZStd::string_view jsonPath, AZStd::string_view valueName,
AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type) override;
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, bool value) override;
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZ::s64 value) override;
void Visit(AZStd::string_view path, AZStd::string_view valueName, AZ::SettingsRegistryInterface::Type, AZStd::string_view value) override;
struct RCAssetRecognizer
{
AssetRecognizer m_recognizer;
AZStd::string m_defaultParams;
bool m_ignore{};
};
AZStd::vector<RCAssetRecognizer> m_assetRecognizers;
private:
void ApplyParamsOverrides(AZStd::string_view path);
AZStd::stack<AZStd::string> m_rcNameStack;
const AZ::SettingsRegistryInterface& m_registry;
const AZStd::vector<AssetBuilderSDK::PlatformInfo>& m_enabledPlatforms;
};
/** Reads the platform ini configuration file to determine
* platforms for which assets needs to be build
*/
@@ -127,22 +200,23 @@ namespace AssetProcessor
* Note that order of the config files is relevant - later files override settings in
* files that are earlier.
**/
bool InitializeFromConfigFiles(QString absoluteSystemRoot, QString absoluteAssetRoot, QString gameName, bool addPlatformConfigs = true, bool addGemsConfigs = true);
bool InitializeFromConfigFiles(const QString& absoluteSystemRoot, const QString& absoluteAssetRoot, const QString& projectPath, bool addPlatformConfigs = true, bool addGemsConfigs = true);
QString PlatformName(unsigned int platformCrc) const;
QString RendererName(unsigned int rendererCrc) const;
//! Merge an AssetProcessor*Config.ini path to the Settings Registry
//! The settings are anchored underneath the AssetProcessor::AssetProcessorSettingsKey JSON pointer
static bool MergeConfigFileToSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry, const AZ::IO::PathView& filePathView);
const AZStd::vector<AssetBuilderSDK::PlatformInfo>& GetEnabledPlatforms() const;
const AssetBuilderSDK::PlatformInfo* const GetPlatformByIdentifier(const char* identifier) const;
//! Add AssetProcessor config files from platform specific folders
bool AddPlatformConfigFilePaths(QStringList& configList);
bool AddPlatformConfigFilePaths(AZStd::vector<AZ::IO::Path>& configList);
int MetaDataFileTypesCount() const { return m_metaDataFileTypes.count(); }
// Metadata file types are (meta file extension, original file extension - or blank if its tacked on the end instead of replacing).
// so for example if its
// blah.tif + blah.tif.metadata, then its ("metadata", "")
// but if its blah.tif + blah.metadata (rplacing tif, data is lost) then its ("metadata", "tif")
// but if its blah.tif + blah.metadata (replacing tif, data is lost) then its ("metadata", "tif")
QPair<QString, QString> GetMetaDataFileTypeAt(int pos) const;
// Metadata extensions can also be a real file, to create a dependency on file types if a specific file changes
@@ -160,7 +234,7 @@ namespace AssetProcessor
int GetScanFolderCount() const;
//! Return the gems info list
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> GetGemsInformation() const;
AZStd::vector<AzFramework::GemInfo> GetGemsInformation() const;
//! Retrieve the scan folder at a given index.
AssetProcessor::ScanFolderInfo& GetScanFolderAt(int index);
@@ -249,18 +323,18 @@ namespace AssetProcessor
protected:
// call this first, to populate the list of platform informations
void ReadPlatformInfosFromConfigFile(QString fileSource);
void ReadPlatformInfosFromSettingsRegistry();
// call this next, in order to find out what platforms are enabled
void PopulateEnabledPlatforms(QStringList configFiles);
// finaly, call this, in order to delete the platforminfos for non-enabled platforms
void PopulateEnabledPlatforms();
// finally, call this, in order to delete the platforminfos for non-enabled platforms
void FinalizeEnabledPlatforms();
// iterate over all the gems and add their folders to the "scan folders" list as appropriate.
void AddGemScanFolders(const AZStd::vector<AzToolsFramework::AssetUtils::GemInfo>& gemInfoList);
void AddGemScanFolders(const AZStd::vector<AzFramework::GemInfo>& gemInfoList);
void ReadEnabledPlatformsFromConfigFile(QString fileSource);
bool ReadRecognizersFromConfigFile(QString fileSource, bool skipScanFolders = false, QStringList scanFolderPatterns = QStringList() );
void ReadMetaDataFromConfigFile(QString fileSource);
void ReadEnabledPlatformsFromSettingsRegistry();
bool ReadRecognizersFromSettingsRegistry(const QString& assetRoot, bool skipScanFolders = false, QStringList scanFolderPatterns = QStringList() );
void ReadMetaDataFromSettingsRegistry();
private:
AZStd::vector<AssetBuilderSDK::PlatformInfo> m_enabledPlatforms;
@@ -269,15 +343,13 @@ namespace AssetProcessor
AZStd::vector<AssetProcessor::ScanFolderInfo> m_scanFolders;
QList<QPair<QString, QString> > m_metaDataFileTypes;
QSet<QString> m_metaDataRealFiles;
AZStd::vector<AzToolsFramework::AssetUtils::GemInfo> m_gemInfoList;
AZStd::vector<AzFramework::GemInfo> m_gemInfoList;
int m_minJobs = 1;
int m_maxJobs = 3;
// used only during file read, keeps the total running list of all the enabled platforms from all config files and command lines
QStringList m_tempEnabledPlatforms;
bool ReadRecognizerFromConfig(AssetRecognizer& target, QSettings& loader); // assumes the group is already selected
AZStd::vector<AZStd::string> m_tempEnabledPlatforms;
///! if non-empty, fatalError contains the error that occurred during read.
///! it will be printed out to the log when
@@ -22,7 +22,6 @@
#include <QElapsedTimer>
#include <QTemporaryDir>
#include <QTextStream>
#include <QSettings>
#include <QTimeZone>
#include <QRandomGenerator>
@@ -38,7 +37,9 @@
#include <AzCore/JSON/document.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzToolsFramework/UI/Logging/LogLine.h>
#include <xxhash/xxhash.h>
@@ -77,7 +78,7 @@ namespace AssetUtilsInternal
{
if (waitTimeInSeconds < 0)
{
AZ_Warning("Asset Processor", waitTimeInSeconds >= 0, "Invalid timeout specified by the user")
AZ_Warning("Asset Processor", waitTimeInSeconds >= 0, "Invalid timeout specified by the user");
waitTimeInSeconds = 0;
}
bool failureOccurredOnce = false; // used for logging.
@@ -152,12 +153,64 @@ namespace AssetUtilsInternal
return true;
}
static bool DumpAssetProcessorUserSettingsToFile(AZ::SettingsRegistryInterface& settingsRegistry,
const AZ::IO::FixedMaxPath& setregPath)
{
// The AssetProcessor settings are currently under the Bootstrap object(This may change in the future
constexpr AZStd::string_view AssetProcessorUserSettingsRootKey = AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey;
AZStd::string apSettingsJson;
AZ::IO::ByteContainerStream apSettingsStream(&apSettingsJson);
AZ::SettingsRegistryMergeUtils::DumperSettings apDumperSettings;
apDumperSettings.m_prettifyOutput = true;
apDumperSettings.m_includeFilter = [&AssetProcessorUserSettingsRootKey](AZStd::string_view path)
{
// The AssetUtils only updates the following keys in the registry
// Dump them all out to the setreg file
auto allowedListKey = AZ::SettingsRegistryInterface::FixedValueString(AssetProcessorUserSettingsRootKey)
+ "/allowed_list";
auto branchTokenKey = AZ::SettingsRegistryInterface::FixedValueString(AssetProcessorUserSettingsRootKey)
+ "/assetProcessor_branch_token";
// The objects leading up to the keys to dump must be included in order the keys to be dumped
return allowedListKey.starts_with(path.substr(0, allowedListKey.size()))
|| branchTokenKey.starts_with(path.substr(0, branchTokenKey.size()));
};
apDumperSettings.m_jsonPointerPrefix = AssetProcessorUserSettingsRootKey;
if (AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(settingsRegistry, AssetProcessorUserSettingsRootKey,
apSettingsStream, apDumperSettings))
{
constexpr auto modeFlags = AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY | AZ::IO::SystemFile::SF_OPEN_CREATE
| AZ::IO::SystemFile::SF_OPEN_CREATE_PATH;
if (AZ::IO::SystemFile apSetregFile; apSetregFile.Open(setregPath.c_str(), modeFlags))
{
size_t bytesWritten = apSetregFile.Write(apSettingsJson.data(), apSettingsJson.size());
return bytesWritten == apSettingsJson.size();
}
else
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to open AssetProcessor user setreg file (%s)\n", setregPath.c_str());
}
}
else
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Dump of AssetProcessor User Settings failed at JSON pointer %.*s \n",
aznumeric_cast<int>(AssetProcessorUserSettingsRootKey.size()), AssetProcessorUserSettingsRootKey.data());
}
return false;
}
}
namespace AssetUtilities
{
constexpr AZStd::string_view AssetProcessorUserSetregRelPath = "user/Registry/asset_processor.setreg";
// do not place Qt objects in global scope, they allocate and refcount threaded data.
AZ::SettingsRegistryInterface::FixedValueString s_gameName;
AZ::SettingsRegistryInterface::FixedValueString s_projectPath;
AZ::SettingsRegistryInterface::FixedValueString s_projectName;
AZ::SettingsRegistryInterface::FixedValueString s_assetRoot;
AZ::SettingsRegistryInterface::FixedValueString s_assetServerAddress;
AZ::SettingsRegistryInterface::FixedValueString s_cachedEngineRoot;
@@ -191,7 +244,7 @@ namespace AssetUtilities
void ResetGameName()
{
s_gameName = {};
s_projectName = {};
}
bool CopyDirectory(QDir source, QDir destination)
@@ -245,7 +298,7 @@ namespace AssetUtilities
return true;
}
bool ComputeAssetRoot(QDir& root, const QDir* appRootOverride)
bool ComputeAssetRoot(QDir& root, const QDir* rootOverride)
{
if (!s_assetRoot.empty())
{
@@ -253,10 +306,10 @@ namespace AssetUtilities
return true;
}
// Use the appRoot if supplied is supplied and not an empty string
if (appRootOverride && !appRootOverride->path().isEmpty())
// Use the override if supplied and not an empty string
if (rootOverride && !rootOverride->path().isEmpty())
{
root = *appRootOverride;
root = *rootOverride;
s_assetRoot = root.absolutePath().toUtf8().constData();
return true;
}
@@ -288,7 +341,7 @@ namespace AssetUtilities
return true;
}
// The EngineRootFolder Key has not been found in the SettingsRegistry, log an warning about
// The EngineRootFolder Key has not been found in the SettingsRegistry
auto engineRootError = AZ::SettingsRegistryInterface::FixedValueString::format("The EngineRootFolder is not set in the SettingsRegistry at key %s.",
AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBusTraits::OnErrorMessage, engineRootError.c_str());
@@ -296,7 +349,7 @@ namespace AssetUtilities
return false;
}
//! Get the engine root folder
//! Get the external engine root folder if the engine is external to the current root folder.
//! If the current root folder is also the engine folder, then this behaves the same as ComputeEngineRoot
bool ComputeEngineRoot(QDir& root, const QDir* engineRootOverride)
{
@@ -312,6 +365,7 @@ namespace AssetUtilities
AssetUtilities::ComputeAssetRoot(root, engineRootOverride);
}
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
// Use the engineRootOverride if supplied and not empty
if (engineRootOverride && !engineRootOverride->path().isEmpty())
{
@@ -320,11 +374,11 @@ namespace AssetUtilities
return true;
}
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry == nullptr)
{
return false;
}
AZ::IO::FixedMaxPathString engineRootFolder;
if (settingsRegistry->Get(engineRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
@@ -336,7 +390,7 @@ namespace AssetUtilities
return false;
}
bool MakeFileWritable(QString fileName)
bool MakeFileWritable(const QString& fileName)
{
#if defined WIN32
DWORD fileAttributes = GetFileAttributesA(fileName.toUtf8());
@@ -386,7 +440,7 @@ namespace AssetUtilities
#endif
}
bool CheckCanLock(QString fileName)
bool CheckCanLock(const QString& fileName)
{
#if defined(AZ_PLATFORM_WINDOWS)
AZStd::wstring usableFileName;
@@ -419,42 +473,55 @@ namespace AssetUtilities
#endif
}
QString ComputeGameName(QString gameNameOverride, bool force)
QString ComputeProjectName(QString gameNameOverride, bool force)
{
if (force || s_gameName.empty())
if (force || s_projectName.empty())
{
// if its been specified on the command line, then ignore bootstrap:
// Override Game Name if a non-empty override string has been supplied
if (!gameNameOverride.isEmpty())
{
s_projectName = gameNameOverride.toUtf8().constData();
}
else
{
s_projectName = AZ::Utils::GetProjectName();
}
}
return QString::fromUtf8(s_projectName.c_str(), aznumeric_cast<int>(s_projectName.size()));
}
QString ComputeProjectPath()
{
if (s_projectPath.empty())
{
// Check command-line args first
QStringList args = QCoreApplication::arguments();
for (QString arg : args)
{
if (arg.contains(QString("/%1=").arg(GameFolderOverrideParameter), Qt::CaseInsensitive) || arg.contains(QString("--%1=").arg(GameFolderOverrideParameter), Qt::CaseInsensitive))
if (arg.contains(QString("/%1=").arg(ProjectPathOverrideParameter), Qt::CaseInsensitive)
|| arg.contains(QString("--%1=").arg(ProjectPathOverrideParameter), Qt::CaseInsensitive))
{
QString rawValueString = arg.split("=")[1].trimmed();
if (!rawValueString.isEmpty())
{
s_gameName = rawValueString.toUtf8().constData();
return rawValueString;
QDir path(rawValueString);
if (path.isAbsolute())
{
s_projectPath = rawValueString.toUtf8().constData();
break;
}
}
}
}
// Override Game Name if a non-empty override string has been supplied
if (!gameNameOverride.isEmpty())
{
s_gameName = gameNameOverride.toUtf8().constData();
}
else
{
QDir engineRoot;
if (!AssetUtilities::ComputeEngineRoot(engineRoot))
{
return QString();
}
s_gameName = ReadGameNameFromSettingsRegistry(engineRoot.absolutePath()).toUtf8().constData();
}
}
return QString::fromUtf8(s_gameName.c_str(), aznumeric_cast<int>(s_gameName.size()));
if (s_projectPath.empty())
{
s_projectPath = AZ::Utils::GetProjectPath();
}
return QString::fromUtf8(s_projectPath.c_str(), aznumeric_cast<int>(s_projectPath.size()));
}
bool InServerMode()
@@ -479,7 +546,7 @@ namespace AssetUtilities
}
else
{
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Invalid server address, please check the AssetProcessorPlatformConfig.ini file \
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Invalid server address, please check the AssetProcessorPlatformConfig.setreg file \
to ensure that the address is correct. Asset Processor won't be running in server mode.");
}
@@ -517,19 +584,18 @@ to ensure that the address is correct. Asset Processor won't be running in serve
}
}
QDir engineRoot;
ComputeEngineRoot(engineRoot);
QString rootConfigFile = engineRoot.absoluteFilePath("AssetProcessorPlatformConfig.ini");
if (QFile::exists(rootConfigFile))
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
QString address;
QSettings loader(rootConfigFile, QSettings::IniFormat);
loader.beginGroup("Server");
address = loader.value("cacheServerAddress", QString()).toString();
loader.endGroup();
s_assetServerAddress = address.toUtf8().constData();
return address;
AZStd::string address;
if (settingsRegistry->Get(address, AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorSettingsKey)
+ "/Server/cacheServerAddress"))
{
AZ_TracePrintf(AssetProcessor::DebugChannel, "Server Address: %s\n", address.c_str());
}
s_assetServerAddress = address;
return QString::fromUtf8(address.data(), aznumeric_cast<int>(address.size()));
}
return QString();
@@ -549,18 +615,12 @@ to ensure that the address is correct. Asset Processor won't be running in serve
return *s_fileHashSetting;
}
QDir engineRoot;
ComputeEngineRoot(engineRoot);
QString rootConfigFile = engineRoot.absoluteFilePath("AssetProcessorPlatformConfig.ini");
if (QFile::exists(rootConfigFile))
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
bool curValue;
QSettings loader(rootConfigFile, QSettings::IniFormat);
loader.beginGroup("Fingerprinting");
curValue = loader.value("UseFileHashing", true).toBool();
loader.endGroup();
bool curValue = true;
settingsRegistry->Get(curValue, AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorSettingsKey)
+ "/Fingerprinting/UseFileHashing");
AZ_TracePrintf(AssetProcessor::DebugChannel, "UseFileHashing: %s\n", curValue ? "True" : "False");
s_fileHashSetting = curValue;
@@ -573,46 +633,8 @@ to ensure that the address is correct. Asset Processor won't be running in serve
return *s_fileHashSetting;
}
QString ReadGameNameFromSettingsRegistry(QString initialFolder /*= QString()*/)
QString ReadAllowedlistFromSettingsRegistry([[maybe_unused]] QString initialFolder)
{
if (initialFolder.isEmpty())
{
QDir engineRoot;
if (!AssetUtilities::ComputeEngineRoot(engineRoot))
{
return QString();
}
initialFolder = engineRoot.absolutePath();
}
constexpr size_t BufferSize = AZ_ARRAY_SIZE(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + AZStd::char_traits<char>::length("/sys_game_folder");
AZStd::fixed_string<BufferSize> projectKey{ AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey };
projectKey += "/sys_game_folder";
AZ::SettingsRegistryInterface::FixedValueString projectName;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry && settingsRegistry->Get(projectName, projectKey))
{
return QString::fromUtf8(projectName.c_str(), aznumeric_cast<int>(projectName.size()));
}
AZ_Warning("AssetUtils", false, "Unable to find the Project Name(sys_game_folder) key in the settings registry");
return {};
}
QString ReadAllowedlistFromSettingsRegistry(QString initialFolder /*= QString()*/)
{
if (initialFolder.isEmpty())
{
QDir assetRoot;
if (!AssetUtilities::ComputeAssetRoot(assetRoot))
{
return QString();
}
initialFolder = assetRoot.absolutePath();
}
constexpr size_t BufferSize = AZ_ARRAY_SIZE(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + AZStd::char_traits<char>::length("/allowed_list");
AZStd::fixed_string<BufferSize> allowedListKey{ AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey };
allowedListKey += "/allowed_list";
@@ -626,18 +648,8 @@ to ensure that the address is correct. Asset Processor won't be running in serve
return {};
}
QString ReadRemoteIpFromSettingsRegistry(QString initialFolder /*= QString()*/)
QString ReadRemoteIpFromSettingsRegistry([[maybe_unused]] QString initialFolder)
{
if (initialFolder.isEmpty())
{
QDir engineRoot;
if (!AssetUtilities::ComputeEngineRoot(engineRoot))
{
return QString();
}
initialFolder = engineRoot.absolutePath();
}
constexpr size_t BufferSize = AZ_ARRAY_SIZE(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + AZStd::char_traits<char>::length("/remote_ip");
AZStd::fixed_string<BufferSize> remoteIpKey{ AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey };
remoteIpKey += "/remote_ip";
@@ -651,159 +663,44 @@ to ensure that the address is correct. Asset Processor won't be running in serve
return {};
}
bool WriteAllowedlistToBootstrap(QStringList newAllowedList)
bool WriteAllowedlistToSettingsRegistry(const QStringList& newAllowedList)
{
QDir assetRoot;
ComputeAssetRoot(assetRoot);
QString bootstrapFilename = assetRoot.filePath("bootstrap.cfg");
QFile bootstrapFile(bootstrapFilename);
AZ::IO::FixedMaxPath assetProcessorUserSetregPath = AZ::Utils::GetProjectPath();
assetProcessorUserSetregPath /= AssetProcessorUserSetregRelPath;
// do not alter the branch file unless we are able to obtain an exclusive lock. Other apps (such as NPP) may actually write 0 bytes first, then slowly spool out the remainder)
if (!CheckCanLock(bootstrapFilename))
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (!settingsRegistry)
{
AZ_Error(AssetProcessor::ConsoleChannel, false, "Unable access Settings Registry. Branch Token cannot be updated");
return false;
}
if (!bootstrapFile.open(QIODevice::ReadOnly))
auto allowedListKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
+ "/allowed_list";
AZStd::string currentAllowedList;
if (settingsRegistry->Get(currentAllowedList, allowedListKey))
{
return false;
}
// Split the current allowedList into an array and compare against the new allowed list
AZStd::vector<AZStd::string_view> allowedListArray;
auto AppendAllowedIpTokens = [&allowedListArray](AZStd::string_view token) { allowedListArray.emplace_back(token); };
AZ::StringFunc::TokenizeVisitor(currentAllowedList, AppendAllowedIpTokens, ',');
// regexp that matches either the beginning of the file, some whitespace, and allowed_list, or,
// matches a newline, then whitespace, then allowed_list it will not match comments.
QRegExp allowedListPattern("(^|\\n)\\s*allowed_list\\s*=\\s*(.+)", Qt::CaseInsensitive, QRegExp::RegExp);
//read the file line by line and try to find the allowed_list line
QString readAllowedList;
QString allowedListline;
while (!bootstrapFile.atEnd())
{
QString contents(bootstrapFile.readLine());
int matchIdx = allowedListPattern.indexIn(contents);
if (matchIdx != -1)
auto CompareQListToAzVector = [](AZStd::string_view currentAllowedIp, const QString& newAllowedIp)
{
allowedListline = contents;
readAllowedList = allowedListPattern.cap(2);
break;
return currentAllowedIp == newAllowedIp.toUtf8().constData();
};
if (AZStd::equal(allowedListArray.begin(), allowedListArray.end(), newAllowedList.begin(), newAllowedList.end(), CompareQListToAzVector))
{
// no need to update, remote_ip already matches
return true;
}
}
//read the entire file into so we can do a buffer replacement
bootstrapFile.seek(0);
QString fileContents;
fileContents = bootstrapFile.readAll();
bootstrapFile.close();
// Update Settings Registry with new token
AZStd::string azNewAllowedList{ newAllowedList.join(', ').toUtf8().constData() };
settingsRegistry->Set(allowedListKey, azNewAllowedList);
//format the new allowed list
QString formattedNewAllowedList = newAllowedList.join(", ");
//if we didn't find a allowed_list entry then append one
if (allowedListline.isEmpty())
{
fileContents.append("\nallowed_list = " + formattedNewAllowedList + "\n");
}
else if (QString::compare(formattedNewAllowedList, readAllowedList, Qt::CaseInsensitive) == 0)
{
// no need to update, they match
return true;
}
else
{
//Replace the found line with a new one
fileContents.replace(allowedListline, "allowed_list = " + formattedNewAllowedList + "\n");
}
// Make the bootstrap file writable
if (!MakeFileWritable(bootstrapFilename))
{
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Failed to make the bootstrap file writable.")
return false;
}
if (!bootstrapFile.open(QIODevice::WriteOnly))
{
return false;
}
QTextStream output(&bootstrapFile);
output << fileContents;
bootstrapFile.close();
return true;
}
bool WriteRemoteIpToBootstrap(QString newRemoteIp)
{
QDir assetRoot;
ComputeAssetRoot(assetRoot);
QString bootstrapFilename = assetRoot.filePath("bootstrap.cfg");
QFile bootstrapFile(bootstrapFilename);
// do not alter the branch file unless we are able to obtain an exclusive lock. Other apps (such as NPP) may actually write 0 bytes first, then slowly spool out the remainder)
if (!CheckCanLock(bootstrapFilename))
{
return false;
}
if (!bootstrapFile.open(QIODevice::ReadOnly))
{
return false;
}
// regexp that matches either the beginning of the file, and remote_ip, or,
// matches a newline, then whitespace, then remote_ip it will not match comments.
QRegExp remoteIpPattern("(^|\\n)\\s*remote_ip\\s*=\\s*(.+)", Qt::CaseInsensitive, QRegExp::RegExp);
//read the file line by line and try to find the remote_ip line
QString readRemoteIp;
QString remoteIpline;
while (!bootstrapFile.atEnd())
{
QString contents(bootstrapFile.readLine());
int matchIdx = remoteIpPattern.indexIn(contents);
if (matchIdx != -1)
{
remoteIpline = contents;
readRemoteIp = remoteIpPattern.cap(2);
break;
}
}
//read the entire file into so we can do a buffer replacement
bootstrapFile.seek(0);
QString fileContents;
fileContents = bootstrapFile.readAll();
bootstrapFile.close();
//if we didn't find a remote_ip entry then append one
if (remoteIpline.isEmpty())
{
fileContents.append("\nremote_ip = " + newRemoteIp + "\n");
}
else if (QString::compare(newRemoteIp, readRemoteIp, Qt::CaseInsensitive) == 0)
{
// no need to update, they match
return true;
}
else
{
//Replace the found line with a new one
fileContents.replace(remoteIpline, "remote_ip = " + newRemoteIp + "\n");
}
// Make the bootstrap file writable
if (!MakeFileWritable(bootstrapFilename))
{
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Failed to make the bootstrap file writable.")
return false;
}
if (!bootstrapFile.open(QIODevice::WriteOnly))
{
return false;
}
QTextStream output(&bootstrapFile);
output << fileContents;
bootstrapFile.close();
return true;
return AssetUtilsInternal::DumpAssetProcessorUserSettingsToFile(*settingsRegistry, assetProcessorUserSetregPath);
}
quint16 ReadListeningPortFromSettingsRegistry(QString initialFolder /*= QString()*/)
@@ -923,23 +820,20 @@ to ensure that the address is correct. Asset Processor won't be running in serve
bool ComputeProjectCacheRoot(QDir& projectCacheRoot)
{
QDir assetRoot;
if (!ComputeAssetRoot(assetRoot))
if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr)
{
return false; // failed to detect engine root
AZ::SettingsRegistryInterface::FixedValueString projectCacheRootValue;
if (registry->Get(projectCacheRootValue, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
!projectCacheRootValue.empty())
{
projectCacheRoot = QDir(QString::fromUtf8(projectCacheRootValue.c_str(), aznumeric_cast<int>(projectCacheRootValue.size())));
return true;
}
}
QString gameDir = ComputeGameName(assetRoot.absolutePath());
if (gameDir.isEmpty())
{
return false;
}
projectCacheRoot = QDir(assetRoot.filePath("Cache/" + gameDir));
return true;
return false;
}
bool ComputeFenceDirectory(QDir& fenceDir)
{
QDir cacheRoot;
@@ -951,6 +845,25 @@ to ensure that the address is correct. Asset Processor won't be running in serve
return true;
}
QString StripAssetPlatform(AZStd::string_view relativeProductPath)
{
// Skip over the assetPlatform path segment if it is matches one of the platform defaults
// Otherwise return the path unchanged
AZStd::string_view strippedProductPath{ relativeProductPath };
if (AZStd::optional pathSegment = AZ::StringFunc::TokenizeNext(strippedProductPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
pathSegment.has_value())
{
AZ::IO::FixedMaxPathString assetPlatformSegmentLower{ *pathSegment };
AZStd::to_lower(assetPlatformSegmentLower.begin(), assetPlatformSegmentLower.end());
if (AzFramework::PlatformHelper::GetPlatformIdFromName(assetPlatformSegmentLower) != AzFramework::PlatformId::Invalid)
{
return QString::fromUtf8(strippedProductPath.data(), aznumeric_cast<int>(strippedProductPath.size()));
}
}
return QString::fromUtf8(relativeProductPath.data(), aznumeric_cast<int>(relativeProductPath.size()));
}
QString NormalizeFilePath(const QString& filePath)
{
// do NOT convert to absolute paths here, we just want to manipulate the string itself.
@@ -1035,92 +948,41 @@ to ensure that the address is correct. Asset Processor won't be running in serve
bool UpdateBranchToken()
{
QDir assetRoot;
ComputeAssetRoot(assetRoot);
QString bootstrapFilename = assetRoot.filePath("bootstrap.cfg");
QFile bootstrapFile(bootstrapFilename);
QString fileContents;
// do not alter the branch file unless we are able to obtain an exclusive lock. Other apps (such as NPP) may actually write 0 bytes first, then slowly spool out the remainder)
QElapsedTimer timer;
timer.start();
bool hasLock = false;
do
{
if (CheckCanLock(bootstrapFilename))
{
hasLock = true;
break;
}
QThread::msleep(AssetUtilsInternal::g_RetryWaitInterval);
} while (!timer.hasExpired(10 * AssetUtilsInternal::g_RetryWaitInterval));
if (!hasLock)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to lock bootstrap file at: %s\n", bootstrapFilename.toUtf8().constData());
return false;
}
if (!bootstrapFile.open(QIODevice::ReadOnly))
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to open bootstrap file at: %s\n", bootstrapFilename.toUtf8().constData());
return false;
}
fileContents = bootstrapFile.readAll();
bootstrapFile.close();
AZ::IO::FixedMaxPath assetProcessorUserSetregPath = AZ::Utils::GetProjectPath();
assetProcessorUserSetregPath /= AssetProcessorUserSetregRelPath;
AZStd::string appBranchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForAppRoot, appBranchToken);
QString currentBranchToken(appBranchToken.c_str());
QString readBranchToken;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, appBranchToken);
// regexp that matches either the beginning of the file, some whitespace, and assetProcessor_branch_token, or,
// matches a newline, then whitespace, then assetProcessor_branch_token
// it will not match comments.
QRegExp branchTokenPattern("(^|\\n)\\s*assetProcessor_branch_token\\s*=\\s*(\\S+)\\b", Qt::CaseInsensitive, QRegExp::RegExp);
int matchIdx = branchTokenPattern.indexIn(fileContents);
if (matchIdx != -1)
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (!settingsRegistry)
{
readBranchToken = branchTokenPattern.cap(2);
AZ_Error(AssetProcessor::ConsoleChannel, false, "Unable access Settings Registry. Branch Token cannot be updated");
return false;
}
if (readBranchToken.isEmpty())
AZStd::string registryBranchToken;
auto branchTokenKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/assetProcessor_branch_token";
if (settingsRegistry->Get(registryBranchToken, branchTokenKey))
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "adding branch token (%s) in (%s)\n", currentBranchToken.toUtf8().constData(), bootstrapFilename.toUtf8().constData());
fileContents.append("\nassetProcessor_branch_token = " + currentBranchToken + "\n");
}
else if (QString::compare(currentBranchToken, readBranchToken, Qt::CaseInsensitive) == 0)
{
// no need to update, branch token match
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Branch token (%s) is already correct in (%s)\n", currentBranchToken.toUtf8().constData(), bootstrapFilename.toUtf8().constData());
return true;
if (appBranchToken == registryBranchToken)
{
// no need to update, branch token match
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Branch token (%s) is already correct in (%s)\n", appBranchToken.c_str(), assetProcessorUserSetregPath.c_str());
return true;
}
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Updating branch token (%s) in (%s)\n", appBranchToken.c_str(), assetProcessorUserSetregPath.c_str());
}
else
{
//Updating branch token
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Updating branch token (%s) in (%s)\n", currentBranchToken.toUtf8().constData(), bootstrapFilename.toUtf8().constData());
fileContents.replace(branchTokenPattern.cap(0), "\nassetProcessor_branch_token = " + currentBranchToken + "\n");
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Adding branch token (%s) in (%s)\n", appBranchToken.c_str(), assetProcessorUserSetregPath.c_str());
}
// Make the bootstrap file writable
if (!MakeFileWritable(bootstrapFilename))
{
AZ_Warning(AssetProcessor::ConsoleChannel, false, "Failed to make the bootstrap file writable.")
return false;
}
if (!bootstrapFile.open(QIODevice::WriteOnly))
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to open bootstrap file (%s)\n", bootstrapFilename.toUtf8().constData());
return false;
}
// Update Settings Registry with new token
settingsRegistry->Set(branchTokenKey, appBranchToken);
QTextStream output(&bootstrapFile);
output << fileContents;
bootstrapFile.close();
return true;
return AssetUtilsInternal::DumpAssetProcessorUserSettingsToFile(*settingsRegistry, assetProcessorUserSetregPath);
}
QString ComputeJobDescription(const AssetProcessor::AssetRecognizer* recognizer)
@@ -1130,7 +992,7 @@ to ensure that the address is correct. Asset Processor won't be running in serve
AZStd::string ComputeJobLogFolder()
{
return AZStd::string::format("@log@/logs/JobLogs");
return AZStd::string::format("@log@/JobLogs");
}
AZStd::string ComputeJobLogFileName(const AzToolsFramework::AssetSystem::JobInfo& jobInfo)
@@ -1477,14 +1339,28 @@ to ensure that the address is correct. Asset Processor won't be running in serve
bool CreateTempWorkspace(QString& result)
{
// Use the engine root as a temp workspace folder
// this works better for numerous reasons
// * Its on the same drive as the /Cache/ so we will be moving files instead of copying from drive to drive
// Use the project user folder as a temp workspace folder
// The benefits are
// * It's on the same drive as the Cache/ so we will be moving files instead of copying from drive to drive
// * It is discoverable by the user and thus deletable and we can also tell people to send us that folder without them having to go digging for it
// * If you can't write to it you have much bigger problems
QDir rootDir;
if (ComputeAssetRoot(rootDir))
bool foundValidPath{};
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (AZ::IO::Path userPath; settingsRegistry->Get(userPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
{
rootDir.setPath(QString::fromUtf8(userPath.c_str(), aznumeric_cast<int>(userPath.Native().size())));
foundValidPath = true;
}
}
if (!foundValidPath)
{
foundValidPath = ComputeAssetRoot(rootDir);
}
if (foundValidPath)
{
QString tempPath = rootDir.absolutePath();
return CreateTempWorkspace(tempPath, result);
@@ -1499,7 +1375,6 @@ to ensure that the address is correct. Asset Processor won't be running in serve
QString inputName;
QString platformName;
QString jobDescription;
QString gameName = AssetUtilities::ComputeGameName();
AZ::Uuid guid = AZ::Uuid::CreateNull();
using namespace AzToolsFramework::AssetDatabase;
@@ -1513,16 +1388,16 @@ to ensure that the address is correct. Asset Processor won't be running in serve
platform = AzToolsFramework::AssetSystem::GetHostAssetPlatform();
}
QString platformPrepend = QString("%1/%2/").arg(platform, gameName);
QString productNameWithPlatformAndGameName = productName;
QString platformPrepend = QString("%1/").arg(platform);
QString productNameWithPlatform = productName;
if (!productName.startsWith(platformPrepend, Qt::CaseInsensitive))
{
productNameWithPlatformAndGameName = productName = QString("%1/%2/%3").arg(platform, gameName, productName);
productNameWithPlatform = productName = QString("%1/%2").arg(platform, productName);
}
ProductDatabaseEntryContainer products;
if (databaseConnection->GetProductsByProductName(productNameWithPlatformAndGameName, products))
if (databaseConnection->GetProductsByProductName(productNameWithPlatform, products))
{
// if we find stuff, then return immediately, productName is already a productName.
return productName;
@@ -1535,24 +1410,9 @@ to ensure that the address is correct. Asset Processor won't be running in serve
return productName;
}
if (!databaseConnection->GetProductsLikeProductName(productNameWithPlatformAndGameName, AssetDatabaseConnection::LikeType::StartsWith, products))
if (!databaseConnection->GetProductsLikeProductName(productNameWithPlatform, AssetDatabaseConnection::LikeType::StartsWith, products))
{
//if we are here it means that the asset database does not know about this product,
//we will now remove the gameName and try again ,so now the path will only have $PLATFORM/ in front of it
int gameNameIndex = productName.indexOf(gameName, 0, Qt::CaseInsensitive);
if (gameNameIndex != -1)
{
//we will now remove the gameName and the separator
productName.remove(gameNameIndex, gameName.length() + 1);// adding one for the native separator
}
//Search the database for this product
if (!databaseConnection->GetProductsLikeProductName(productName, AssetDatabaseConnection::LikeType::StartsWith, products))
{
//return empty string if the database still does not have any idea about the product
productName = QString();
}
return {};
}
return productName.toLower();
}
@@ -51,8 +51,7 @@ namespace AssetProcessor
namespace AssetUtilities
{
inline constexpr char GameFolderOverrideParameter[] = "gamefolder";
inline constexpr char ProjectPathOverrideParameter[] = "project-path";
//! Set precision fingerprint timestamps will be truncated to avoid mismatches across systems/packaging with different file timestamp precisions
//! Timestamps default to milliseconds. A value of 1 will keep the default millisecond precision. A value of 1000 will reduce the precision to seconds
@@ -79,10 +78,10 @@ namespace AssetUtilities
//! makes the file writable
//! return true if operation is successful, otherwise return false
bool MakeFileWritable(QString filename);
bool MakeFileWritable(const QString& filename);
//! Check to see if we can Lock the file
bool CheckCanLock(QString filename);
bool CheckCanLock(const QString& filename);
//! Updates the branch token in the bootstrap file
bool UpdateBranchToken();
@@ -98,11 +97,14 @@ namespace AssetUtilities
bool ShouldUseFileHashing();
//! Determine the name of the current game - for example, SamplesProject
//! Can be overridden by passing in a non-empty gameNameOverride
//! The override will persist if the GameName wasn't set previously or
//! Determine the name of the current project - for example, SamplesProject
//! Can be overridden by passing in a non-empty projectNameOverride
//! The override will persist if the project name wasn't set previously or
//! force=true is supplied
QString ComputeGameName(QString gameNameOverride = QString(), bool force = false);
QString ComputeProjectName(QString projectNameOverride = QString(), bool force = false);
//! Determine the absolute path of the current project
QString ComputeProjectPath();
//! Reads the allowed list directly from the bootstrap file
QString ReadAllowedlistFromSettingsRegistry(QString initialFolder = QString());
@@ -111,13 +113,7 @@ namespace AssetUtilities
QString ReadRemoteIpFromSettingsRegistry(QString initialFolder = QString());
//! Writes the allowed list directly to the bootstrap file
bool WriteAllowedlistToBootstrap(QStringList allowedList);
//! Writes the remote ip directly to the bootstrap file
bool WriteRemoteIpToBootstrap(QString remoteIp);
//! Reads the game name directly from the bootstrap file
QString ReadGameNameFromSettingsRegistry(QString initialFolder = QString());
bool WriteAllowedlistToSettingsRegistry(const QStringList& allowedList);
//! Reads the listening port from the bootstrap file
//! By default the listening port is 45643
@@ -143,13 +139,24 @@ namespace AssetUtilities
QString ComputeJobDescription(const AssetProcessor::AssetRecognizer* recognizer);
//! Compute the root of the cache for the current project.
//! This is generally the "cache" folder, subfolder gamedir.
//! This is generally the "<Project>/Cache" folder
bool ComputeProjectCacheRoot(QDir& projectCacheRoot);
//! Compute the folder that will be used for fence files.
bool ComputeFenceDirectory(QDir& fenceDir);
//! Converts all slashes to forward slashes, removes double slashes,
//! Strips the first "asset platform" from the first path segment of a relative product path
//! This is meant for removing the asset platform for paths such as "pc/MyAssetFolder/MyAsset.asset"
//! Therefore the result here becomes "MyAssetFolder/MyAsset"
//!
//! Similarly invoking this function on relative path that begins with the "server" platform
//! "server/AssetFolder/Server.asset2" -> "AssetFolder/Server.asset2"
//! This function does not strip an asset platform from anywhere, but the first path segment
//! Therefore invoking strip Asset on "MyProject/Cache/pc/MyAsset/MyAsset.asset"
//! would return a copy of the relative path
QString StripAssetPlatform(AZStd::string_view relativeProductPath);
//! Converts all slashes to forward slashes, removes double slashes,
//! replaces all indirections such as '.' or '..' as appropriate.
//! On windows, the drive letter (if present) is converted to uppercase.
//! Besides that, all case is preserved.
@@ -11,7 +11,7 @@ es3=enabled ; note - bad platform - its not one of the above ones!
; without a scan folder, it is an invalid file.
[ScanFolder Game]
watch=@ROOT@/@GAMENAME@
watch=@PROJECTROOT@
recursive=1
order=0
@@ -2,7 +2,7 @@
; without a scan folder, it is an invalid file.
[ScanFolder Game]
watch=@ROOT@/@GAMENAME@
watch=@PROJECTROOT@
recursive=1
order=0
@@ -8,7 +8,7 @@ tags=tools,renderer
; without a scan folder, it is an invalid file.
[ScanFolder Game]
watch=@ROOT@/@GAMENAME@
watch=@PROJECTROOT@
recursive=1
order=0
@@ -35,16 +35,16 @@ cbc=abc
fbx.assetinfo=fbx
[ScanFolder Game]
watch=@ROOT@/@GAMENAME@
watch=@PROJECTROOT@
; use a special display name here to make sure macros work
display=@GAMENAME@ Scan Folder
display=@PROJECTROOT@ Scan Folder
recursive=1
order=0
; this test makes sure that those macros make sense and are present as well as that order is preserved.
; it also makes sure that the friendly name ("FeatureTests") is used, if no display is present and does not lose its case.
[ScanFolder FeatureTests]
watch=@ROOT@/@GAMENAME@FeatureTests
watch=@PROJECTROOT@FeatureTests
output=featuretestsoutputfolder
recursive=0
order=5000
@@ -53,7 +53,7 @@ order=5000
; (which is constructed from its name in the square brackets)
; instead of other attributes such as the watch folder, or output prefix.
[ScanFolder FeatureTests2]
watch=@ROOT@/@GAMENAME@FeatureTests
watch=@PROJECTROOT@FeatureTests
output=featuretestsoutputfolder
recursive=0
order=6000
@@ -42,7 +42,7 @@ cbc=abc
fbx.assetinfo=fbx
[ScanFolder Game]
watch=@ROOT@/@GAMENAME@
watch=@PROJECTROOT@
display=gameoutput
recursive=1
order=0
-12
View File
@@ -1,12 +0,0 @@
<RCC>
<qresource prefix="testdata">
<file>config_broken_badplatform/AssetProcessorPlatformConfig.ini</file>
<file>config_broken_noscans/AssetProcessorPlatformConfig.ini</file>
<file>config_broken_recognizers/AssetProcessorPlatformConfig.ini</file>
<file>config_broken_noplatform/AssetProcessorPlatformConfig.ini</file>
<file>config_regular/AssetProcessorPlatformConfig.ini</file>
<file>config_regular_platform_scanfolder/AssetProcessorPlatformConfig.ini</file>
<file>EmptyDummyProject/AssetProcessorGamePlatformConfig.ini</file>
<file>DummyProject/AssetProcessorGamePlatformConfig.ini</file>
</qresource>
</RCC>
-1
View File
@@ -16,7 +16,6 @@ add_subdirectory(AzTestRunner)
add_subdirectory(CryCommonTools)
add_subdirectory(CrySCompileServer)
add_subdirectory(CryXML)
add_subdirectory(GemRegistry)
add_subdirectory(HLSLCrossCompiler)
add_subdirectory(HLSLCrossCompilerMETAL)
add_subdirectory(News)
@@ -20,6 +20,9 @@
#include <AzQtComponents/Components/StyleManager.h>
#include <AzCore/PlatformDef.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/std/typetraits/underlying_type.h>
#include <QString>
@@ -82,7 +85,13 @@ namespace Lumberyard
AzQtComponents::StyleManager styleManager{ nullptr };
QApplication app{ argCount, nullptr };
styleManager.Initialize(&app);
AZ::IO::FixedMaxPath engineRootPath;
{
AZ::ComponentApplication componentApplication;
auto settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
styleManager.initialize(&app, engineRootPath);
QString reportPath{ GetReportString(report.file_path.value()) };
-62
View File
@@ -1,62 +0,0 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
ly_add_target(
NAME GemRegistry.Static STATIC
NAMESPACE AZ
FILES_CMAKE
gemregistry_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
include
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::AzFramework
)
ly_add_target(
NAME GemRegistry ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE AZ
FILES_CMAKE
gemregistry_shared_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
include
BUILD_DEPENDENCIES
PRIVATE
AZ::GemRegistry.Static
)
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME GemRegistry.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
gemregistry_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::GemRegistry.Static
)
ly_add_googletest(
NAME AZ::GemRegistry.Tests
)
endif()
@@ -1,20 +0,0 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
include/GemRegistry/Version.h
include/GemRegistry/IGemRegistry.h
include/GemRegistry/Dependency.h
source/ProjectSettings.h
source/ProjectSettings.cpp
source/GemDescription.h
source/GemDescription.cpp
)
@@ -1,15 +0,0 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the License). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an AS IS BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
source/GemRegistry.h
source/GemRegistry.cpp
)
@@ -1,21 +0,0 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
tests/main.cpp
tests/VersionTest.cpp
tests/DependencyTest.cpp
tests/DescriptionTest.cpp
tests/ProjectSettingsTest.cpp
tests/RegistryTest.cpp
source/GemRegistry.h
source/GemRegistry.cpp
)
@@ -1,28 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Uuid.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/regex.h>
#include <AzFramework/Dependency/Dependency.h>
#include "GemRegistry/Version.h"
namespace Gems
{
using EngineSpecifier = AzFramework::Specifier<EngineVersion::parts_count>;
using GemSpecifier = AzFramework::Specifier<GemVersion::parts_count>;
using EngineDependency = AzFramework::Dependency<EngineVersion::parts_count>;
using GemDependency = AzFramework::Dependency<GemVersion::parts_count>;
} // namespace Gems
@@ -1,399 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/weak_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/unordered_map.h>
#include "GemRegistry/Dependency.h"
#include "GemRegistry/Version.h"
namespace Gems
{
/// Describes how other Gems (and the final executable) will link against this Gem (valid only for Type::GameModule and Type::ServerModule).
enum class LinkType
{
/// Do not link against this Gem, it is loaded as a Dynamic Library at runtime.
Dynamic,
/// Link against this Gem, it is also loaded as a Dynamic Library at runtime.
DynamicStatic,
/// Gem has no code, there is nothing to link against.
NoCode,
};
/**
* Defines a module produced by a Gem.
*/
struct ModuleDefinition
{
enum class Type
{
GameModule,
EditorModule,
StaticLib,
Builder,
Standalone,
ServerModule,
};
/// The type of module this represents
Type m_type;
/// The name of the module (for dll naming)
AZStd::string m_name;
/// If this module is type GameModule, how is it linked?
LinkType m_linkType = LinkType::NoCode;
/// The complete name of the file produced (for all Types but StaticLib)
AZStd::string m_fileName;
/// If the module extends another module, this points to that
AZStd::weak_ptr<const ModuleDefinition> m_parent;
/// All of the modules that extend this module
AZStd::vector<AZStd::weak_ptr<const ModuleDefinition>> m_children;
// Construction and Destruction is trivial
ModuleDefinition() = default;
~ModuleDefinition() = default;
// Disallow copying and moving
ModuleDefinition(const ModuleDefinition&) = delete;
ModuleDefinition& operator=(const ModuleDefinition&) = delete;
ModuleDefinition(ModuleDefinition&&) = delete;
ModuleDefinition& operator=(ModuleDefinition&& rhs) = delete;
};
using ModuleDefinitionConstPtr = AZStd::shared_ptr<const ModuleDefinition>;
using ModuleDefinitionVector = AZStd::vector<ModuleDefinitionConstPtr>;
/**
* Describes an instance of a Gem.
*/
class IGemDescription
{
public:
/// The ID of the Gem
virtual const AZ::Uuid& GetID() const = 0;
/// The name of the Gem
virtual const AZStd::string& GetName() const = 0;
/// The UI-friendly name of the Gem
virtual const AZStd::string& GetDisplayName() const = 0;
/// The version of the Gem
virtual const GemVersion& GetVersion() const = 0;
/// Relative path to the folder of this Gem
virtual const AZStd::string& GetPath() const = 0;
/// Absolute path to the folder of this Gem
virtual const AZStd::string& GetAbsolutePath() const = 0;
/// Summary description of the Gem
virtual const AZStd::string& GetSummary() const = 0;
/// Icon path of the gem
virtual const AZStd::string& GetIconPath() const = 0;
/// Tags to associated with the Gem
virtual const AZStd::vector<AZStd::string>& GetTags() const = 0;
/// Get the list of modules produced by the Gem
virtual const ModuleDefinitionVector& GetModules() const = 0;
/// Get all modules to be loaded for a given function
/// This method traverses children to find the most derived module of each type per tree
virtual const ModuleDefinitionVector& GetModulesOfType(ModuleDefinition::Type type) const = 0;
/// The name of the engine module class to initialize
virtual const AZStd::string& GetEngineModuleClass() const = 0;
/// Get the Gem's other gems dependencies
virtual const AZStd::vector<AZStd::shared_ptr<GemDependency> >& GetGemDependencies() const = 0;
/// Get the Gem's engine dependency
virtual const AZStd::shared_ptr<EngineDependency> GetEngineDependency() const = 0;
/// Determine if this is a Game Gem
virtual const bool IsGameGem() const = 0;
/// Determine if this is a required Gem
virtual const bool IsRequired() const = 0;
virtual ~IGemDescription() = default;
};
using IGemDescriptionConstPtr = AZStd::shared_ptr<const IGemDescription>;
/// A specific Gem known to a Project.
/// The Gem is not used unless it is enabled.
struct ProjectGemSpecifier
: public GemSpecifier
{
/// Folder in which this specific Gem can be found.
AZStd::string m_path;
ProjectGemSpecifier(const AZ::Uuid& id, const GemVersion& version, const AZStd::string& path)
: GemSpecifier(id, version)
, m_path(path)
{}
~ProjectGemSpecifier() override = default;
};
using ProjectGemSpecifierMap = AZStd::unordered_map<AZ::Uuid, ProjectGemSpecifier>;
/**
* Stores project-specific settings, such as which Gems are enabled and which versions are required.
*/
class IProjectSettings
{
public:
/**
* Initializes the ProjectSettings with a project name to load the settings from.
*
* \param[in] appRootFolder The application root folder where the project sub folder resides
* \param[in] projectSubFolder The folder in which the project's assets reside (and the configuration file)
*
* \returns True on success, false on failure.
*/
virtual AZ::Outcome<void, AZStd::string> Initialize(const AZStd::string& appRootFolder, const AZStd::string& projectSubFolder) = 0;
/**
* Enables the specified instance of a Gem.
*
* \param[in] spec The specific Gem to enable.
*
* \returns True on success, False on failure.
*/
virtual bool EnableGem(const ProjectGemSpecifier& spec) = 0;
/**
* Disables the specified instance of a Gem.
*
* \param[in] spec The specific Gem to disable.
*
* \returns True on success, False on failure.
*/
virtual bool DisableGem(const GemSpecifier& spec) = 0;
/**
* Checks if a Gem of the specified description is enabled.
*
* \param[in] spec The specific Gem to check.
*
* \returns True if the Gem is enabled, False if it is disabled.
*/
virtual bool IsGemEnabled(const GemSpecifier& spec) const = 0;
/**
* Checks if a Gem of the specified ID and version constraints is enabled.
*
* \param[in] id The ID of the Gem to check.
* \param[in] versionConstraints An array of strings, each of which is a condition using the gem dependency syntax
*
* \returns True if the Gem is enabled and passes every version constraint condition, False if it is disabled or does not match the version constraints.
*/
virtual bool IsGemEnabled(const AZ::Uuid& id, const AZStd::vector<AZStd::string>& versionConstraints) const = 0;
/**
* Checks if a Gem of the specified dependency is enabled.
*
* \param[in] dep The dependency to validate.
*
* \returns True if the Gem is enabled, False if it is disabled.
*/
virtual bool IsGemDependencyMet(const AZStd::shared_ptr<GemDependency> dep) const = 0;
/**
* Checks if the engine dependency is met.
*
* \param[in] dep The dependency to validate.
* \param[in] againstVersion
* The version of the engine to validate against
*
* \returns True if the Gem is enabled, False if it is disabled.
*/
virtual bool IsEngineDependencyMet(const AZStd::shared_ptr<EngineDependency> dep, const EngineVersion& againstVersion) const = 0;
/**
* Gets the Gems known to this project.
* Only enabled Gems are actually used at runtime.
* A project can only reference one version of a Gem.
*
* \returns The vector of enabled Gems.
*/
virtual const ProjectGemSpecifierMap& GetGems() const = 0;
/**
* Sets the Gem map to the passed in list. Used when resetting after a failed save.
*/
virtual void SetGems(const ProjectGemSpecifierMap& newGemMap) = 0;
/**
* Checks that all installed Gems have their dependencies met.
* Any unmet dependencies can be found via IGemRegistry::GetErrorMessage();
*
* \param[in] engineVersion
* The version of the engine to validate against
*
* \returns Void on success, error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> ValidateDependencies(const EngineVersion& engineVersion = EngineVersion()) const = 0;
/**
* Saves the current state of the project settings to it's project configuration file.
*
* \returns Void on success, error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> Save() const = 0;
/**
* Get the project name that this project settings represents
* \returns The project name.
*/
virtual const AZStd::string& GetProjectName() const = 0;
/**
* Get the app root folder for the project
* \returns The project root path.
*/
virtual const AZStd::string& GetProjectRootPath() const = 0;
virtual ~IProjectSettings() = default;
};
/**
* Defines how to search for Gems.
*/
struct SearchPath
{
/// The root path to search
AZStd::string m_path;
/// The filter to apply to TOP LEVEL searching
AZStd::string m_filter;
explicit SearchPath(const AZStd::string& path)
: m_path(path)
, m_filter("*")
{ }
SearchPath(const AZStd::string& path, const AZStd::string& filter)
: m_path(path)
, m_filter(filter)
{ }
};
inline bool operator==(const SearchPath& left, const SearchPath& right)
{
return left.m_path == right.m_path
&& left.m_filter == right.m_filter;
}
/**
* Manages installed Gems.
*/
class IGemRegistry
{
public:
/**
* Add to the list of paths to search for Gems when calling LoadAllGemsFromDisk
*
* \param[in] searchPath The path to add
* \param[in] loadGemsNow Load Gems from searchPath now
*
* \returns Void on success, error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> AddSearchPath(const SearchPath& searchPath, bool loadGemsNow) = 0;
/**
* Scans the Gems/ folder for all installed Gems.
*
* In the event of an error loading a Gem, the error will be recorded,
* the Gem will not be loaded, and false will be returned.
*
* Loaded Gems can be accessed via GetGemDescription() or GetAllGemDescriptions().
*
* \returns AZ::Success if the search succeeded
* AZ::Failure with error message if any errors occurred.
*/
virtual AZ::Outcome<void, AZStd::string> LoadAllGemsFromDisk() = 0;
/**
* Looks for a gems.json file in the given folder and returns a IGemDescriptionConstPtr if found
*
* \param[in] gemFolderRelPath A valid path on disk to a directory that contains a gem.json file relative to the engine root
*
* \returns IGemDescriptionConstPtr if a gem.json file could be parsed out of the gemFolderPath,
* AZ::Failure with error message if any errors occurred.
*/
virtual AZ::Outcome<IGemDescriptionConstPtr, AZStd::string> ParseToGemDescriptionPtr(const AZStd::string& gemFolderRelPath, const char* absoluteFilePath) = 0;
/**
* Loads Gems for the specified project.
* May be called for multiple projects.
*
* \param[in] settings The project to load Gems for.
* \param[in] resetPreviousProjects If true, reset any setting/gem descriptors that any previous calls to LoadProject may have added
*
* \returns Void on success, error message on failure.
*/
virtual AZ::Outcome<void, AZStd::string> LoadProject(const IProjectSettings& settings, bool resetPreviousProjects) = 0;
/**
* Gets the description for a Gem.
*
* \param[in] spec The specific Gem to search for.
*
* \returns A pointer to the Gem's description if it exists, otherwise nullptr.
*/
virtual IGemDescriptionConstPtr GetGemDescription(const GemSpecifier& spec) const = 0;
/**
* Gets the description for the latest version of a Gem.
*
* \param[in] uuid The specific uuid of the Gem to search for.
*
* \returns A pointer to the Gem's description highest version if it exists, otherwise nullptr.
*/
virtual IGemDescriptionConstPtr GetLatestGem(const AZ::Uuid& uuid) const = 0;
/**
* Gets a list of all loaded Gem descriptions.
*/
virtual AZStd::vector<IGemDescriptionConstPtr> GetAllGemDescriptions() const = 0;
/**
* Gets a list of all loaded required Gem descriptions.
*/
virtual AZStd::vector<IGemDescriptionConstPtr> GetAllRequiredGemDescriptions() const = 0;
/**
* Get the project-specific gem description if any
*/
virtual IGemDescriptionConstPtr GetProjectGemDescription(const AZStd::string& projectName) const = 0;
/**
* Creates a new instance of IProjectSettings.
*
* \returns A new project settings object.
*/
virtual IProjectSettings* CreateProjectSettings() = 0;
/**
* Destroys an instance of IProjectSettings.
*
* \params[in] settings The settings instance to destroy.
*/
virtual void DestroyProjectSettings(IProjectSettings* settings) = 0;
virtual ~IGemRegistry() = default;
};
/**
* The type of function exported for creating a new GemRegistry.
*/
using RegistryCreatorFunction = IGemRegistry * (*)();
#define GEMS_REGISTRY_CREATOR_FUNCTION_NAME "CreateGemRegistry"
/**
* The type of function exported for destroying a GemRegistry.
*/
using RegistryDestroyerFunction = void(*)(IGemRegistry*);
#define GEMS_REGISTRY_DESTROYER_FUNCTION_NAME "DestroyGemRegistry"
} // namespace Gems
@@ -1,52 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/hash.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/containers/array.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Dependency/Version.h>
#include <initializer_list>
#include <sstream>
namespace Gems
{
using GemVersion = AzFramework::SemanticVersion;
// /**
// * Represents a version of the Lumberyard Game Engine
// */
using EngineVersion = AzFramework::Version<4>;
} // namespace Gems
namespace AZStd
{
template <size_t N>
struct hash<AzFramework::Version<N>>
{
size_t operator()(const AzFramework::Version<N>& ver) const
{
return AZStd::hash_range(ver.m_parts.begin(), ver.m_parts.end());
}
};
template <>
struct hash<AzFramework::SemanticVersion>
: public hash<AzFramework::Version<3>>
{ };
} // namespace AZStd
@@ -1,621 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "GemDescription.h"
#include "GemRegistry.h"
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/JSON/error/en.h>
#include <AzFramework/StringFunc/StringFunc.h>
// For LinkTypeFromString
#include <unordered_map>
#include <string>
namespace Gems
{
GemDescription::GemDescription()
: m_id(AZ::Uuid::CreateNull())
, m_name()
, m_displayName()
, m_version()
, m_path()
, m_absolutePath()
, m_summary()
, m_iconPath()
, m_tags()
, m_modules()
, m_modulesByType()
, m_engineModuleClass()
, m_gemDependencies()
, m_gameGem(false)
, m_required(false)
, m_engineDependency(nullptr)
{
m_modulesByType.emplace(ModuleDefinition::Type::GameModule);
m_modulesByType.emplace(ModuleDefinition::Type::ServerModule);
m_modulesByType.emplace(ModuleDefinition::Type::EditorModule);
m_modulesByType.emplace(ModuleDefinition::Type::StaticLib);
m_modulesByType.emplace(ModuleDefinition::Type::Builder);
m_modulesByType.emplace(ModuleDefinition::Type::Standalone);
}
GemDescription::GemDescription(const GemDescription& rhs)
: m_id(rhs.m_id)
, m_name(rhs.m_name)
, m_displayName(rhs.m_displayName)
, m_version(rhs.m_version)
, m_path(rhs.m_path)
, m_absolutePath(rhs.m_absolutePath)
, m_summary(rhs.m_summary)
, m_iconPath(rhs.m_iconPath)
, m_tags(rhs.m_tags)
, m_modules(rhs.m_modules)
, m_modulesByType(rhs.m_modulesByType)
, m_engineModuleClass(rhs.m_engineModuleClass)
, m_gemDependencies(rhs.m_gemDependencies)
, m_gameGem(rhs.m_gameGem)
, m_required(rhs.m_required)
, m_engineDependency(rhs.m_engineDependency)
{
}
GemDescription::GemDescription(GemDescription&& rhs)
: m_id(rhs.m_id)
, m_name(AZStd::move(rhs.m_name))
, m_displayName(AZStd::move(rhs.m_displayName))
, m_path(AZStd::move(rhs.m_path))
, m_absolutePath(AZStd::move(rhs.m_absolutePath))
, m_summary(AZStd::move(rhs.m_summary))
, m_iconPath(AZStd::move(rhs.m_iconPath))
, m_tags(AZStd::move(rhs.m_tags))
, m_version(rhs.m_version)
, m_modules(AZStd::move(rhs.m_modules))
, m_modulesByType(AZStd::move(rhs.m_modulesByType))
, m_engineModuleClass(AZStd::move(rhs.m_engineModuleClass))
, m_gemDependencies(AZStd::move(rhs.m_gemDependencies))
, m_gameGem(AZStd::move(rhs.m_gameGem))
, m_required(AZStd::move(rhs.m_required))
, m_engineDependency(AZStd::move(rhs.m_engineDependency))
{
rhs.m_id = AZ::Uuid::CreateNull();
rhs.m_version = GemVersion { 0, 0, 0 };
}
// returns whether conversion was successful
static bool LinkTypeFromString(const char* value, LinkType& linkTypeOut)
{
// static map for lookups
static const std::unordered_map<std::string, LinkType> linkNameToType = {
{ GPF_TAG_LINK_TYPE_DYNAMIC, LinkType::Dynamic },
{ GPF_TAG_LINK_TYPE_DYNAMIC_STATIC, LinkType::DynamicStatic },
{ GPF_TAG_LINK_TYPE_NO_CODE, LinkType::NoCode },
};
auto found = linkNameToType.find(value);
if (found != linkNameToType.end())
{
linkTypeOut = found->second;
return true;
}
else
{
return false;
}
}
static bool ModuleTypeFromString(const char* value, ModuleDefinition::Type& moduleTypeOut)
{
static const std::unordered_map<const char*, ModuleDefinition::Type> moduleNameToType = {
{ GPF_TAG_MODULE_TYPE_GAME_MODULE, ModuleDefinition::Type::GameModule },
{ GPF_TAG_MODULE_TYPE_SERVER_MODULE, ModuleDefinition::Type::ServerModule },
{ GPF_TAG_MODULE_TYPE_EDITOR_MODULE, ModuleDefinition::Type::EditorModule },
{ GPF_TAG_MODULE_TYPE_STATIC_LIB, ModuleDefinition::Type::StaticLib },
{ GPF_TAG_MODULE_TYPE_BUILDER, ModuleDefinition::Type::Builder },
{ GPF_TAG_MODULE_TYPE_STANDALONE, ModuleDefinition::Type::Standalone },
};
auto found = AZStd::find_if(moduleNameToType.begin(), moduleNameToType.end(), [&value](decltype(moduleNameToType)::const_reference pair) {
return strcmp(pair.first, value) == 0;
});
if (found != moduleNameToType.end())
{
moduleTypeOut = found->second;
return true;
}
else
{
return false;
}
}
// Bring contents of file up to current version.
AZ::Outcome<void, AZStd::string> UpgradeGemDescriptionJson(rapidjson::Document& descNode)
{
// get format version
if (!RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_FORMAT_VERSION, IsInt))
{
return AZ::Failure(AZStd::string(GPF_TAG_FORMAT_VERSION " int is required."));
}
int gemFormatVersion = descNode[GPF_TAG_FORMAT_VERSION].GetInt();
// decline ancient and future versions
if (gemFormatVersion < 2 || gemFormatVersion > GEM_DEF_FILE_VERSION)
{
return AZ::Failure(AZStd::string::format(GPF_TAG_FORMAT_VERSION " is version %d, but %d is expected.",
gemFormatVersion, GEM_DEF_FILE_VERSION));
}
// upgrade v2 -> v3
if (gemFormatVersion < 3)
{
// beginning in v3 Gems contain an AZ::Module, in the past they contained an IGem
descNode.AddMember("IsLegacyIGem", true, descNode.GetAllocator());
}
// upgrade v3 -> v4
if (gemFormatVersion < 4)
{
// read link type, if not NoCode, migrate to GameModule
if (!RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_LINK_TYPE, IsString))
{
return AZ::Failure(AZStd::string(GPF_TAG_LINK_TYPE " string must not be empty."));
}
// Explicitly copy string so we can remove it from the object
AZStd::string linkTypeString = descNode[GPF_TAG_LINK_TYPE].GetString();
descNode.RemoveMember(GPF_TAG_LINK_TYPE);
LinkType linkType;
if (!LinkTypeFromString(linkTypeString.c_str(), linkType))
{
return AZ::Failure(AZStd::string(GPF_TAG_LINK_TYPE " string is invalid."));
}
// If no-code, don't make module definitions
if (linkType != LinkType::NoCode)
{
// Create modules list
rapidjson::Value modulesList{ rapidjson::kArrayType };
// Create module definition
{
rapidjson::Value gameModule{ rapidjson::kObjectType };
gameModule.AddMember(GPF_TAG_MODULE_TYPE, GPF_TAG_MODULE_TYPE_GAME_MODULE, descNode.GetAllocator());
gameModule.AddMember(GPF_TAG_LINK_TYPE, rapidjson::Value(linkTypeString.c_str(), descNode.GetAllocator()), descNode.GetAllocator());
modulesList.PushBack(AZStd::move(gameModule), descNode.GetAllocator());
}
// Create server module definition
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_MODULE_TYPE_SERVER_MODULE, IsBool) && descNode[GPF_TAG_MODULE_TYPE_SERVER_MODULE].GetBool())
{
rapidjson::Value serverModule{ rapidjson::kObjectType };
serverModule.AddMember(GPF_TAG_MODULE_TYPE, GPF_TAG_MODULE_TYPE_SERVER_MODULE, descNode.GetAllocator());
serverModule.AddMember(GPF_TAG_LINK_TYPE, rapidjson::Value(linkTypeString.c_str(), descNode.GetAllocator()), descNode.GetAllocator());
serverModule.AddMember(GPF_TAG_MODULE_NAME, "Server", descNode.GetAllocator());
modulesList.PushBack(AZStd::move(serverModule), descNode.GetAllocator());
}
// Create editor module definition
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_EDITOR_MODULE, IsBool) && descNode[GPF_TAG_EDITOR_MODULE].GetBool())
{
rapidjson::Value editorModule{ rapidjson::kObjectType };
editorModule.AddMember(GPF_TAG_MODULE_TYPE, GPF_TAG_MODULE_TYPE_EDITOR_MODULE, descNode.GetAllocator());
editorModule.AddMember(GPF_TAG_MODULE_NAME, "Editor", descNode.GetAllocator());
editorModule.AddMember(GPF_TAG_MODULE_EXTENDS, "GameModule", descNode.GetAllocator());
modulesList.PushBack(AZStd::move(editorModule), descNode.GetAllocator());
}
descNode.RemoveMember(GPF_TAG_EDITOR_MODULE);
// Add modules list to
descNode.AddMember(GPF_TAG_MODULES, modulesList, descNode.GetAllocator());
}
}
// file is now up to date
descNode[GPF_TAG_FORMAT_VERSION] = GEM_DEF_FILE_VERSION;
return AZ::Success();
}
AZ::Outcome<GemDescription, AZStd::string> GemDescription::CreateFromJson(
rapidjson::Document& descNode,
const AZStd::string& gemFolderPath,
const AZStd::string& absoluteFilePath)
{
// gem to build
GemDescription gem;
gem.m_path = gemFolderPath;
gem.m_absolutePath = absoluteFilePath;
AzFramework::StringFunc::Path::StripFullName(gem.m_absolutePath);
AzFramework::StringFunc::RChop(gem.m_absolutePath, 1);
if (!descNode.IsObject())
{
return AZ::Failure(AZStd::string("Json root element must be an object."));
}
// upgrade contents to current version
auto upgradeOutcome = UpgradeGemDescriptionJson(descNode);
if (!upgradeOutcome.IsSuccess())
{
return AZ::Failure(upgradeOutcome.TakeError());
}
// read name
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_NAME, IsString))
{
gem.m_name = descNode[GPF_TAG_NAME].GetString();
}
else
{
return AZ::Failure(AZStd::string(GPF_TAG_NAME " string must not be empty."));
}
// read display name
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_DISPLAY_NAME, IsString))
{
gem.m_displayName = descNode[GPF_TAG_DISPLAY_NAME].GetString();
}
// read id
if (!RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_UUID, IsString))
{
return AZ::Failure(AZStd::string(GPF_TAG_UUID " string is required."));
}
gem.m_id = AZ::Uuid::CreateString(descNode[GPF_TAG_UUID].GetString());
if (gem.m_id.IsNull())
{
return AZ::Failure(AZStd::string::format(GPF_TAG_UUID " string \"%s\" is invalid.", descNode[GPF_TAG_UUID].GetString()));
}
// read version
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_VERSION, IsString))
{
auto versionOutcome = GemVersion::ParseFromString(descNode[GPF_TAG_VERSION].GetString());
if (versionOutcome)
{
gem.m_version = versionOutcome.GetValue();
}
else
{
return AZ::Failure(AZStd::string(versionOutcome.GetError()));
}
}
else
{
return AZ::Failure(AZStd::string(GPF_TAG_VERSION " string is required."));
}
// To reduce the potential for user error, a Gem depending on the Lumberyard engine version
// supports both arrays and strings.
if (descNode.HasMember(GPF_TAG_LY_VERSION))
{
AZStd::vector<AZStd::string> versionConstraints;
// read version constraints
// Check if the version constraint is a string first.
if(RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_LY_VERSION, IsString))
{
AZStd::string constraintString = descNode[GPF_TAG_LY_VERSION].GetString();
versionConstraints.push_back(constraintString);
}
// If it wasn't a string, make sure it's an array. If not, error out.
else if (!RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_LY_VERSION, IsArray))
{
return AZ::Failure(AZStd::string(GPF_TAG_LY_VERSION " array is required for engine version."));
}
// If it's an empty array, ignore it.
// For ease of use editing the Gem.json files, we support empty arrays, so users
// can leave the engine version key in without providing a value.
else if (descNode[GPF_TAG_LY_VERSION].Size() > 0)
{
const auto& constraints = descNode[GPF_TAG_LY_VERSION];
const auto& end = constraints.End();
for (auto it = constraints.Begin(); it != end; ++it)
{
const auto& constraint = *it;
if (!constraint.IsString())
{
return AZ::Failure(AZStd::string(GPF_TAG_LY_VERSION " array for engine version must contain strings."));
}
versionConstraints.push_back(constraint.GetString());
}
}
// If constraints were actually provided, create the dependency.
if(versionConstraints.size() > 0)
{
EngineDependency dep;
dep.SetID(AZ::Uuid::CreateNull());
AZ::Outcome<void, AZStd::string> outcome = dep.ParseVersions(versionConstraints);
if (!outcome)
{
return AZ::Failure(AZStd::string::format(GPF_TAG_LY_VERSION " for engine version is invalid. %s", outcome.GetError().c_str()));
}
gem.m_engineDependency = AZStd::make_shared<EngineDependency>(dep);
}
}
// dependencies
if (descNode.HasMember(GPF_TAG_DEPENDENCIES))
{
if (!descNode[GPF_TAG_DEPENDENCIES].IsArray())
{
return AZ::Failure(AZStd::string(GPF_TAG_DEPENDENCIES " must be an array."));
}
// List of descriptions of Gems we depend upon
const rapidjson::Value& depsNode = descNode[GPF_TAG_DEPENDENCIES];
const auto& end = depsNode.End();
for (auto it = depsNode.Begin(); it != end; ++it)
{
const auto& depNode = *it;
if (!depNode.IsObject())
{
return AZ::Failure(AZStd::string(GPF_TAG_DEPENDENCIES " must contain objects."));
}
// read id
if (!RAPIDJSON_IS_VALID_MEMBER(depNode, GPF_TAG_UUID, IsString))
{
return AZ::Failure(AZStd::string(GPF_TAG_UUID " string is required for dependency."));
}
const char* idStr = depNode[GPF_TAG_UUID].GetString();
AZ::Uuid id(idStr);
if (id.IsNull())
{
return AZ::Failure(AZStd::string::format(GPF_TAG_UUID " in dependency is invalid: %s.", idStr));
}
// read version constraints
if (!RAPIDJSON_IS_VALID_MEMBER(depNode, GPF_TAG_VERSION_CONSTRAINTS, IsArray))
{
return AZ::Failure(AZStd::string(GPF_TAG_VERSION_CONSTRAINTS " array is required for dependency."));
}
// Make sure versions are specified
if (depNode[GPF_TAG_VERSION_CONSTRAINTS].Size() < 1)
{
return AZ::Failure(AZStd::string(GPF_TAG_VERSION_CONSTRAINTS " must have at least 1 entry for dependency."));
}
AZStd::vector<AZStd::string> versionConstraints;
const auto& constraints = depNode[GPF_TAG_VERSION_CONSTRAINTS];
const auto& constraintsEnd(constraints.End());
for (auto constraintIt = constraints.Begin(); constraintIt != constraintsEnd; ++constraintIt)
{
const auto& constraint = *constraintIt;
if (!constraint.IsString())
{
return AZ::Failure(AZStd::string(GPF_TAG_VERSION_CONSTRAINTS " array for dependency must contain strings."));
}
versionConstraints.push_back(constraint.GetString());
}
// create Dependency
GemDependency dep;
dep.SetID(id);
if (!dep.ParseVersions(versionConstraints))
{
return AZ::Failure(AZStd::string(GPF_TAG_VERSION_CONSTRAINTS " for dependency is invalid"));
}
gem.m_gemDependencies.push_back(AZStd::make_shared<GemDependency>(dep));
}
}
// Is Game Gem? flag
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_IS_GAME_GEM, IsBool))
{
gem.m_gameGem = descNode[GPF_TAG_IS_GAME_GEM].GetBool();
}
else
{
gem.m_gameGem = false;
}
// Is Required? flag
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_IS_REQUIRED, IsBool))
{
gem.m_required = descNode[GPF_TAG_IS_REQUIRED].GetBool();
}
else
{
gem.m_required = false;
}
// optional metadata
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_SUMMARY, IsString))
{
gem.m_summary = descNode[GPF_TAG_SUMMARY].GetString();
}
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_ICON_PATH, IsString))
{
gem.m_iconPath = descNode[GPF_TAG_ICON_PATH].GetString();
}
if (descNode.HasMember(GPF_TAG_TAGS))
{
const rapidjson::Value& tags = descNode[GPF_TAG_TAGS];
if (tags.IsArray())
{
const auto& end = tags.End();
for (auto it = tags.Begin(); it != end; ++it)
{
const auto& tag = *it;
gem.m_tags.push_back(tag.GetString());
}
}
else
{
return AZ::Failure(AZStd::string("Value for key " GPF_TAG_TAGS " must be an array."));
}
}
// engine module class
gem.m_engineModuleClass = RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_MODULE_CLASS, IsString)
? descNode[GPF_TAG_MODULE_CLASS].GetString()
: gem.GetName() + AZStd::string("Gem");
// Cache constants
char idStr[UUID_STR_BUF_LEN];
gem.GetID().ToString(idStr, UUID_STR_BUF_LEN, false, false);
AZStd::to_lower(idStr, idStr + strlen(idStr));
// Read the modules list
if (RAPIDJSON_IS_VALID_MEMBER(descNode, GPF_TAG_MODULES, IsArray))
{
bool foundDefaultModule = false;
AZStd::unordered_map<AZStd::string, AZStd::shared_ptr<ModuleDefinition>> modulesByName;
AZStd::vector<AZStd::pair<AZStd::shared_ptr<ModuleDefinition>, AZStd::string>> dependencies;
const rapidjson::Value& modulesNode = descNode[GPF_TAG_MODULES];
for (auto moduleObjPtr = modulesNode.Begin(); moduleObjPtr != modulesNode.End(); ++moduleObjPtr)
{
const rapidjson::Value& moduleObj = *moduleObjPtr;
if (!moduleObj.IsObject())
{
return AZ::Failure(AZStd::string("Each object in " GPF_TAG_MODULES " must be an object!"));
}
auto modulePtr = AZStd::make_shared<ModuleDefinition>();
gem.m_modules.emplace_back(modulePtr);
// Get the module type
if (!RAPIDJSON_IS_VALID_MEMBER(moduleObj, GPF_TAG_MODULE_TYPE, IsString))
{
return AZ::Failure(AZStd::string("Each module requires a " GPF_TAG_MODULE_TYPE " field."));
}
const char* moduleTypeStr = moduleObj[GPF_TAG_MODULE_TYPE].GetString();
if (!ModuleTypeFromString(moduleTypeStr, modulePtr->m_type))
{
return AZ::Failure(AZStd::string::format("Module type %s is invalid!", moduleTypeStr));
}
// Get the module name (default to the type)
if (RAPIDJSON_IS_VALID_MEMBER(moduleObj, GPF_TAG_MODULE_NAME, IsString))
{
modulePtr->m_name = moduleObj[GPF_TAG_MODULE_NAME].GetString();
}
else if (modulePtr->m_type == ModuleDefinition::Type::GameModule || modulePtr->m_type == ModuleDefinition::Type::ServerModule)
{
modulePtr->m_name = moduleTypeStr;
}
else
{
return AZ::Failure(AZStd::string::format("Default \"" GPF_TAG_MODULE_NAME "\" field is only supported for modules of type \"GameModule\", not %s.", moduleTypeStr));
}
// Check for duplicate names
if (modulesByName.find(modulePtr->m_name) != modulesByName.end())
{
return AZ::Failure(AZStd::string::format("Module name \"%s\" is used more than once!", modulePtr->m_name.c_str()));
}
// If the type is GameModule, omit name from file name (maintains functionality of v3)
if (modulePtr->m_type == ModuleDefinition::Type::GameModule || modulePtr->m_type == ModuleDefinition::Type::ServerModule)
{
if (!foundDefaultModule)
{
foundDefaultModule = true;
// if the module name for 'GameModule' type is specified, such as 'Private' then it needs to be appended into the gem name
if (modulePtr->m_name != moduleTypeStr)
{
modulePtr->m_fileName = AZStd::string::format("Gem.%s.%s.%s.v%s", gem.GetName().c_str(), modulePtr->m_name.c_str(), idStr, gem.GetVersion().ToString().c_str());
}
else
{
modulePtr->m_fileName = AZStd::string::format("Gem.%s.%s.v%s", gem.GetName().c_str(), idStr, gem.GetVersion().ToString().c_str());
}
}
// If LinkType is specified, read and validate it.
if (RAPIDJSON_IS_VALID_MEMBER(moduleObj, GPF_TAG_LINK_TYPE, IsString))
{
const char* linkTypeStr = moduleObj[GPF_TAG_LINK_TYPE].GetString();
if (!LinkTypeFromString(linkTypeStr, modulePtr->m_linkType))
{
return AZ::Failure(AZStd::string::format(GPF_TAG_LINK_TYPE " specified (\"%s\") is invalid", linkTypeStr));
}
}
}
// If the module needs a file name, populate it.
if (modulePtr->m_fileName.empty() && modulePtr->m_type != ModuleDefinition::Type::StaticLib)
{
modulePtr->m_fileName = AZStd::string::format("Gem.%s.%s.%s.v%s", gem.GetName().c_str(), modulePtr->m_name.c_str(), idStr, gem.GetVersion().ToString().c_str());
}
modulesByName.emplace(modulePtr->m_name, modulePtr);
// Populate extensions
if (modulePtr->m_type != ModuleDefinition::Type::StaticLib &&
RAPIDJSON_IS_VALID_MEMBER(moduleObj, GPF_TAG_MODULE_EXTENDS, IsString))
{
dependencies.emplace_back(modulePtr, AZStd::string(moduleObj[GPF_TAG_MODULE_EXTENDS].GetString()));
}
}
// Populate dependencies
for (const auto& dependencyPair : dependencies)
{
auto dependencyIterator = modulesByName.find(dependencyPair.second);
if (dependencyIterator == modulesByName.end())
{
return AZ::Failure(AZStd::string::format("Module \"%s\" depends on \"" GPF_TAG_MODULE_EXTENDS "\" invalid module \"%s\"", dependencyPair.first->m_name.c_str(), dependencyPair.second.c_str()));
}
if (dependencyIterator->second->m_type != ModuleDefinition::Type::GameModule && dependencyIterator->second->m_type != ModuleDefinition::Type::ServerModule)
{
return AZ::Failure(AZStd::string::format("Modules may only \"" GPF_TAG_MODULE_EXTENDS "\" modules of type \"" GPF_TAG_MODULE_TYPE_GAME_MODULE "\", " GPF_TAG_MODULE_TYPE_SERVER_MODULE "\"."));
}
dependencyPair.first->m_parent = dependencyIterator->second;
dependencyIterator->second->m_children.emplace_back(dependencyPair.first);
}
// Populate modulesByType
for (const auto& modulePtr : gem.m_modules)
{
gem.m_modulesByType[modulePtr->m_type].emplace_back(modulePtr);
// If this module is a GameModule, and there is no Editor override, apply it to Editor as well.
if (modulePtr->m_type == ModuleDefinition::Type::GameModule)
{
bool foundEditorModule = false;
// Check children for editor modules
for (const auto& childWeak : modulePtr->m_children)
{
auto child = childWeak.lock();
AZ_Assert(child, "Child somehow out of scope already!");
if (child->m_type == ModuleDefinition::Type::EditorModule)
{
foundEditorModule = true;
break;
}
}
if (!foundEditorModule)
{
// If no children are for editor, add module to editor list
gem.m_modulesByType[ModuleDefinition::Type::EditorModule].emplace_back(modulePtr);
}
}
}
}
return AZ::Success(AZStd::move(gem));
}
} // namespace Gems
@@ -1,100 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "GemRegistry/IGemRegistry.h"
#include <AzCore/std/functional.h>
#include <AzCore/JSON/document.h>
namespace Gems
{
class GemDescription
: public IGemDescription
{
public:
GemDescription(const GemDescription& rhs);
GemDescription(GemDescription&& rhs);
~GemDescription() override = default;
// IGemDescription
const AZ::Uuid& GetID() const override { return m_id; }
const AZStd::string& GetName() const override { return m_name; }
const AZStd::string& GetDisplayName() const override { return m_displayName.empty() ? m_name : m_displayName; }
const GemVersion& GetVersion() const override { return m_version; }
const AZStd::string& GetPath() const override { return m_path; }
const AZStd::string& GetAbsolutePath() const override { return m_absolutePath; }
const AZStd::string& GetSummary() const override { return m_summary; }
const AZStd::string& GetIconPath() const override { return m_iconPath; }
const AZStd::vector<AZStd::string>& GetTags() const override { return m_tags; }
const ModuleDefinitionVector& GetModules() const override { return m_modules; }
const ModuleDefinitionVector& GetModulesOfType(ModuleDefinition::Type type) const override { return m_modulesByType.find(type)->second; }
const AZStd::string& GetEngineModuleClass() const override { return m_engineModuleClass; }
const AZStd::vector<AZStd::shared_ptr<GemDependency> >& GetGemDependencies() const override { return m_gemDependencies; }
const bool IsGameGem() const override { return m_gameGem; }
const bool IsRequired() const override { return m_required; }
const AZStd::shared_ptr<EngineDependency> GetEngineDependency() const override { return m_engineDependency; }
// ~IGemDescription
// Internal methods
/// Create GemDescription from Json.
///
/// \param[in] json Json object to parse. json may be modified during parse.
/// \param[in] gemFolderPath Relative path from engine root to Gem folder.
///
/// \returns If successful, the GemDescription.
/// If unsuccessful, an explanation why parsing failed.
static AZ::Outcome<GemDescription, AZStd::string> CreateFromJson(
rapidjson::Document& json,
const AZStd::string& gemFolderPath,
const AZStd::string& absoluteFilePath);
private:
// Outsiders may not create an empty GemDescription
GemDescription();
/// The ID of the Gem
AZ::Uuid m_id;
/// The name of the Gem
AZStd::string m_name;
/// The UI-friendly name of the Gem
AZStd::string m_displayName;
/// The version of the Gem
GemVersion m_version;
/// Relative path to Gem folder
AZStd::string m_path;
/// Absolute path to Gem folder
AZStd::string m_absolutePath;
/// Summary description of the Gem
AZStd::string m_summary;
/// Icon path of the gem
AZStd::string m_iconPath;
/// Tags to associate with the Gem
AZStd::vector<AZStd::string> m_tags;
/// List of modules produced by the Gem
ModuleDefinitionVector m_modules;
/// All modules to be loaded for a given function
AZStd::unordered_map<ModuleDefinition::Type, ModuleDefinitionVector> m_modulesByType;
/// The name of the engine module class to initialize
AZStd::string m_engineModuleClass;
/// A Gem's dependencies
AZStd::vector<AZStd::shared_ptr<GemDependency> > m_gemDependencies;
/// Flag to indicate if this is a Game GEM
bool m_gameGem;
/// Flag to indicate that this is a required GEM
bool m_required;
/// A Gem's engine dependency
AZStd::shared_ptr<EngineDependency> m_engineDependency = nullptr;
};
} // namespace Gems
@@ -1,425 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ProjectSettings.h"
#include "GemRegistry.h"
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/error/en.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace Gems
{
AZ_CLASS_ALLOCATOR_IMPL(GemRegistry, AZ::SystemAllocator, 0)
AZ::Outcome<void, AZStd::string> GemRegistry::AddSearchPath(const SearchPath& searchPathIn, bool loadGemsNow)
{
SearchPath searchPath = searchPathIn;
// Remove trailing slash if present
char lastChar = *(searchPath.m_path.end() - 1);
if (lastChar == '/' ||
lastChar == '\\')
{
AzFramework::StringFunc::RChop(searchPath.m_path, 1);
}
if (AZStd::find(m_searchPaths.begin(), m_searchPaths.end(), searchPath) == m_searchPaths.end())
{
m_searchPaths.emplace_back(searchPath);
}
if (loadGemsNow)
{
return LoadGemsFromDir(searchPath);
}
else
{
return AZ::Success();
}
}
AZ::Outcome<void, AZStd::string> GemRegistry::LoadAllGemsFromDisk()
{
AZStd::string errorString;
for (const auto& searchPath : m_searchPaths)
{
auto pathOutcome = LoadGemsFromDir(searchPath);
if (!pathOutcome.IsSuccess())
{
errorString += pathOutcome.GetError() + "\n";
}
}
if (errorString.empty())
{
return AZ::Success();
}
else
{
// Remove trailing \n
return AZ::Failure(errorString.substr(0, errorString.length() - 1));
}
}
AZ::Outcome<void, AZStd::string> GemRegistry::LoadProject(const IProjectSettings& settings, bool resetPreviousProjects)
{
if (resetPreviousProjects)
{
m_gemDescs.clear();
}
for (const auto& pair : settings.GetGems())
{
const char* absolutePath = nullptr;
// First priority goes to the project root's folder
AZStd::string testGemPath = settings.GetProjectRootPath();
if (AzFramework::StringFunc::Path::ConstructFull(settings.GetProjectRootPath().c_str(), pair.second.m_path.c_str(), testGemPath, true))
{
if (AzFramework::StringFunc::Path::Join(testGemPath.c_str(), GEM_DEF_FILE, testGemPath))
{
if (AZ::IO::SystemFile::Exists(testGemPath.c_str()))
{
absolutePath = testGemPath.c_str();
}
}
}
auto loadOutcome = LoadGemDescription(pair.second.m_path, absolutePath);
if (!loadOutcome.IsSuccess())
{
return AZ::Failure(loadOutcome.GetError());
}
}
return AZ::Success();
}
IGemDescriptionConstPtr GemRegistry::GetGemDescription(const GemSpecifier& spec) const
{
IGemDescriptionConstPtr result;
auto idIt = m_gemDescs.find(spec.m_id);
if (idIt != m_gemDescs.end())
{
auto versionIt = idIt->second.find(spec.m_version);
if (versionIt != idIt->second.end())
{
result = AZStd::static_pointer_cast<IGemDescriptionConstPtr::element_type>(versionIt->second);
}
}
return result;
}
IGemDescriptionConstPtr GemRegistry::GetLatestGem(const AZ::Uuid& uuid) const
{
IGemDescriptionConstPtr result;
auto idIt = m_gemDescs.find(uuid);
if (idIt != m_gemDescs.end())
{
GemVersion latestVersion;
GemDescriptionPtr desc;
for (const auto& pair : idIt->second)
{
if (pair.first > latestVersion)
{
latestVersion = pair.first;
desc = pair.second;
}
}
if (!latestVersion.IsZero())
{
result = AZStd::static_pointer_cast<IGemDescriptionConstPtr::element_type>(desc);
}
}
return result;
}
AZStd::vector<IGemDescriptionConstPtr> GemRegistry::GetAllGemDescriptions() const
{
AZStd::vector<IGemDescriptionConstPtr> results;
for (auto && idIt : m_gemDescs)
{
for (auto && versionIt : idIt.second)
{
results.push_back(AZStd::static_pointer_cast<IGemDescriptionConstPtr::element_type>(versionIt.second));
}
}
return results;
}
AZStd::vector<IGemDescriptionConstPtr> GemRegistry::GetAllRequiredGemDescriptions() const
{
AZStd::vector<IGemDescriptionConstPtr> results;
for (auto && idIt : m_gemDescs)
{
for (auto && versionIt : idIt.second)
{
if (versionIt.second->IsRequired())
{
results.push_back(AZStd::static_pointer_cast<IGemDescriptionConstPtr::element_type>(versionIt.second));
}
}
}
return results;
}
IGemDescriptionConstPtr GemRegistry::GetProjectGemDescription(const AZStd::string& projectName) const
{
IGemDescriptionConstPtr result;
// We know searchPaths[0] is the old engine root, so we'll just use that to avoid a search
AZStd::string gemFolderPath = projectName + "/Gem";
auto descOutcome = ParseToGemDescription(gemFolderPath, nullptr);
if (descOutcome)
{
result = AZStd::make_shared<GemDescription>(descOutcome.TakeValue());
}
return result;
}
IProjectSettings* GemRegistry::CreateProjectSettings()
{
return aznew ProjectSettings(this);
}
void GemRegistry::DestroyProjectSettings(IProjectSettings* settings)
{
delete settings;
}
AZ::Outcome<void, AZStd::string> GemRegistry::LoadGemsFromDir(const SearchPath& searchPath)
{
AZ::IO::LocalFileIO fileIo;
AZStd::string errorString;
// Handles each file and directory found
// Safe to capture all by reference because the find will run sync
AZ::IO::LocalFileIO::FindFilesCallbackType fileFinderCb;
fileFinderCb = [&](const char* fullPath) -> bool
{
if (fileIo.IsDirectory(fullPath))
{
// recurse into subdirectory
// "*" filter will match all files/directories except specials ('.', '..', etc.) with the fewest compares
fileIo.FindFiles(fullPath, "*", fileFinderCb);
}
else
{
AZStd::string fileName;
AzFramework::StringFunc::Path::GetFullFileName(fullPath, fileName);
if (0 == azstricmp(fileName.c_str(), GEM_DEF_FILE))
{
// need relative path to gem folder, so strip searchPath from front and Gem.json from back
AZStd::string gemFolderRelPath = fullPath + searchPath.m_path.length();
AzFramework::StringFunc::Path::StripFullName(gemFolderRelPath);
AzFramework::StringFunc::RChop(gemFolderRelPath, 1); // Remove trailing '/'
auto skipPathFirstSepIndex = gemFolderRelPath.find_first_not_of("/\\");
gemFolderRelPath = gemFolderRelPath.substr(skipPathFirstSepIndex);
auto loadOutcome = LoadGemDescription(gemFolderRelPath, fullPath);
if (loadOutcome.IsSuccess() == false)
{
errorString += AZStd::string::format("Fail to load Gems from path %s disk. %s\n", searchPath.m_path.c_str(), loadOutcome.GetError().c_str());
}
// We found the Gem.json file but we have to keep looking to support nested gems
}
}
return true; // keep searching
};
// Scans subdirectories
fileIo.FindFiles(searchPath.m_path.c_str(), searchPath.m_filter.c_str(), fileFinderCb);
if (errorString.empty())
{
return AZ::Success();
}
else
{
// Remove trailing \n
return AZ::Failure(errorString.substr(0, errorString.length() - 1));
}
}
AZ::Outcome<IGemDescriptionConstPtr, AZStd::string> GemRegistry::LoadGemDescription(const AZStd::string& gemFolderPath, const char* absoluteFilePath)
{
auto descOutcome = ParseToGemDescription(gemFolderPath, absoluteFilePath);
if (!descOutcome)
{
return AZ::Failure(AZStd::string::format("An error occurred while parsing %s: %s", gemFolderPath.c_str(), descOutcome.GetError().c_str()));
}
auto desc = AZStd::make_shared<GemDescription>(descOutcome.TakeValue());
// If the Gem hasn't been loaded yet, add it's id to the root map
auto idIt = m_gemDescs.find(desc->GetID());
if (idIt == m_gemDescs.end())
{
idIt = m_gemDescs.emplace(desc->GetID(), AZStd::unordered_map<GemVersion, GemDescriptionPtr>()).first;
}
// If the Gem's version doesn't exist, add it, otherwise update it
auto versionIt = idIt->second.find(desc->GetVersion());
if (versionIt == idIt->second.end())
{
idIt->second.emplace(desc->GetVersion(), AZStd::move(desc));
}
else
{
versionIt->second = desc;
}
return AZ::Success(AZStd::static_pointer_cast<IGemDescriptionConstPtr::element_type>(desc));
}
AZ::Outcome<IGemDescriptionConstPtr, AZStd::string> GemRegistry::ParseToGemDescriptionPtr(const AZStd::string& gemFolderRelPath, const char* absoluteFilePath)
{
auto descOutcome = ParseToGemDescription(gemFolderRelPath, absoluteFilePath);
if (!descOutcome)
{
return AZ::Failure(AZStd::string::format("An error occurred while parsing %s: %s", gemFolderRelPath.c_str(), descOutcome.GetError().c_str()));
}
auto desc = AZStd::make_shared<GemDescription>(descOutcome.TakeValue());
return AZ::Success(AZStd::static_pointer_cast<IGemDescriptionConstPtr::element_type>(desc));
}
AZ::Outcome<GemDescription, AZStd::string> GemRegistry::ParseToGemDescription(const AZStd::string& gemFolderPath, const char* absoluteFilePath) const
{
// do we have a pluggable, engine-compatible fileIO? For things like tools, we may not be plugged
// into a game engine, and thus, we may need to use raw file io.
AZ::IO::FileIOBase* fileReader = AZ::IO::FileIOBase::GetInstance();
// build absolute path to gem file
AZStd::string filePath;
if (absoluteFilePath)
{
filePath = absoluteFilePath;
}
else
{
for (const auto& searchPath : m_searchPaths)
{
// Append relative path to search path
AzFramework::StringFunc::Path::Join(searchPath.m_path.c_str(), gemFolderPath.c_str(), filePath);
// Append file name to file path
AzFramework::StringFunc::Path::Join(filePath.c_str(), GEM_DEF_FILE, filePath);
// note that paths are case sensitive on some systems.
if (fileReader)
{
if (fileReader->Exists(filePath.c_str()))
{
break;
}
}
else
{
if (AZ::IO::SystemFile::Exists(filePath.c_str()))
{
break;
}
}
}
}
// read json
AZStd::string fileBuf;
if (fileReader)
{
// an engine compatible file reader has been attached, so use that.
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ::u64 fileSize = 0;
if (!fileReader->Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, fileHandle))
{
return AZ::Failure(AZStd::string::format("Failed to read %s - file read failed.", filePath.c_str()));
}
if ((!fileReader->Size(fileHandle, fileSize)) || (fileSize == 0))
{
fileReader->Close(fileHandle);
return AZ::Failure(AZStd::string::format("Failed to read %s - file read failed.", filePath.c_str()));
}
fileBuf.resize(fileSize);
if (!fileReader->Read(fileHandle, fileBuf.data(), fileSize, true))
{
fileReader->Close(fileHandle);
return AZ::Failure(AZStd::string::format("Failed to read %s - file read failed.", filePath.c_str()));
}
fileReader->Close(fileHandle);
}
else
{
// we don't have an engine file io, use raw file IO.
AZ::IO::SystemFile rawFile;
if (!rawFile.Open(filePath.c_str(), AZ::IO::SystemFile::OpenMode::SF_OPEN_READ_ONLY))
{
return AZ::Failure(AZStd::string::format("Failed to read %s - file read failed.", filePath.c_str()));
}
fileBuf.resize(rawFile.Length());
if (fileBuf.size() == 0)
{
return AZ::Failure(AZStd::string::format("Failed to read %s - file read failed.", filePath.c_str()));
}
if (rawFile.Read(fileBuf.size(), fileBuf.data()) != fileBuf.size())
{
return AZ::Failure(AZStd::string::format("Failed to read %s - file read failed.", filePath.c_str()));
}
}
rapidjson::Document document;
document.Parse(fileBuf.data());
if (document.HasParseError())
{
const char* errorStr = rapidjson::GetParseError_En(document.GetParseError());
return AZ::Failure(AZStd::string::format("Failed to parse %s: %s", filePath.c_str(), errorStr));
}
return GemDescription::CreateFromJson(document, gemFolderPath, filePath);
}
} // namespace Gems
//////////////////////////////////////////////////////////////////////////
// DLL Exported Functions
//////////////////////////////////////////////////////////////////////////
#ifndef AZ_MONOLITHIC_BUILD // Module init functions, only required when building as a DLL.
AZ_DECLARE_MODULE_INITIALIZATION
#endif//AZ_MONOLITHIC_BUILD
extern "C" AZ_DLL_EXPORT Gems::IGemRegistry * CreateGemRegistry()
{
return aznew Gems::GemRegistry();
}
extern "C" AZ_DLL_EXPORT void DestroyGemRegistry(Gems::IGemRegistry* reg)
{
delete reg;
}
-105
View File
@@ -1,105 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "GemRegistry/IGemRegistry.h"
#include "GemDescription.h"
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
// Constants
#define UUID_STR_BUF_LEN 64
#define GEMS_ASSETS_FOLDER "Assets"
#define GEM_DEF_FILE "gem.json"
#define GEM_DEF_FILE_VERSION 4
#define GEMS_PROJECT_FILE "gems.json"
#define GEMS_PROJECT_FILE_VERSION 2
#define PROJECT_CONFIG_FILE "project.json"
// Gem project file JSON tags
#define GPF_TAG_FORMAT_VERSION "GemFormatVersion"
#define GPF_TAG_LIST_FORMAT_VERSION "GemListFormatVersion"
#define GPF_TAG_NAME "Name"
#define GPF_TAG_DISPLAY_NAME "DisplayName"
#define GPF_TAG_GEM_ARRAY "Gems"
#define GPF_TAG_UUID "Uuid"
#define GPF_TAG_LY_VERSION "LumberyardVersion"
#define GPF_TAG_VERSION "Version"
#define GPF_TAG_DEPENDENCIES "Dependencies"
#define GPF_TAG_VERSION_CONSTRAINTS "VersionConstraints"
#define GPF_TAG_PATH "Path"
#define GPF_TAG_MODULE_CLASS "EngineModuleClass"
#define GPF_TAG_EDITOR_MODULE "EditorModule"
#define GPF_TAG_SUMMARY "Summary"
#define GPF_TAG_ICON_PATH "IconPath"
#define GPF_TAG_TAGS "Tags"
#define GPF_TAG_LINK_TYPE "LinkType"
#define GPF_TAG_LINK_TYPE_DYNAMIC "Dynamic"
#define GPF_TAG_LINK_TYPE_DYNAMIC_STATIC "DynamicStatic"
#define GPF_TAG_LINK_TYPE_NO_CODE "NoCode"
#define GPF_TAG_MODULES "Modules"
#define GPF_TAG_MODULE_NAME "Name"
#define GPF_TAG_MODULE_TYPE "Type"
#define GPF_TAG_MODULE_TYPE_GAME_MODULE "GameModule"
#define GPF_TAG_MODULE_TYPE_SERVER_MODULE "ServerModule"
#define GPF_TAG_MODULE_TYPE_EDITOR_MODULE "EditorModule"
#define GPF_TAG_MODULE_TYPE_STATIC_LIB "StaticLib"
#define GPF_TAG_MODULE_TYPE_BUILDER "Builder"
#define GPF_TAG_MODULE_TYPE_STANDALONE "Standalone"
#define GPF_TAG_MODULE_EXTENDS "Extends"
#define GPF_TAG_IS_GAME_GEM "IsGameGem"
#define GPF_TAG_IS_REQUIRED "IsRequired"
#define GPF_TAG_COMMENT "_comment"
namespace Gems
{
class GemRegistry
: public IGemRegistry
{
public:
AZ_CLASS_ALLOCATOR_DECL;
//////////////////////////////////////////////////////////////////////////
// IGemRegistry
AZ::Outcome<void, AZStd::string> AddSearchPath(const SearchPath& searchPath, bool loadGemsNow) override;
AZ::Outcome<void, AZStd::string> LoadAllGemsFromDisk() override;
AZ::Outcome<void, AZStd::string> LoadProject(const IProjectSettings& settings, bool resetPreviousProjects) override;
AZ::Outcome<IGemDescriptionConstPtr, AZStd::string> ParseToGemDescriptionPtr(const AZStd::string& gemFolderRelPath, const char* absoluteFilePath) override;
IGemDescriptionConstPtr GetGemDescription(const GemSpecifier& spec) const override;
IGemDescriptionConstPtr GetLatestGem(const AZ::Uuid& uuid) const override;
AZStd::vector<IGemDescriptionConstPtr> GetAllGemDescriptions() const override;
AZStd::vector<IGemDescriptionConstPtr> GetAllRequiredGemDescriptions() const override;
IGemDescriptionConstPtr GetProjectGemDescription(const AZStd::string& projectName) const override;
IProjectSettings* CreateProjectSettings() override;
void DestroyProjectSettings(IProjectSettings* settings) override;
~GemRegistry() override = default;
//////////////////////////////////////////////////////////////////////////
private:
using GemDescriptionPtr = AZStd::shared_ptr<GemDescription>;
AZ::Outcome<void, AZStd::string> LoadGemsFromDir(const SearchPath& searchPath);
// Pass nullptr for absoluteFolderPath to do a search for the Gem
AZ::Outcome<IGemDescriptionConstPtr, AZStd::string> LoadGemDescription(const AZStd::string& gemFolderPath, const char* absoluteFilePath);
AZ::Outcome<GemDescription, AZStd::string> ParseToGemDescription(const AZStd::string& gemFolderPath, const char* absoluteFilePath) const;
AZStd::vector<SearchPath> m_searchPaths; // Explictly ordered so that AddSearchPath() order matters
AZStd::unordered_map<AZ::Uuid, AZStd::unordered_map<GemVersion, GemDescriptionPtr> > m_gemDescs;
};
} // namespace Gems
extern "C" AZ_DLL_EXPORT Gems::IGemRegistry * CreateGemRegistry();
extern "C" AZ_DLL_EXPORT void DestroyGemRegistry(Gems::IGemRegistry* reg);
@@ -1,596 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ProjectSettings.h"
#include "GemRegistry.h"
#include <fstream>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/sort.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/JSON/error/en.h>
#include <AzFramework/API/ApplicationAPI.h>
#if defined(AZ_PLATFORM_ANDROID)
#include <errno.h>
#endif
#define MAX_ERROR_STRING_SIZE 512
namespace Gems
{
AZ_CLASS_ALLOCATOR_IMPL(ProjectSettings, AZ::SystemAllocator, 0)
ProjectSettings::ProjectSettings(GemRegistry* registry)
: m_registry(registry)
, m_initialized(false)
{
}
AZ::Outcome<void, AZStd::string> ProjectSettings::Initialize(const AZStd::string& appRootFolder, const AZStd::string& projectSubFolder)
{
AZ_Assert(!m_initialized, "ProjectSettings has been initialized already.");
// Initialize the app root folder
m_projectRootPath = appRootFolder;
// Project gems file lives in (ProjectFolder)/gems.json - which might be @assets@/gems.json or an absolute path (in tools)
m_gemsSettingsFilePath = appRootFolder;
AzFramework::StringFunc::Path::Join(m_gemsSettingsFilePath.c_str(), projectSubFolder.c_str(), m_gemsSettingsFilePath);
AzFramework::StringFunc::Path::Join(m_gemsSettingsFilePath.c_str(), GEMS_PROJECT_FILE, m_gemsSettingsFilePath);
// Project config file lives in (ProjectFolder)/project.json - which might be @assets@/project.json or an absolute path (in tools)
m_projectSettingsFilePath = appRootFolder;
AzFramework::StringFunc::Path::Join(m_projectSettingsFilePath.c_str(), projectSubFolder.c_str(), m_projectSettingsFilePath);
AzFramework::StringFunc::Path::Join(m_projectSettingsFilePath.c_str(), PROJECT_CONFIG_FILE, m_projectSettingsFilePath);
auto loadOutcome = LoadSettings();
m_initialized = loadOutcome.IsSuccess();
return loadOutcome;
}
bool ProjectSettings::EnableGem(const ProjectGemSpecifier& spec)
{
auto it = m_gems.find(spec.m_id);
if (it != m_gems.end())
{
// If the Gem is already enabled, update the version and path of the entry.
it->second.m_version = spec.m_version;
it->second.m_path = spec.m_path;
}
else
{
// create entry based on data from registry
m_gems.insert(AZStd::make_pair(spec.m_id, spec));
}
return true;
}
bool ProjectSettings::DisableGem(const GemSpecifier& spec)
{
auto it = m_gems.find(spec.m_id);
// If the Gem is enabled at the version specified, disable it.
if (it != m_gems.end())
{
if (spec.m_version != it->second.m_version)
{
return false;
}
m_gems.erase(it);
}
return true;
}
bool ProjectSettings::IsGemEnabled(const GemSpecifier& spec) const
{
auto it = m_gems.find(spec.m_id);
return it != m_gems.end()
&& it->second.m_version == spec.m_version;
}
bool ProjectSettings::IsGemEnabled(const AZ::Uuid& id, const AZStd::vector<AZStd::string>& versionConstraints) const
{
AZStd::shared_ptr<GemDependency> dependency = AZStd::make_shared<GemDependency>();
dependency->SetID(id);
auto parseOutcome = dependency->ParseVersions(versionConstraints);
if (!parseOutcome.IsSuccess())
{
AZ_Assert(false, parseOutcome.GetError().c_str());
return false;
}
return IsGemDependencyMet(dependency);
}
bool ProjectSettings::IsGemDependencyMet(const AZStd::shared_ptr<GemDependency> dep) const
{
// Gems can depend on other Gems
auto it = m_gems.find(dep->GetID());
return it != m_gems.end()
&& dep->IsFullfilledBy(it->second);
}
bool ProjectSettings::IsEngineDependencyMet(const AZStd::shared_ptr<EngineDependency> dep, const EngineVersion& againstVersion) const
{
EngineSpecifier engineSpecifier(AZ::Uuid::CreateNull(), againstVersion);
return dep->IsFullfilledBy(engineSpecifier);
}
class GemDependencyInfo : public GemDependency
{
public:
GemDependencyInfo(IGemDescriptionConstPtr gem)
: GemDependency()
, m_gem{gem}
{
}
IGemDescriptionConstPtr GetGem() const
{
return m_gem;
}
private:
IGemDescriptionConstPtr m_gem;
};
AZ::Outcome<void, AZStd::string> ProjectSettings::ValidateDependencies(const EngineVersion& engineVersion) const
{
AZStd::unordered_map<AZ::Uuid, GemDependencyInfo> globalDeps;
// Build list of required Gems
for (const auto& pair : m_gems)
{
const ProjectGemSpecifier& spec = pair.second;
auto gem = m_registry->GetGemDescription(spec);
if (!gem)
{
return AZ::Failure(AZStd::string::format("Gem with Id \"%s\" not found.", pair.first.ToString<AZStd::string>().c_str()));
}
for (auto && gemDep : gem->GetGemDependencies())
{
const AZ::Uuid id = gemDep->GetID();
GemDependency* dep;
// If the dependency isn't tracked globally, create a new one
auto depIt = globalDeps.find(id);
if (depIt == globalDeps.end())
{
globalDeps.insert(AZStd::make_pair(id, GemDependencyInfo(gem)));
dep = &globalDeps.at(id);
dep->m_id = id;
}
else
{
dep = &depIt->second;
}
// These bounds should be normalized before verification to make sure there aren't conflicting bounds
dep->m_bounds.insert(dep->m_bounds.end(), gemDep->GetBounds().begin(), gemDep->GetBounds().end());
}
}
AZStd::string errorString;
bool isTreeValid = true;
// Verify all engine dependencies are met
for(const auto& pair : m_gems)
{
const ProjectGemSpecifier& spec = pair.second;
auto gem = m_registry->GetGemDescription(spec);
if (!gem)
{
errorString += AZStd::string::format("Gem with Id \"%s\" not found.", pair.first.ToString<AZStd::string>().c_str());
isTreeValid = false;
continue;
}
// do not verify the engine version if input is default constructed
if (engineVersion == EngineVersion())
{
continue;
}
// Check the Gem's engine dependency
auto engineDepPtr = gem->GetEngineDependency();
if (engineDepPtr && !IsEngineDependencyMet(engineDepPtr, engineVersion))
{
AZStd::string errmsg = AZStd::string::format("Gem with Id \"%s\" does not meet the Lumberyard engine version requirement.\n",
pair.first.ToString<AZStd::string>().c_str());
// do not force an assertion to happen here, we are just printing the warning and letting the user
// decide on how to handle it if the engine start up fails.
errorString += errmsg;
AZ_Warning("GemRegistry", false, errmsg.c_str());
}
}
// attempt to construct a complete gem registry for unmet dependency ID to name resolution
GemRegistry completeRegistry;
const char* gemsSearchFilter = "Gems";
const char* appRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot);
if (appRoot)
{
completeRegistry.AddSearchPath({ appRoot, gemsSearchFilter }, false);
}
const char* engineRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
if (engineRoot)
{
completeRegistry.AddSearchPath({ engineRoot, gemsSearchFilter }, false);
}
completeRegistry.LoadAllGemsFromDisk();
// Verify all gems dependencies are all met
for (auto && pair : globalDeps)
{
const GemDependencyInfo& dep = pair.second;
// Find candidate in project's listed gems
auto candidateIt = m_gems.find(dep.GetID());
if (candidateIt == m_gems.end())
{
// no candidate found
char depIdStr[UUID_STR_BUF_LEN];
dep.GetID().ToString(depIdStr, UUID_STR_BUF_LEN, true, true);
char gemIdStr[UUID_STR_BUF_LEN];
dep.GetGem()->GetID().ToString(gemIdStr, UUID_STR_BUF_LEN, true, true);
// don't care about the version, just need the gem name
IGemDescriptionConstPtr depDesc = completeRegistry.GetLatestGem(dep.GetID());
if (depDesc)
{
errorString += AZStd::string::format(
"Gem \"%s\" (%s) dependency on Gem \"%s\" (%s) is unmet.\n",
dep.GetGem()->GetDisplayName().c_str(),
gemIdStr,
depDesc->GetDisplayName().c_str(),
depIdStr
);
}
else
{
errorString += AZStd::string::format(
"Gem \"%s\" (%s) dependency on unresolved Gem with ID %s is unmet.\n",
dep.GetGem()->GetDisplayName().c_str(),
gemIdStr,
depIdStr
);
}
isTreeValid = false;
}
else if (!dep.IsFullfilledBy(candidateIt->second))
{
// candidate found, but it doesn't fulfill all dependency requirements
AZStd::string boundsStr;
for (auto && bound : dep.m_bounds)
{
if (boundsStr.length() == 0)
{
boundsStr = bound.ToString();
}
else
{
boundsStr += ", " + bound.ToString();
}
}
char depIdStr[UUID_STR_BUF_LEN];
dep.GetID().ToString(depIdStr, UUID_STR_BUF_LEN, true, true);
char gemIdStr[UUID_STR_BUF_LEN];
dep.GetGem()->GetID().ToString(gemIdStr, UUID_STR_BUF_LEN, true, true);
// don't care about the version, just need the gem name
IGemDescriptionConstPtr depDesc = completeRegistry.GetLatestGem(dep.GetID());
if (depDesc)
{
errorString += AZStd::string::format(
"Gem \"%s\" (%s) dependency on Gem \"%s\" (%s) is unmet. It must fall within the following version bounds: [%s]\n",
dep.GetGem()->GetDisplayName().c_str(),
gemIdStr,
depDesc->GetDisplayName().c_str(),
depIdStr,
boundsStr.c_str()
);
}
else
{
errorString += AZStd::string::format(
"Gem \"%s\" (%s) dependency on unresolved Gem with ID %s is unmet. It must fall within the following version bounds: [%s]\n",
dep.GetGem()->GetDisplayName().c_str(),
gemIdStr,
depIdStr,
boundsStr.c_str()
);
}
isTreeValid = false;
}
}
if (!isTreeValid)
{
return AZ::Failure(errorString);
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> ProjectSettings::Save() const
{
using namespace AZ::IO;
FileIOBase* fileIo = FileIOBase::GetInstance();
HandleType projectSettingsHandle = InvalidHandle;
if (fileIo->Open(m_gemsSettingsFilePath.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, projectSettingsHandle))
{
rapidjson::Document jsonRep = GetJsonRepresentation();
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
jsonRep.Accept(writer);
AZ::u64 bytesWritten = 0;
if (!fileIo->Write(projectSettingsHandle, buffer.GetString(), buffer.GetSize(), &bytesWritten))
{
return AZ::Failure(AZStd::string::format("Failed to write Gems settings to file: %s", m_gemsSettingsFilePath.c_str()));
}
if (bytesWritten != buffer.GetSize())
{
return AZ::Failure(AZStd::string::format("Failed to write complete Gems settings to file: %s", m_gemsSettingsFilePath.c_str()));
}
fileIo->Close(projectSettingsHandle);
return AZ::Success();
}
else
{
char errorBuffer[MAX_ERROR_STRING_SIZE];
#if defined(AZ_PLATFORM_WINDOWS)
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
nullptr,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
errorBuffer,
MAX_ERROR_STRING_SIZE,
nullptr);
#else
azstrerror_s(errorBuffer, MAX_ERROR_STRING_SIZE, errno);
#endif // defined(AZ_PLATFORM_WINDOWS)
return AZ::Failure(AZStd::string::format("Failed to open %s for write: %s", m_gemsSettingsFilePath.c_str(), errorBuffer));
}
}
const AZStd::string& ProjectSettings::GetProjectName() const
{
return m_projectName;
}
const AZStd::string& ProjectSettings::GetProjectRootPath() const
{
return m_projectRootPath;
}
AZ::Outcome<void, AZStd::string> ProjectSettings::LoadSettings()
{
// an engine compatible file reader has been attached, so use that.
AZ::IO::FileIOBase* fileReader = AZ::IO::FileIOBase::GetInstance();
// Read and parse the gems.json file
{
AZ::IO::Path gemsSettingsPath(m_gemsSettingsFilePath);
auto readGemsJsonResult = AzFramework::FileFunc::ReadJsonFile(gemsSettingsPath, fileReader);
if (!readGemsJsonResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to read Json file %s: %s",
m_gemsSettingsFilePath.c_str(), readGemsJsonResult.GetError().c_str()));
}
auto parseGemsJsonResult = ParseGemsJson(readGemsJsonResult.GetValue());
if (!parseGemsJsonResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to parse Json file %s: %s",
m_gemsSettingsFilePath.c_str(), parseGemsJsonResult.GetError().c_str()));
}
}
// Read and parse the project.json file
{
AZ::IO::Path projectSettingsPath(m_projectSettingsFilePath);
auto readProjectJsonResult = AzFramework::FileFunc::ReadJsonFile(projectSettingsPath, fileReader);
if (!readProjectJsonResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to read Json file %s: %s",
m_gemsSettingsFilePath.c_str(), readProjectJsonResult.GetError().c_str()));
}
auto parseProjectJsonResult = ParseProjectJson(readProjectJsonResult.GetValue());
if (!parseProjectJsonResult.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to parse Json file %s: %s",
m_gemsSettingsFilePath.c_str(), parseProjectJsonResult.GetError().c_str()));
}
}
return AZ::Success();
}
rapidjson::Document ProjectSettings::GetJsonRepresentation() const
{
rapidjson::Document rootObj(rapidjson::kObjectType);
rootObj.AddMember<int>(GPF_TAG_LIST_FORMAT_VERSION, GEMS_PROJECT_FILE_VERSION, rootObj.GetAllocator());
// We want to write out Gems in the same order each time.
// Create vector for sorting.
AZStd::vector<const ProjectGemSpecifier*> sortedGems(m_gems.size());
auto transformFn = [](const ProjectGemSpecifierMap::value_type& pair) { return &pair.second; };
AZStd::transform(m_gems.begin(), m_gems.end(), sortedGems.begin(), transformFn);
// we'll sort based on ID.
AZStd::sort(sortedGems.begin(), sortedGems.end(), [](const ProjectGemSpecifier* a, const ProjectGemSpecifier* b) -> bool
{
return a->m_id < b->m_id;
});
auto addMember = [&rootObj](rapidjson::Value& obj, const char* key, const char* str)
{
rapidjson::Value k(rapidjson::StringRef(key), rootObj.GetAllocator());
rapidjson::Value v(rapidjson::StringRef(str), rootObj.GetAllocator());
obj.AddMember(k.Move(), v.Move(), rootObj.GetAllocator());
};
// build Gems array
rapidjson::Value gemsArray(rapidjson::kArrayType);
for (const ProjectGemSpecifier* gemSpec : sortedGems)
{
char idStr[UUID_STR_BUF_LEN];
gemSpec->m_id.ToString(idStr, UUID_STR_BUF_LEN, false, false);
AZStd::to_lower(idStr, idStr + strlen(idStr));
AZStd::string path = gemSpec->m_path;
// Replace '\' with '/'
AZStd::replace(path.begin(), path.end(), '\\', '/');
// Remove trailing slash
if (*path.rbegin() == '/')
{
path.pop_back();
}
rapidjson::Value gemObj(rapidjson::kObjectType);
addMember(gemObj, GPF_TAG_PATH, path.c_str());
addMember(gemObj, GPF_TAG_UUID, idStr);
addMember(gemObj, GPF_TAG_VERSION, gemSpec->m_version.ToString().c_str());
// write name in comment (if possible)
if (IGemDescriptionConstPtr gemDesc = m_registry->GetGemDescription(*gemSpec))
{
addMember(gemObj, GPF_TAG_COMMENT, gemDesc->GetName().c_str());
}
gemsArray.PushBack(gemObj, rootObj.GetAllocator());
}
rootObj.AddMember(GPF_TAG_GEM_ARRAY, gemsArray, rootObj.GetAllocator());
return rootObj;
}
AZ::Outcome<void, AZStd::string> ProjectSettings::ParseGemsJson(const rapidjson::Document& jsonRep)
{
// check version
if (!RAPIDJSON_IS_VALID_MEMBER(jsonRep, GPF_TAG_LIST_FORMAT_VERSION, IsInt))
{
return AZ::Failure(AZStd::string(GPF_TAG_LIST_FORMAT_VERSION " number is required."));
}
int gemListFormatVersion = jsonRep[GPF_TAG_LIST_FORMAT_VERSION].GetInt();
if (gemListFormatVersion != GEMS_PROJECT_FILE_VERSION)
{
return AZ::Failure(AZStd::string::format(
GPF_TAG_LIST_FORMAT_VERSION " is version %d, but %d is expected.",
gemListFormatVersion,
GEMS_PROJECT_FILE_VERSION));
}
// read gems
if (!RAPIDJSON_IS_VALID_MEMBER(jsonRep, GPF_TAG_GEM_ARRAY, IsArray))
{
return AZ::Failure(AZStd::string(GPF_TAG_GEM_ARRAY " list is required"));
}
const rapidjson::Value& gemList = jsonRep[GPF_TAG_GEM_ARRAY];
const auto& end = gemList.End();
for (auto it = gemList.Begin(); it != end; ++it)
{
const auto& elem = *it;
// gem id
if (!RAPIDJSON_IS_VALID_MEMBER(elem, GPF_TAG_UUID, IsString))
{
return AZ::Failure(AZStd::string(GPF_TAG_UUID " string is required for Gem."));
}
const char* idStr = elem[GPF_TAG_UUID].GetString();
AZ::Uuid id = AZ::Uuid::CreateString(idStr);
if (id.IsNull())
{
return AZ::Failure(AZStd::string(GPF_TAG_UUID " string is invalid for Gem."));
}
// gem version
if (!RAPIDJSON_IS_VALID_MEMBER(elem, GPF_TAG_VERSION, IsString))
{
return AZ::Failure(AZStd::string::format(
GPF_TAG_VERSION " string is missing for Gem with ID %s.",
idStr));
}
auto versionOutcome = GemVersion::ParseFromString(elem[GPF_TAG_VERSION].GetString());
if (!versionOutcome)
{
return AZ::Failure(AZStd::string::format(
GPF_TAG_VERSION " string is invalid for Gem with ID %s: %s",
idStr, versionOutcome.GetError().c_str()));
}
GemVersion version = versionOutcome.GetValue();
// gem path
if (!RAPIDJSON_IS_VALID_MEMBER(elem, GPF_TAG_PATH, IsString))
{
return AZ::Failure(AZStd::string(GPF_TAG_PATH " string is required for Gem"));
}
const char* path = elem[GPF_TAG_PATH].GetString();
EnableGem(ProjectGemSpecifier(id, version, path));
}
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> ProjectSettings::ParseProjectJson(const rapidjson::Document& json)
{
// For now, we only
static const char* project_name_key = "project_name";
if (!RAPIDJSON_IS_VALID_MEMBER(json, project_name_key, IsString))
{
return AZ::Failure(AZStd::string::format("Missing/Invalid key '%s' in project.json.", project_name_key));
}
m_projectName = json[project_name_key].GetString();
return AZ::Success();
}
} // namespace Gems
@@ -1,69 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/JSON/document.h>
#include "GemRegistry.h"
namespace Gems
{
class ProjectSettings
: public IProjectSettings
{
public:
AZ_CLASS_ALLOCATOR_DECL
ProjectSettings(GemRegistry* registry);
~ProjectSettings() override = default;
// IProjectSettings
AZ::Outcome<void, AZStd::string> Initialize(const AZStd::string& appRootFolder, const AZStd::string& projectSubFolder) override;
bool EnableGem(const ProjectGemSpecifier& spec) override;
bool DisableGem(const GemSpecifier& spec) override;
bool IsGemEnabled(const GemSpecifier& spec) const override;
bool IsGemEnabled(const AZ::Uuid& id, const AZStd::vector<AZStd::string>& versionConstraints) const override;
bool IsGemDependencyMet(const AZStd::shared_ptr<GemDependency> dep) const override;
bool IsEngineDependencyMet(const AZStd::shared_ptr<EngineDependency> dep, const EngineVersion& againstVersion) const override;
const ProjectGemSpecifierMap& GetGems() const override { return m_gems; }
void SetGems(const ProjectGemSpecifierMap& newGemMap) override { m_gems = newGemMap; }
AZ::Outcome<void, AZStd::string> ValidateDependencies(const EngineVersion& engineVersion) const override;
AZ::Outcome<void, AZStd::string> Save() const override;
const AZStd::string& GetProjectName() const override;
const AZStd::string& GetProjectRootPath() const override;
// ~IProjectSettings
// Internal methods
/// Loads settings from the path provided by m_settingsFilePath
AZ::Outcome<void, AZStd::string> LoadSettings();
/// Converts the ProjectGemSpecifierMap (m_gems) into it's Json representation for saving
rapidjson::Document GetJsonRepresentation() const;
/// Converts GEMS Json into the ProjectGemSpecifierMap (m_gems)
AZ::Outcome<void, AZStd::string> ParseGemsJson(const rapidjson::Document& json);
/// Reads from project.json to initialize project-specific values
AZ::Outcome<void, AZStd::string> ParseProjectJson(const rapidjson::Document& json);
private:
ProjectGemSpecifierMap m_gems;
GemRegistry* m_registry;
AZStd::string m_gemsSettingsFilePath;
AZStd::string m_projectSettingsFilePath;
AZStd::string m_projectName;
AZStd::string m_projectRootPath;
bool m_initialized;
};
} // namespace Gems
@@ -1,408 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include <GemRegistry/Dependency.h>
using namespace Gems;
using GemComp = GemDependency::Bound::Comparison;
using EngineComp = EngineDependency::Bound::Comparison;
class DependencyTest
: public ::testing::Test
{
protected:
enum class Bound
{
Upper,
Lower
};
void SetUp() override
{
m_errCopyCtorFailed = "Failed to copy Dependency instance.";
m_errParseInvalidSucceeded = "Failed to report invalid version.";
m_errParseFailed = "Failed to parse valid version string.";
m_errIncorrectBoundCount = "Improper number of bounds generated.";
m_errComparisonMismatch = "Comparison generated does not match ";
m_errSpecFulfillsFailed = "Spec that fulfills dependency was reported as invalid.";
m_errInvalidSpecFulfills = "Spec that does not fulfill dependency was reported as valid.";
m_errToStringIncorrect = "ToString result is incorrect.";
}
const char* ErrComparisonMismatch(const char* op)
{
return AZStd::string::format("Comparison generated does not match %s.", op).c_str();
}
const char* ErrInvalidBound(Bound b)
{
return AZStd::string::format("Version generated does not match %s bound.", b == Bound::Upper ? "upper" : "lower").c_str();
}
const char* m_errCopyCtorFailed;
const char* m_errParseInvalidSucceeded;
const char* m_errParseFailed;
const char* m_errIncorrectBoundCount;
const char* m_errComparisonMismatch;
const char* m_errSpecFulfillsFailed;
const char* m_errInvalidSpecFulfills;
const char* m_errToStringIncorrect;
};
TEST_F(DependencyTest, MiscTests)
{
{
GemDependency dep1;
dep1.m_id = AZ::Uuid::CreateRandom();
dep1.m_bounds.push_back(GemDependency::Bound{});
GemDependency dep2{
dep1
};
EXPECT_EQ(dep2.m_id, dep1.m_id) << m_errCopyCtorFailed;
EXPECT_EQ(dep2.m_bounds.size(), dep1.m_bounds.size()) << m_errCopyCtorFailed;
}
{
EngineDependency dep1;
dep1.m_id = AZ::Uuid::CreateRandom();
dep1.m_bounds.push_back(EngineDependency::Bound{});
EngineDependency dep2 {
dep1
};
EXPECT_EQ(dep2.m_id, dep1.m_id) << m_errCopyCtorFailed;
EXPECT_EQ(dep2.m_bounds.size(), dep1.m_bounds.size()) << m_errCopyCtorFailed;
}
}
TEST_F(DependencyTest, FailureTest)
{
{
GemDependency dep;
EXPECT_FALSE(dep.ParseVersions({ "Not a version requirement!" }).IsSuccess()) << m_errParseInvalidSucceeded;
EXPECT_FALSE(dep.ParseVersions({ "~>1" }).IsSuccess()) << m_errParseInvalidSucceeded;
EXPECT_FALSE(dep.ParseVersions({ "~>1.invalid" }).IsSuccess()) << m_errParseInvalidSucceeded;
}
{
EngineDependency dep;
EXPECT_FALSE(dep.ParseVersions({ "Not a version requirement!" }).IsSuccess()) << m_errParseInvalidSucceeded;
EXPECT_FALSE(dep.ParseVersions({ "~>1" }).IsSuccess()) << m_errParseInvalidSucceeded;
EXPECT_FALSE(dep.ParseVersions({ "~>1.invalid" }).IsSuccess()) << m_errParseInvalidSucceeded;
}
}
TEST_F(DependencyTest, TwiddleWakkaTest)
{
{
GemDependency dep;
ASSERT_TRUE(dep.ParseVersions({ "~>1.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::TwiddleWakka) << ErrComparisonMismatch("Twiddle Wakka");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(1, 0, 0)) << ErrInvalidBound(Bound::Lower);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "~>1.0.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::TwiddleWakka) << ErrComparisonMismatch("Twiddle Wakka");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(1, 0, 1)) << ErrInvalidBound(Bound::Lower);
}
{
EngineDependency dep;
ASSERT_TRUE(dep.ParseVersions({ "~>1.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::TwiddleWakka) << ErrComparisonMismatch("Twiddle Wakka");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 1, 0, 0, 0 })) << ErrInvalidBound(Bound::Lower);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "~>1.0.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::TwiddleWakka) << ErrComparisonMismatch("Twiddle Wakka");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 1, 0, 1, 0 })) << ErrInvalidBound(Bound::Lower);
}
}
TEST_F(DependencyTest, SingleVersionTest)
{
{
GemDependency dep;
ASSERT_TRUE(dep.ParseVersions({ ">=1.0.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::GreaterThan | GemComp::EqualTo) << ErrComparisonMismatch(">=");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(1, 0, 0)) << ErrInvalidBound(Bound::Lower);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">2.20.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::GreaterThan) << ErrComparisonMismatch(">");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(2, 20, 0)) << ErrInvalidBound(Bound::Lower);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "==3.4.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::EqualTo) << ErrComparisonMismatch("==");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(3, 4, 0)) << ErrInvalidBound(Bound::Lower);
}
{
EngineDependency dep;
ASSERT_TRUE(dep.ParseVersions({ ">=1.0.0.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::GreaterThan | EngineComp::EqualTo) << ErrComparisonMismatch(">=");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 1, 0, 0, 0 })) << ErrInvalidBound(Bound::Lower);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">2.20.0.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::GreaterThan) << ErrComparisonMismatch(">");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 2, 20, 0, 0 })) << ErrInvalidBound(Bound::Lower);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "==3.4.0.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::EqualTo) << ErrComparisonMismatch("==");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 3, 4, 0, 0 })) << ErrInvalidBound(Bound::Lower);
}
}
TEST_F(DependencyTest, DoubleVersionTest)
{
{
GemDependency dep;
ASSERT_TRUE(dep.ParseVersions({ ">=1.0.0", "<2.0.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::GreaterThan | GemComp::EqualTo) << ErrComparisonMismatch(">=");
EXPECT_EQ(dep.GetBounds()[1].GetComparison(), GemComp::LessThan) << ErrComparisonMismatch("<");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(1, 0, 0)) << ErrInvalidBound(Bound::Lower);
EXPECT_EQ(dep.GetBounds()[1].GetVersion(), GemVersion(2, 0, 0)) << ErrInvalidBound(Bound::Upper);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">2.20.0", "<=3" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::GreaterThan) << ErrComparisonMismatch(">");
EXPECT_EQ(dep.GetBounds()[1].GetComparison(), GemComp::LessThan | GemComp::EqualTo) << ErrComparisonMismatch("<=");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(2, 20, 0)) << ErrInvalidBound(Bound::Lower);
EXPECT_EQ(dep.GetBounds()[1].GetVersion(), GemVersion(3, 0, 0)) << ErrInvalidBound(Bound::Upper);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "<3.4.0", ">=20.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), GemComp::LessThan) << ErrComparisonMismatch("<");
EXPECT_EQ(dep.GetBounds()[1].GetComparison(), GemComp::GreaterThan | GemComp::EqualTo) << ErrComparisonMismatch(">=");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), GemVersion(3, 4, 0)) << ErrInvalidBound(Bound::Lower);
EXPECT_EQ(dep.GetBounds()[1].GetVersion(), GemVersion(20, 1, 0)) << ErrInvalidBound(Bound::Upper);
}
{
EngineDependency dep;
ASSERT_TRUE(dep.ParseVersions({ ">=1.0.0.0", "<2.0.0.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::GreaterThan | EngineComp::EqualTo) << ErrComparisonMismatch(">=");
EXPECT_EQ(dep.GetBounds()[1].GetComparison(), EngineComp::LessThan) << ErrComparisonMismatch("<");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 1, 0, 0, 0 })) << ErrInvalidBound(Bound::Lower);
EXPECT_EQ(dep.GetBounds()[1].GetVersion(), EngineVersion({ 2, 0, 0, 0 })) << ErrInvalidBound(Bound::Upper);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">2.20.0.0", "<=3" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::GreaterThan) << ErrComparisonMismatch(">");
EXPECT_EQ(dep.GetBounds()[1].GetComparison(), EngineComp::LessThan | EngineComp::EqualTo) << ErrComparisonMismatch("<=");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 2, 20, 0, 0 })) << ErrInvalidBound(Bound::Lower);
EXPECT_EQ(dep.GetBounds()[1].GetVersion(), EngineVersion({ 3, 0, 0, 0 })) << ErrInvalidBound(Bound::Upper);
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "<3.4.0.0", ">=20.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_EQ(dep.GetBounds()[0].GetComparison(), EngineComp::LessThan) << ErrComparisonMismatch("<");
EXPECT_EQ(dep.GetBounds()[1].GetComparison(), EngineComp::GreaterThan | EngineComp::EqualTo) << ErrComparisonMismatch(">=");
EXPECT_EQ(dep.GetBounds()[0].GetVersion(), EngineVersion({ 3, 4, 0, 0 })) << ErrInvalidBound(Bound::Lower);
EXPECT_EQ(dep.GetBounds()[1].GetVersion(), EngineVersion({ 20, 1, 0, 0 })) << ErrInvalidBound(Bound::Upper);
}
}
TEST_F(DependencyTest, FullfillmentTest)
{
{
AZ::Uuid gemId = AZ::Uuid::CreateRandom();
GemDependency dep;
dep.SetID(gemId);
GemSpecifier spec1 = { gemId, GemVersion(1, 0, 0) };
GemSpecifier specR = { AZ::Uuid::CreateRandom(), GemVersion(0, 0, 0) };
ASSERT_TRUE(dep.ParseVersions(AZStd::vector<AZStd::string>()).IsSuccess())<< m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 0) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy(spec1)) << m_errSpecFulfillsFailed;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">=1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy(spec1)) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy(specR)) << m_errInvalidSpecFulfills;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">0", "<1.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy(spec1)) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy(specR)) << m_errInvalidSpecFulfills;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">1", "<2", "==1.2" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 3) << m_errIncorrectBoundCount;
EXPECT_FALSE(dep.IsFullfilledBy(spec1)) << m_errInvalidSpecFulfills;
EXPECT_FALSE(dep.IsFullfilledBy(specR)) << m_errInvalidSpecFulfills;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "~>1.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy({ gemId, GemVersion(1, 0, 0) })) << m_errSpecFulfillsFailed;
EXPECT_TRUE(dep.IsFullfilledBy({ gemId, GemVersion(1, 1, 0) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ gemId, GemVersion(2, 0, 0) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ gemId, GemVersion(0, 0, 1) })) << m_errSpecFulfillsFailed;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "~>1.0.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy({ gemId, GemVersion(1, 0, 1) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ gemId, GemVersion(1, 1, 0) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ gemId, GemVersion(1, 0, 0) })) << m_errSpecFulfillsFailed;
}
{
AZ::Uuid engineId = AZ::Uuid::CreateRandom();
EngineDependency dep;
dep.SetID(engineId);
EngineSpecifier spec1 = { engineId, EngineVersion({ 1, 0, 0, 0 }) };
EngineSpecifier specR = { AZ::Uuid::CreateRandom(), EngineVersion({ 0, 0, 0, 0 }) };
ASSERT_TRUE(dep.ParseVersions(AZStd::vector<AZStd::string>()).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 0) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy(spec1)) << m_errSpecFulfillsFailed;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">=1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy(spec1)) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy(specR)) << m_errInvalidSpecFulfills;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">0", "<1.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 2) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy(spec1)) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy(specR)) << m_errInvalidSpecFulfills;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ ">1", "<2", "==1.2" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 3) << m_errIncorrectBoundCount;
EXPECT_FALSE(dep.IsFullfilledBy(spec1)) << m_errInvalidSpecFulfills;
EXPECT_FALSE(dep.IsFullfilledBy(specR)) << m_errInvalidSpecFulfills;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "~>1.0" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy({ engineId, EngineVersion({ 1, 0, 0, 0 }) })) << m_errSpecFulfillsFailed;
EXPECT_TRUE(dep.IsFullfilledBy({ engineId, EngineVersion({ 1, 1, 0, 0 }) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ engineId, EngineVersion({ 2, 0, 0, 0 }) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ engineId, EngineVersion({ 0, 0, 1, 0 }) })) << m_errSpecFulfillsFailed;
dep.m_bounds.clear();
ASSERT_TRUE(dep.ParseVersions({ "~>1.0.1" }).IsSuccess()) << m_errParseFailed;
ASSERT_EQ(dep.GetBounds().size(), 1) << m_errIncorrectBoundCount;
EXPECT_TRUE(dep.IsFullfilledBy({ engineId, EngineVersion({ 1, 0, 1, 0 }) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ engineId, EngineVersion({ 1, 1, 0, 0 }) })) << m_errSpecFulfillsFailed;
EXPECT_FALSE(dep.IsFullfilledBy({ engineId, EngineVersion({ 1, 0, 0, 0 }) })) << m_errSpecFulfillsFailed;
}
}
TEST_F(DependencyTest, BoundToStringTest)
{
{
GemVersion v1 {
1, 0, 0
};
GemDependency::Bound bnd;
bnd.SetVersion(v1);
bnd.SetComparison(GemComp::EqualTo);
EXPECT_STREQ(bnd.ToString().c_str(), "==1.0.0") << m_errToStringIncorrect;
bnd.SetComparison(GemComp::GreaterThan);
EXPECT_STREQ(bnd.ToString().c_str(), ">1.0.0") << m_errToStringIncorrect;
bnd.SetComparison(GemComp::LessThan);
EXPECT_STREQ(bnd.ToString().c_str(), "<1.0.0") << m_errToStringIncorrect;
bnd.SetComparison(GemComp::GreaterThan | GemComp::EqualTo);
EXPECT_STREQ(bnd.ToString().c_str(), ">=1.0.0") << m_errToStringIncorrect;
bnd.SetComparison(GemComp::LessThan | GemComp::EqualTo);
EXPECT_STREQ(bnd.ToString().c_str(), "<=1.0.0") << m_errToStringIncorrect;
}
{
EngineVersion v1{
1, 0, 0, 0
};
EngineDependency::Bound bnd;
bnd.SetVersion(v1);
bnd.SetComparison(EngineComp::EqualTo);
EXPECT_STREQ(bnd.ToString().c_str(), "==1.0.0.0") << m_errToStringIncorrect;
bnd.SetComparison(EngineComp::GreaterThan);
EXPECT_STREQ(bnd.ToString().c_str(), ">1.0.0.0") << m_errToStringIncorrect;
bnd.SetComparison(EngineComp::LessThan);
EXPECT_STREQ(bnd.ToString().c_str(), "<1.0.0.0") << m_errToStringIncorrect;
bnd.SetComparison(EngineComp::GreaterThan | EngineComp::EqualTo);
EXPECT_STREQ(bnd.ToString().c_str(), ">=1.0.0.0") << m_errToStringIncorrect;
bnd.SetComparison(EngineComp::LessThan | EngineComp::EqualTo);
EXPECT_STREQ(bnd.ToString().c_str(), "<=1.0.0.0") << m_errToStringIncorrect;
}
}
@@ -1,378 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include "GemDescription.h"
using Gems::GemDescription;
using Gems::ModuleDefinition;
class DescriptionTest
: public ::testing::Test
{
protected:
void SetUp() override
{
}
void TearDown() override
{
}
/// Helper to parse json text to a GemDescription result
static AZ::Outcome<GemDescription, AZStd::string> CreateFromString(const char* text)
{
rapidjson::Document document;
document.Parse(text);
EXPECT_FALSE(document.HasParseError());
return GemDescription::CreateFromJson(document, "", "");
}
/// Helper to parse json text (that should compile) to a GemDescription
static GemDescription ParseString(const char* text)
{
auto result = CreateFromString(text);
EXPECT_TRUE(result.IsSuccess());
return AZStd::move(result.GetValue());
}
};
// Helper asserts
#define EXPECT_MODULE_COUNT(desc, expectedCount) EXPECT_EQ(expectedCount, desc.GetModules().size());
#define EXPECT_MODULE_TYPE_COUNT(desc, type, expectedCount) EXPECT_EQ(expectedCount, desc.GetModulesOfType(ModuleDefinition::Type::type).size())
////////////////////////////////////////////////////////////////////////
// Success tests
////////////////////////////////////////////////////////////////////////
// Test for parsing V3 Gem Descriptions
TEST_F(DescriptionTest, ParseJson_V3_GameModule)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 3,
"Uuid": "ff06785f7145416b9d46fde39098cb0c",
"Name": "LmbrCentral",
"Version": "0.1.0",
"LinkType": "Dynamic",
"Summary": "Required LmbrCentral Engine Gem.",
"Tags": ["Untagged"],
"IconPath": "preview.png",
"IsRequired": true
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 1);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 0);
}
// Test for parsing V3 Gem Descriptions with an editor module
TEST_F(DescriptionTest, ParseJson_V3_EditorModule)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 3,
"Uuid": "ff06785f7145416b9d46fde39098cb0c",
"Name": "LmbrCentral",
"Version": "0.1.0",
"LinkType": "Dynamic",
"Summary": "Required LmbrCentral Engine Gem.",
"Tags": ["Untagged"],
"IconPath": "preview.png",
"EditorModule": true,
"IsRequired": true
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 2);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 0);
}
// Test for parsing V4 Gem Descriptions with a game module
TEST_F(DescriptionTest, ParseJson_V4_GameModule)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Type": "GameModule"
}
]
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 1);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 0);
}
// Test for parsing V4 Gem Descriptions with an editor module
TEST_F(DescriptionTest, ParseJson_V4_EditorModule)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Name": "Editor",
"Type": "EditorModule"
}
]
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 1);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 0);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 0);
}
// Test for parsing V4 Gem Descriptions with a game and an editor module (that extends the editor module
TEST_F(DescriptionTest, ParseJson_V4_EditorModuleExtends)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Type": "GameModule"
},
{
"Name": "Editor",
"Type": "EditorModule",
"Extends": "GameModule"
}
]
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 2);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 1);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 0);
}
// Test for parsing V4 Gem Descriptions with a static lib
TEST_F(DescriptionTest, ParseJson_V4_StaticLib)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Name": "CameraHelper",
"Type": "StaticLib"
}
]
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 1);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 0);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 0);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 1);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 0);
}
// Test for parsing V4 Gem Descriptions with a static lib
TEST_F(DescriptionTest, ParseJson_V4_Standalone)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Name": "CameraHelper",
"Type": "Standalone"
}
]
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 1);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 0);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 0);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 1);
}
// Test for parsing V4 Gem Descriptions with a builder module
TEST_F(DescriptionTest, ParseJson_V4_BuilderModule)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Name": "CameraBuilder",
"Type": "Builder"
}
]
}
)JSON";
GemDescription desc = ParseString(description);
EXPECT_MODULE_COUNT(desc, 1);
EXPECT_MODULE_TYPE_COUNT(desc, GameModule, 0);
EXPECT_MODULE_TYPE_COUNT(desc, EditorModule, 0);
EXPECT_MODULE_TYPE_COUNT(desc, StaticLib, 0);
EXPECT_MODULE_TYPE_COUNT(desc, Builder, 1);
EXPECT_MODULE_TYPE_COUNT(desc, Standalone, 0);
}
////////////////////////////////////////////////////////////////////////
// Failure tests
////////////////////////////////////////////////////////////////////////
// Test for parsing V4 Gem Descriptions with an invalid extends
TEST_F(DescriptionTest, ParseJson_V4_ExtendsNonExistantModule)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Type": "GameModule"
},
{
"Name": "Editor",
"Type": "EditorModule",
"Extends": "ModuleThatDoesntExist"
}
]
}
)JSON";
EXPECT_FALSE(CreateFromString(description).IsSuccess());
}
// Test for parsing V4 Gem Descriptions with an invalid extends
TEST_F(DescriptionTest, ParseJson_V4_ExtendsStaticLib)
{
static const char* description = R"JSON(
{
"GemFormatVersion": 4,
"Uuid": "f910686b6725452fbfc4671f95f733c6",
"Name": "Camera",
"Version": "0.1.0",
"DisplayName": "Camera",
"Tags": ["Camera"],
"Summary": "The Camera Gem includes a basic camera component that defines a frustum for runtime rendering.",
"IconPath": "preview.png",
"Modules": [
{
"Name": "CameraHelper",
"Type": "StaticLib"
},
{
"Name": "Editor",
"Type": "EditorModule",
"Extends": "CameraHelper"
}
]
}
)JSON";
EXPECT_FALSE(CreateFromString(description).IsSuccess());
}
@@ -1,247 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include "ProjectSettings.h"
#include "GemDescription.h"
using Gems::ProjectSettings;
using Gems::ProjectGemSpecifier;
using AzFramework::Specifier;
using Gems::GemVersion;
class ProjectSettingsTest
: public ::testing::Test
{
protected:
void SetUp() override
{
m_registry = aznew Gems::GemRegistry();
m_errCreationFailed = "Failed to create a ProjectSettings instance.";
m_errNonEmptyEnabledGems = "EnabledGems should start empty.";
m_errEnableGemFailed = "Failed to enable valid Gem Spec.";
m_errIsGemEnabledFailed = "Failed to accurately determine if a gem was enabled or not";
m_errDescriptionParseFailed = "Failed to parse valid Description: ";
m_errInvalidDescriptionParseSucceeded = "Parsing of invalid Description succeeded.";
}
void TearDown() override
{
delete m_registry;
}
enum GenerateJsonFlags : AZ::u8
{
GJF_IncludeID = 1 << 0,
GJF_IncludeVersion = 1 << 1,
GJF_IncludePath = 1 << 2,
GJF_All = GJF_IncludeID | GJF_IncludeVersion | GJF_IncludePath,
};
rapidjson::Document ParseFromString(AZStd::string document)
{
rapidjson::Document root(rapidjson::kObjectType);
root.Parse(document.c_str());
return root;
}
void GenerateJson(rapidjson::Document& rootObj, AZ::Uuid id, GemVersion version, const AZStd::string& path, AZ::u8 flags)
{
rootObj.SetObject();
rootObj.AddMember(GPF_TAG_LIST_FORMAT_VERSION, GEMS_PROJECT_FILE_VERSION, rootObj.GetAllocator());
rapidjson::Value gemsArray(rapidjson::kArrayType);
if (flags != 0)
{
// build a gem
rapidjson::Value gemObj(rapidjson::kObjectType);
if (flags & GJF_IncludeID)
{
char idstr[UUID_STR_BUF_LEN];
id.ToString(idstr, UUID_STR_BUF_LEN, false, false);
rapidjson::Value v(idstr, rootObj.GetAllocator());
gemObj.AddMember(GPF_TAG_UUID, v.Move(), rootObj.GetAllocator());
}
if (flags & GJF_IncludeVersion)
{
rapidjson::Value v(version.ToString().c_str(), rootObj.GetAllocator());
gemObj.AddMember(GPF_TAG_VERSION, v.Move(), rootObj.GetAllocator());
}
if (flags & GJF_IncludePath)
{
rapidjson::Value v(path.c_str(), rootObj.GetAllocator());
gemObj.AddMember(GPF_TAG_PATH, v.Move(), rootObj.GetAllocator());
}
gemsArray.PushBack(gemObj.Move(), rootObj.GetAllocator()); // copy into array
}
rootObj.AddMember(GPF_TAG_GEM_ARRAY, gemsArray.Move(), rootObj.GetAllocator()); // copy into root object
}
Gems::GemRegistry* m_registry;
const char* m_errCreationFailed;
const char* m_errNonEmptyEnabledGems;
const char* m_errEnableGemFailed;
const char* m_errIsGemEnabledFailed;
const char* m_errDescriptionParseFailed;
const char* m_errInvalidDescriptionParseSucceeded;
};
TEST_F(ProjectSettingsTest, CreateAndDestroyTest)
{
Gems::IProjectSettings* ps = m_registry->CreateProjectSettings();
EXPECT_NE(ps, nullptr) << m_errCreationFailed;
m_registry->DestroyProjectSettings(ps);
}
TEST_F(ProjectSettingsTest, EnableDisableTest)
{
ProjectSettings ps {
m_registry
};
AZ::Uuid id = AZ::Uuid::CreateRandom();
GemVersion v1 {
1, 0, 0
};
AZStd::string path {
"some\\path"
};
ProjectGemSpecifier s0 {
id, v1, path
};
EXPECT_TRUE(ps.EnableGem(s0)) << m_errEnableGemFailed;
EXPECT_TRUE(ps.IsGemEnabled(s0)) << m_errEnableGemFailed;
EXPECT_TRUE(ps.DisableGem(s0)) << m_errEnableGemFailed;
EXPECT_FALSE(ps.IsGemEnabled(s0)) << m_errEnableGemFailed;
}
TEST_F(ProjectSettingsTest, IsEnabledTest)
{
ProjectSettings ps {
m_registry
};
ProjectGemSpecifier enabledGemSpecifier {
AZ::Uuid::CreateRandom(), GemVersion {
1, 0, 0
}, "some\\path"
};
EXPECT_TRUE(ps.EnableGem(enabledGemSpecifier)) << m_errEnableGemFailed;
// Compare 1.0.0 to 1.0.0
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "<1.0.0" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "<=1.0.0" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "==1.0.0" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">=1.0.0" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">1.0.0" })) << m_errIsGemEnabledFailed;
// Compare 1.0.0 to 1.0.1
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "<1.0.1" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "<=1.0.1" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "==1.0.1" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">=1.0.1" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">1.0.1" })) << m_errIsGemEnabledFailed;
// Compare 1.0.0 to 0.1.1
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "<0.1.1" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "<=0.1.1" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "==0.1.1" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">=0.1.1" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">0.1.1" })) << m_errIsGemEnabledFailed;
// Test ranges around the enabled gem
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">=0.1.0", "<=1.1.0" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "<=1.1.0", ">=0.1.0" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "~>1.0.0" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "~>1.0" })) << m_errIsGemEnabledFailed;
// Test ranges at or above the enabled gem
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">=1.0.0", "<=1.1.0" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">1.0.0", "<=1.1.0" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "~>1.1.0" })) << m_errIsGemEnabledFailed;
//// Test ranges at or below the enabled gem
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">=0.1.0", "<1.0.0" })) << m_errIsGemEnabledFailed;
EXPECT_TRUE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { ">=0.1.0", "<=1.0.0" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "~>0.1.0" })) << m_errIsGemEnabledFailed;
EXPECT_FALSE(ps.IsGemEnabled(enabledGemSpecifier.m_id, { "~>0.1" })) << m_errIsGemEnabledFailed;
}
TEST_F(ProjectSettingsTest, ParseTest)
{
ProjectSettings ps {
m_registry
};
AZ::Uuid id = AZ::Uuid::CreateRandom();
GemVersion v1 {
1, 0, 0
};
AZStd::string path("Some\\Path");
rapidjson::Document json(rapidjson::kObjectType);
GenerateJson(json, id, v1, path, GJF_All);
AZ::Outcome<void, AZStd::string> outcome = ps.ParseGemsJson(json);
ASSERT_TRUE(outcome.IsSuccess()) << m_errDescriptionParseFailed << outcome.GetError().c_str();
auto gemMap = ps.GetGems();
EXPECT_EQ(gemMap.size(), 1) << m_errDescriptionParseFailed << outcome.GetError().c_str();
EXPECT_EQ(gemMap.begin()->second.m_id, id) << m_errDescriptionParseFailed << outcome.GetError().c_str();
EXPECT_EQ(gemMap.begin()->second.m_version, v1) << m_errDescriptionParseFailed << outcome.GetError().c_str();
GenerateJson(json, id, v1, path, GJF_IncludeVersion | GJF_IncludePath);
EXPECT_FALSE(ps.ParseGemsJson(json)) << m_errInvalidDescriptionParseSucceeded;
GenerateJson(json, id, v1, path, GJF_IncludeID | GJF_IncludePath);
EXPECT_FALSE(ps.ParseGemsJson(json)) << m_errInvalidDescriptionParseSucceeded;
GenerateJson(json, id, v1, path, GJF_IncludeVersion | GJF_IncludeID);
EXPECT_FALSE(ps.ParseGemsJson(json)) << m_errInvalidDescriptionParseSucceeded;
GenerateJson(json, id, v1, path, GJF_IncludeID | GJF_IncludeVersion | GJF_IncludePath);
EXPECT_TRUE(ps.ParseGemsJson(json)) << m_errDescriptionParseFailed;
}
TEST_F(ProjectSettingsTest, SaveTest)
{
ProjectSettings ps {
m_registry
};
AZ::Uuid id = AZ::Uuid::CreateRandom();
char idStr[UUID_STR_BUF_LEN];
id.ToString(idStr, UUID_STR_BUF_LEN, false, false);
GemVersion v1 {
1, 0, 0
};
AZStd::string path("Some\\Path");
rapidjson::Document json(rapidjson::kObjectType);
GenerateJson(json, id, v1, path, GJF_All);
ASSERT_TRUE(ps.ParseGemsJson(json)) << m_errDescriptionParseFailed;
EXPECT_TRUE(json.HasMember(GPF_TAG_LIST_FORMAT_VERSION)) << m_errDescriptionParseFailed;
EXPECT_TRUE(json[GPF_TAG_LIST_FORMAT_VERSION].IsInt()) << m_errDescriptionParseFailed;
EXPECT_EQ(json[GPF_TAG_LIST_FORMAT_VERSION].GetInt(), GEMS_PROJECT_FILE_VERSION) << m_errDescriptionParseFailed;
ASSERT_TRUE(json.HasMember(GPF_TAG_GEM_ARRAY)) << m_errDescriptionParseFailed;
ASSERT_TRUE(json[GPF_TAG_GEM_ARRAY].IsArray()) << m_errDescriptionParseFailed;
ASSERT_EQ(json[GPF_TAG_GEM_ARRAY].Size(), 1) << m_errDescriptionParseFailed;
EXPECT_STRCASEEQ(json[GPF_TAG_GEM_ARRAY][0][GPF_TAG_UUID].GetString(), idStr) << m_errDescriptionParseFailed;
EXPECT_STRCASEEQ(json[GPF_TAG_GEM_ARRAY][0][GPF_TAG_VERSION].GetString(), v1.ToString().c_str()) << m_errDescriptionParseFailed;
EXPECT_STRCASEEQ(json[GPF_TAG_GEM_ARRAY][0][GPF_TAG_PATH].GetString(), path.c_str()) << m_errDescriptionParseFailed;
ps.DisableGem({ id, v1 });
json = ps.GetJsonRepresentation();
ASSERT_EQ(json[GPF_TAG_GEM_ARRAY].Size(), 0);
}
@@ -1,36 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include "GemRegistry.h"
using Gems::GemRegistry;
class RegistryTest
: public ::testing::Test
{
protected:
void SetUp() override
{
m_errCreationFailed = "Failed to create a GemRegistry instance.";
m_errInvalidErrorMessage = "Error message is not what it was set to.";
}
const char* m_errCreationFailed;
const char* m_errInvalidErrorMessage;
};
TEST_F(RegistryTest, CreateAndDestroyTest)
{
Gems::IGemRegistry* gr = CreateGemRegistry();
ASSERT_NE(gr, nullptr) << m_errCreationFailed;
DestroyGemRegistry(gr);
}
@@ -1,465 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/std/containers/unordered_map.h>
#include <AzTest/AzTest.h>
#include <GemRegistry/Version.h>
using Gems::GemVersion;
using Gems::EngineVersion;
class VersionTest
: public ::testing::Test
{
protected:
void SetUp() override
{
m_errParseFailed = "Failed to parse valid version string.";
m_errParseInvalidSucceeded = "Parsing invalid version string succeeded.";
m_errParseInvalid = "ParseFromString resulted in incorrect version.";
m_errCompareIncorrect = "Result of Compare is incorrect.";
m_errToStringIncorrect = "ToString result is incorrect.";
m_errHasherIncorrect = "Did not get back the same value from hash map.";
}
const char* m_errParseFailed;
const char* m_errParseInvalidSucceeded;
const char* m_errParseInvalid;
const char* m_errCompareIncorrect;
const char* m_errToStringIncorrect;
const char* m_errHasherIncorrect;
};
TEST_F(VersionTest, InitializerListConstructor_ValidValues_ReturnSameValues)
{
GemVersion v0 = { 1, 2, 3 };
ASSERT_EQ(v0.m_parts[0], 1) << m_errParseInvalid;
ASSERT_EQ(v0.m_parts[1], 2) << m_errParseInvalid;
ASSERT_EQ(v0.m_parts[2], 3) << m_errParseInvalid;
EngineVersion v1 = { 1, 2, 3, 4 };
ASSERT_EQ(v1.m_parts[0], 1) << m_errParseInvalid;
ASSERT_EQ(v1.m_parts[1], 2) << m_errParseInvalid;
ASSERT_EQ(v1.m_parts[2], 3) << m_errParseInvalid;
ASSERT_EQ(v1.m_parts[3], 4) << m_errParseInvalid;
}
TEST_F(VersionTest, ParseFromString_ValidString_ReturnSuccessOutcomeWithCorrectValues)
{
auto version0Outcome = GemVersion::ParseFromString("1.2.3");
ASSERT_TRUE(version0Outcome.IsSuccess()) << m_errParseFailed;
GemVersion v0 = version0Outcome.GetValue();
ASSERT_EQ(v0.GetMajor(), 1) << m_errParseInvalid;
ASSERT_EQ(v0.GetMinor(), 2) << m_errParseInvalid;
ASSERT_EQ(v0.GetPatch(), 3) << m_errParseInvalid;
auto version1Outcome = EngineVersion::ParseFromString("1.2.3.4");
ASSERT_TRUE(version1Outcome.IsSuccess()) << m_errParseFailed;
EngineVersion v1 = version1Outcome.GetValue();
ASSERT_EQ(v1.m_parts[0], 1) << m_errParseInvalid;
ASSERT_EQ(v1.m_parts[1], 2) << m_errParseInvalid;
ASSERT_EQ(v1.m_parts[2], 3) << m_errParseInvalid;
ASSERT_EQ(v1.m_parts[3], 4) << m_errParseInvalid;
}
TEST_F(VersionTest, ParseFromString_EmptyString_ReturnFailureOutcome)
{
ASSERT_FALSE(GemVersion::ParseFromString("").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("").IsSuccess()) << m_errParseInvalidSucceeded;
}
TEST_F(VersionTest, ParseFromString_InvalidPartSize_ReturnFailureOutcome)
{
ASSERT_FALSE(GemVersion::ParseFromString("1.2").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(GemVersion::ParseFromString("1.2.3.4").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("1.4.2.1.1").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("1.2.3").IsSuccess()) << m_errParseInvalidSucceeded;
}
TEST_F(VersionTest, ParseFromString_InvalidCharactersString_ReturnFailureOutcome)
{
ASSERT_FALSE(GemVersion::ParseFromString("NotAVersion").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(GemVersion::ParseFromString("NotAVersion.2.3").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(GemVersion::ParseFromString("1.NotAVersion.3").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(GemVersion::ParseFromString("1.2.NotAVersion").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("NotBVersion").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("NotBVersion.2.3").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("1.NotBVersion.3").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("1.2.NotBVersion").IsSuccess()) << m_errParseInvalidSucceeded;
}
TEST_F(VersionTest, ParseFromString_InvalidSeparator_ReturnFailureOutcome)
{
ASSERT_FALSE(GemVersion::ParseFromString("1,2,3").IsSuccess()) << m_errParseInvalidSucceeded;
ASSERT_FALSE(EngineVersion::ParseFromString("1,2,3,4").IsSuccess()) << m_errParseInvalidSucceeded;
}
TEST_F(VersionTest, Compare_DifferentMajor_ReturnLesserThanZero)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 2, 0, 0 };
ASSERT_LT(GemVersion::Compare(v1, v2), 0) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 2, 0, 0, 0 };
ASSERT_LT(EngineVersion::Compare(v3, v4), 0) << m_errCompareIncorrect;
}
TEST_F(VersionTest, Compare_DifferentMinor_ReturnLesserThanZero)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 1, 1, 0 };
ASSERT_LT(GemVersion::Compare(v1, v2), 0) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 1, 1, 0, 0 };
ASSERT_LT(EngineVersion::Compare(v3, v4), 0) << m_errCompareIncorrect;
}
TEST_F(VersionTest, Compare_DifferentMajorAndMinor_ReturnGreaterThanZero)
{
GemVersion v1 = { 2, 0, 0 };
GemVersion v2 = { 1, 1, 0 };
ASSERT_GT(GemVersion::Compare(v1, v2), 0) << m_errCompareIncorrect;
EngineVersion v3 = { 2, 0, 0, 0 };
EngineVersion v4 = { 1, 1, 0, 0 };
ASSERT_GT(EngineVersion::Compare(v3, v4), 0) << m_errCompareIncorrect;
}
TEST_F(VersionTest, Compare_SameValue_ReturnZero)
{
GemVersion v1 = { 1, 1, 0 };
GemVersion v2 = { 1, 1, 0 };
ASSERT_EQ(GemVersion::Compare(v1, v2), 0) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 1, 0, 0 };
EngineVersion v4 = { 1, 1, 0, 0 };
ASSERT_EQ(EngineVersion::Compare(v3, v4), 0) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareLesserThan_DifferentMajor_ReturnTrue)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 2, 0, 0 };
ASSERT_TRUE(v1 < v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 2, 0, 0, 0 };
ASSERT_TRUE(v3 < v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareLesserThan_SameMajor_ReturnTrue)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 1, 1, 0 };
ASSERT_TRUE(v1 < v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 1, 1, 0, 0 };
ASSERT_TRUE(v3 < v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareLesserEqualsTo_DifferentMajor_ReturnTrue)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 2, 0, 0 };
ASSERT_TRUE(v1 <= v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 2, 0, 0, 0 };
ASSERT_TRUE(v3 <= v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareLesserEqualsTo_SameMajorSmallerMinor_ReturnTrue)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 1, 1, 0 };
ASSERT_TRUE(v1 <= v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 1, 1, 0, 0 };
ASSERT_TRUE(v3 <= v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareLesserEqualsTo_Samevalues_ReturnTrue)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 1, 0, 0 };
ASSERT_TRUE(v1 <= v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 1, 0, 0, 0 };
ASSERT_TRUE(v3 <= v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareGreaterThan_DifferentMajor_ReturnTrue)
{
GemVersion v1 = { 2, 0, 0 };
GemVersion v2 = { 1, 0, 0 };
ASSERT_TRUE(v1 > v2) << m_errCompareIncorrect;
EngineVersion v3 = { 2, 0, 0, 0 };
EngineVersion v4 = { 1, 0, 0, 0 };
ASSERT_TRUE(v3 > v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareGreaterThan_SameMajor_ReturnTrue)
{
GemVersion v1 = { 1, 1, 0 };
GemVersion v2 = { 1, 0, 0 };
ASSERT_TRUE(v1 > v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 1, 0, 0 };
EngineVersion v4 = { 1, 0, 0, 0 };
ASSERT_TRUE(v3 > v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareGreaterEqualsTo_DifferentMajor_ReturnTrue)
{
GemVersion v1 = { 2, 0, 0 };
GemVersion v2 = { 1, 0, 0 };
ASSERT_TRUE(v1 >= v2) << m_errCompareIncorrect;
EngineVersion v3 = { 2, 0, 0, 0 };
EngineVersion v4 = { 1, 0, 0, 0 };
ASSERT_TRUE(v3 >= v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareGreaterEqualsTo_SameMajorSmallerMinor_ReturnTrue)
{
GemVersion v1 = { 1, 1, 0 };
GemVersion v2 = { 1, 0, 0 };
ASSERT_TRUE(v1 >= v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 1, 0, 0 };
EngineVersion v4 = { 1, 0, 0, 0 };
ASSERT_TRUE(v3 >= v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareGreaterEqualsTo_Samevalues_ReturnTrue)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 1, 0, 0 };
ASSERT_TRUE(v1 >= v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 1, 0, 0, 0 };
ASSERT_TRUE(v3 >= v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareEqualsTo_SameValue_ReturnTrue)
{
GemVersion v1 = { 1, 1, 0 };
GemVersion v2 = { 1, 1, 0 };
ASSERT_TRUE(v1 == v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 1, 0, 0 };
EngineVersion v4 = { 1, 1, 0, 0 };
ASSERT_TRUE(v3 == v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareEqualsTo_DifferentValue_ReturnFalse)
{
GemVersion v1 = { 1, 1, 0 };
GemVersion v2 = { 0, 1, 0 };
ASSERT_FALSE(v1 == v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 1, 0, 0 };
EngineVersion v4 = { 0, 1, 0, 0 };
ASSERT_FALSE(v3 == v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareNotEqualsTo_DifferentValues_ReturnTrue)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 2, 0, 0 };
ASSERT_TRUE(v1 != v2) << m_errCompareIncorrect;
EngineVersion v3 = { 1, 0, 0, 0 };
EngineVersion v4 = { 2, 0, 0, 0 };
ASSERT_TRUE(v3 != v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, CompareNotEqualsTo_SameValues_ReturnFalse)
{
GemVersion v1 = { 2, 0, 1 };
GemVersion v2 = { 2, 0, 1 };
ASSERT_FALSE(v1 != v2) << m_errCompareIncorrect;
EngineVersion v3 = { 2, 0, 1, 0 };
EngineVersion v4 = { 2, 0, 1, 0 };
ASSERT_FALSE(v3 != v4) << m_errCompareIncorrect;
}
TEST_F(VersionTest, ToString_VariousValues_ReturnsCorrectStringOutput)
{
GemVersion v1 = { 1, 0, 0 };
GemVersion v2 = { 2, 0, 0 };
GemVersion v3 = { 1, 1, 0 };
GemVersion v4 = { 1, 1, 0 };
ASSERT_STREQ(v1.ToString().c_str(), "1.0.0") << m_errToStringIncorrect;
ASSERT_STREQ(v2.ToString().c_str(), "2.0.0") << m_errToStringIncorrect;
ASSERT_STREQ(v3.ToString().c_str(), "1.1.0") << m_errToStringIncorrect;
ASSERT_STREQ(v4.ToString().c_str(), "1.1.0") << m_errToStringIncorrect;
EngineVersion v5 = { 1, 0, 0, 0 };
EngineVersion v6 = { 2, 0, 0, 0 };
EngineVersion v7 = { 1, 1, 0, 0 };
EngineVersion v8 = { 1, 1, 0, 0 };
ASSERT_STREQ(v5.ToString().c_str(), "1.0.0.0") << m_errToStringIncorrect;
ASSERT_STREQ(v6.ToString().c_str(), "2.0.0.0") << m_errToStringIncorrect;
ASSERT_STREQ(v7.ToString().c_str(), "1.1.0.0") << m_errToStringIncorrect;
ASSERT_STREQ(v8.ToString().c_str(), "1.1.0.0") << m_errToStringIncorrect;
}
TEST_F(VersionTest, ToString_ToStringFromParseStringValue_ReturnSameStringFromInput)
{
const char* version0String = "1.2.3";
auto version0Outcome = GemVersion::ParseFromString(version0String);
GemVersion v0 = version0Outcome.GetValue();
ASSERT_TRUE(version0Outcome.IsSuccess()) << m_errParseFailed;
ASSERT_STREQ(v0.ToString().c_str(), version0String) << m_errToStringIncorrect;
const char* version1String = "1.2.3.4";
auto version1Outcome = EngineVersion::ParseFromString(version1String);
EngineVersion v1 = version1Outcome.GetValue();
ASSERT_TRUE(version1Outcome.IsSuccess()) << m_errParseFailed;
ASSERT_STREQ(v1.ToString().c_str(), version1String) << m_errToStringIncorrect;
}
TEST_F(VersionTest, Hasher_DifferentValues_GetBackSameValue)
{
{
AZStd::unordered_map<GemVersion, int> unorderedMap;
GemVersion v1 = { 1, 0, 0 };
int v1value = 1;
GemVersion v2 = { 2, 0, 0 };
int v2value = 2;
GemVersion v3 = { 1, 1, 0 };
int v3value = 3;
GemVersion v4 = { 1, 1, 1 };
int v4value = 4;
unorderedMap[v1] = v1value;
unorderedMap[v2] = v2value;
unorderedMap[v3] = v3value;
unorderedMap[v4] = v4value;
ASSERT_EQ(unorderedMap[v1], v1value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap[v2], v2value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap[v3], v3value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap[v4], v4value) << m_errHasherIncorrect;
}
{
AZStd::unordered_map<EngineVersion, int> unorderedMap;
EngineVersion v1 = { 1, 0, 0, 0 };
int v1value = 1;
EngineVersion v2 = { 2, 0, 0, 0 };
int v2value = 2;
EngineVersion v3 = { 1, 1, 0, 0 };
int v3value = 3;
EngineVersion v4 = { 1, 1, 1, 0 };
int v4value = 4;
unorderedMap[v1] = v1value;
unorderedMap[v2] = v2value;
unorderedMap[v3] = v3value;
unorderedMap[v4] = v4value;
ASSERT_EQ(unorderedMap[v1], v1value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap[v2], v2value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap[v3], v3value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap[v4], v4value) << m_errHasherIncorrect;
}
}
TEST_F(VersionTest, Hasher_SameValues_GetBackSameValue)
{
{
AZStd::unordered_map<GemVersion, int> unorderedMap;
GemVersion v1 = { 1, 1, 1 };
GemVersion v2 = { 1, 1, 1 };
int v12value = 1;
unorderedMap[v1] = v12value;
ASSERT_EQ(unorderedMap.at(v1), v12value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap.at(v2), v12value) << m_errHasherIncorrect;
}
{
AZStd::unordered_map<EngineVersion, int> unorderedMap;
EngineVersion v1 = { 1, 1, 1, 0 };
EngineVersion v2 = { 1, 1, 1, 0 };
int v12value = 1;
unorderedMap[v1] = v12value;
ASSERT_EQ(unorderedMap.at(v1), v12value) << m_errHasherIncorrect;
ASSERT_EQ(unorderedMap.at(v2), v12value) << m_errHasherIncorrect;
}
}
-38
View File
@@ -1,38 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include <AzCore/Memory/AllocatorManager.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Memory/SystemAllocator.h>
class GemRegistryTestEnvironment
: public AZ::Test::ITestEnvironment
{
public:
virtual ~GemRegistryTestEnvironment()
{}
protected:
void SetupEnvironment() override
{
AZ::AllocatorInstance<AZ::OSAllocator>::Create();
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
void TeardownEnvironment() override
{
AZ::AllocatorInstance<AZ::OSAllocator>::Destroy();
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
};
AZ_UNIT_TEST_HOOK(new GemRegistryTestEnvironment);
@@ -11,6 +11,7 @@
*/
#include <AzCore/PlatformDef.h>
#include <AzCore/IO/Path/Path.h>
#include "NewsBuilder.h"
#include "Qt/ArticleDetails.h"
#include "Qt/BuilderArticleViewContainer.h"
@@ -39,8 +40,7 @@ AZ_POP_DISABLE_WARNING
namespace News
{
NewsBuilder::NewsBuilder(QWidget* parent)
NewsBuilder::NewsBuilder(QWidget* parent, const AZ::IO::PathView& engineRootPath)
: QMainWindow(parent)
, m_ui(new Ui::NewsBuilderClass())
, m_manifest(new BuilderResourceManifest(
@@ -51,15 +51,16 @@ namespace News
, m_articleViewContainer(new BuilderArticleViewContainer(this, *m_manifest))
, m_logContainer(new LogContainer(this))
{
AzQtComponents::StyleManager* m_styleSheet = new AzQtComponents::StyleManager(this);
m_styleSheet->initialize(qApp);
m_styleSheet->initialize(qApp, engineRootPath);
m_ui->setupUi(this);
QDir rootDir(AzQtComponents::FindEngineRootDir(qApp));
QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast<int>(engineRootPath.Native().size()));
const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/News/NewsBuilder/Resources");
const auto qrcPath = QStringLiteral(":/NewsBuilder");
AzQtComponents::StyleManager::addSearchPaths("newsbuilder", pathOnDisk, qrcPath);
AzQtComponents::StyleManager::addSearchPaths("newsbuilder", pathOnDisk, qrcPath, engineRootPath);
AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("newsbuilder:NewsBuilder.qss"));
+3 -1
View File
@@ -17,6 +17,8 @@
#include "NewsShared/LogType.h"
#include "NewsShared/ErrorCodes.h"
#include <AzCore/IO/Path/Path_fwd.h>
#endif
class QSignalMapper;
@@ -41,7 +43,7 @@ namespace News
Q_OBJECT
public:
explicit NewsBuilder(QWidget* parent = nullptr);
explicit NewsBuilder(QWidget* parent, const AZ::IO::PathView& engineRootPath);
~NewsBuilder();
private:
+10 -3
View File
@@ -15,19 +15,26 @@
#include "Qt/NewsBuilder.h"
#include <AzCore/base.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/IO/Path/Path.h>
int main(int argc, char *argv[])
{
// Must be set before QApplication is initialized, so that we support HighDpi monitors, like the Retina displays
// on Windows 10
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
QApplication a(argc, argv);
News::NewsBuilder w;
AZ::IO::FixedMaxPath engineRootPath;
{
AZ::ComponentApplication componentApplication;
auto settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
News::NewsBuilder w(nullptr, engineRootPath);
w.show();
return a.exec();
}
-20
View File
@@ -1,20 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProfVis", "ProfVis\ProfVis.csproj", "{3AF4C1C6-4DF2-4F3E-9997-B2844D461DF8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3AF4C1C6-4DF2-4F3E-9997-B2844D461DF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AF4C1C6-4DF2-4F3E-9997-B2844D461DF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AF4C1C6-4DF2-4F3E-9997-B2844D461DF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AF4C1C6-4DF2-4F3E-9997-B2844D461DF8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
-162
View File
@@ -1,162 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
namespace ProfVis
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.reloadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.progressBar1 = new System.Windows.Forms.ProgressBar();
this.searchTextBox = new System.Windows.Forms.TextBox();
this.dataGridView_Prof = new ProfVis.DGW();
this.menuStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridView_Prof)).BeginInit();
this.SuspendLayout();
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileToolStripMenuItem,
this.reloadToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(869, 24);
this.menuStrip1.TabIndex = 6;
this.menuStrip1.Text = "menuStrip1";
//
// fileToolStripMenuItem
//
this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.openToolStripMenuItem});
this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
this.fileToolStripMenuItem.Text = "File";
//
// openToolStripMenuItem
//
this.openToolStripMenuItem.Name = "openToolStripMenuItem";
this.openToolStripMenuItem.Size = new System.Drawing.Size(112, 22);
this.openToolStripMenuItem.Text = "&Open...";
this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click);
//
// reloadToolStripMenuItem
//
this.reloadToolStripMenuItem.Name = "reloadToolStripMenuItem";
this.reloadToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
this.reloadToolStripMenuItem.Text = "Reload";
this.reloadToolStripMenuItem.Click += new System.EventHandler(this.reloadToolStripMenuItem_Click);
//
// progressBar1
//
this.progressBar1.Dock = System.Windows.Forms.DockStyle.Top;
this.progressBar1.Location = new System.Drawing.Point(0, 24);
this.progressBar1.MarqueeAnimationSpeed = 5;
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(869, 23);
this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar1.TabIndex = 7;
this.progressBar1.Visible = false;
//
// searchTextBox
//
this.searchTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.searchTextBox.Location = new System.Drawing.Point(646, 2);
this.searchTextBox.Name = "searchTextBox";
this.searchTextBox.Size = new System.Drawing.Size(221, 20);
this.searchTextBox.TabIndex = 8;
this.searchTextBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.searchTextBox_KeyUp);
//
// dataGridView_Prof
//
this.dataGridView_Prof.AllowUserToAddRows = false;
this.dataGridView_Prof.AllowUserToDeleteRows = false;
this.dataGridView_Prof.AllowUserToResizeColumns = false;
this.dataGridView_Prof.AllowUserToResizeRows = false;
this.dataGridView_Prof.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView_Prof.Location = new System.Drawing.Point(37, 29);
this.dataGridView_Prof.Name = "dataGridView_Prof";
this.dataGridView_Prof.ReadOnly = true;
this.dataGridView_Prof.RowTemplate.ReadOnly = true;
this.dataGridView_Prof.ShowCellToolTips = false;
this.dataGridView_Prof.Size = new System.Drawing.Size(408, 351);
this.dataGridView_Prof.TabIndex = 5;
this.dataGridView_Prof.CellPainting += new System.Windows.Forms.DataGridViewCellPaintingEventHandler(this.dataGridView_Prof_CellPainting);
this.dataGridView_Prof.MouseMove += new System.Windows.Forms.MouseEventHandler(this.dataGridView_Prof_MouseMove);
//
// Form1
//
this.AllowDrop = true;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(869, 548);
this.Controls.Add(this.searchTextBox);
this.Controls.Add(this.progressBar1);
this.Controls.Add(this.dataGridView_Prof);
this.Controls.Add(this.menuStrip1);
this.MainMenuStrip = this.menuStrip1;
this.Name = "Form1";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
this.Load += new System.EventHandler(this.Form1_Load);
this.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
this.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter);
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridView_Prof)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem openToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem reloadToolStripMenuItem;
public DGW dataGridView_Prof;
private System.Windows.Forms.ProgressBar progressBar1;
private System.Windows.Forms.TextBox searchTextBox;
}
}
-891
View File
@@ -1,891 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
namespace ProfVis
{
public partial class Form1 : Form
{
public Form1(string[] args)
{
InitializeComponent();
Show();
if (args.Length >= 1)
{
OpenSession(args[0]);
}
}
public static Color[] s_clrBack = { Color.FromArgb(235, 235, 235), Color.White };
public static Color[] s_clrBars = { Color.FromArgb(140, 198, 63),
Color.FromArgb(247, 148, 30),
Color.FromArgb(37, 170, 225),
Color.FromArgb(122, 45, 24),
Color.FromArgb(239, 62, 54),
};
private ToolTip m_tooltip; //TODO: kill this and use some custom panel to prevent flickering
public List<Brush> m_BarsBrushes;
public Font m_barFont;
private static Random m_random;
// session
private string m_sessionName;
public List<CThreadTag> m_threadsData;
public static string m_filter = "";
private BackgroundWorker m_loadingWorker;
private void Form1_Load(object sender, EventArgs e)
{
//DoubleBuffered = true;
dataGridView_Prof.Dock = DockStyle.Fill;
dataGridView_Prof.AutoGenerateColumns = false;
dataGridView_Prof.ColumnCount = 2;
//AdjustDataGridViewSizing();
dataGridView_Prof.Columns[0].Name = "Thread";
dataGridView_Prof.Columns[1].Name = "Blocks";
dataGridView_Prof.Columns[0].Frozen = true;
dataGridView_Prof.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
dataGridView_Prof.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
//dataGridView_Prof.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
//dataGridView_Prof.Columns[1].Width = 500;
dataGridView_Prof.Columns[0].SortMode = DataGridViewColumnSortMode.NotSortable;
dataGridView_Prof.Columns[1].SortMode = DataGridViewColumnSortMode.NotSortable;
dataGridView_Prof.ShowCellToolTips = false;
dataGridView_Prof.MouseWheel += new MouseEventHandler(dataGridView_Prof_MouseWheel);
dataGridView_Prof.MouseClick += new MouseEventHandler(dataGridView_Prof_MouseClick);
//dataGridView_Prof.AllowUserToResizeRows = true;
m_barFont = (Font)dataGridView_Prof.DefaultCellStyle.Font.Clone();
m_tooltip = new ToolTip();
m_tooltip.ShowAlways = false;
m_tooltip.UseAnimation = false;
m_tooltip.UseFading = false;
m_tooltip.AutomaticDelay = 0;
m_tooltip.AutoPopDelay = 0;
m_BarsBrushes = new List<Brush>();
for(int i = 0; i < Form1.s_clrBars.Length; ++i)
{
m_BarsBrushes.Add(new SolidBrush(s_clrBars[i]));
}
m_loadingWorker = new BackgroundWorker();
m_loadingWorker.WorkerReportsProgress = true;
m_loadingWorker.ProgressChanged += (object progressSender, ProgressChangedEventArgs progressArgs) =>
{
DataGridViewRow row = progressArgs.UserState as DataGridViewRow;
if (row != null)
dataGridView_Prof.Rows.Add(row);
};
m_loadingWorker.RunWorkerCompleted += (object progressSender, RunWorkerCompletedEventArgs progressArgs) =>
{
progressBar1.Visible = false;
progressBar1.Enabled = false;
menuStrip1.Enabled = true;
this.Text = m_sessionName;
};
m_loadingWorker.DoWork += (object progressSender, DoWorkEventArgs doWorkArgs) =>
{
Graphics g = this.CreateGraphics();
try
{
m_hoveredData = null;
if (m_threadsData != null)
m_threadsData.Clear();
string filename = doWorkArgs.Argument as string;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filename);
m_sessionName = filename;
m_threadsData = new List<CThreadTag>();
XmlNode rootNode = xmlDoc.SelectSingleNode("root");
int threadIndex = 0;
int num_childs = rootNode.ChildNodes.Count;
foreach (XmlNode threadNode in rootNode)
{
string threadName = threadNode.Attributes.GetNamedItem("name").Value;
float threadTimeMS = Single.Parse(threadNode.Attributes.GetNamedItem("totalTimeMS").Value, System.Globalization.CultureInfo.InvariantCulture);
long threadStart = Convert.ToInt64(threadNode.Attributes.GetNamedItem("startTime").Value);
long threadEnd = Convert.ToInt64(threadNode.Attributes.GetNamedItem("stopTime").Value);
long threadTime = threadEnd - threadStart;
m_threadsData.Add(new CThreadTag());
CThreadTag threadData = m_threadsData[threadIndex];
threadData.m_Times = new CBlockData(threadName, threadTimeMS, threadStart, threadEnd, 0, "");
DataGridViewRow row = new DataGridViewRow();
DataGridViewCell cell0 = new DataGridViewTextBoxCell();
cell0.Value = threadName;
row.Cells.Add(cell0);
row.Tag = threadData;
m_loadingWorker.ReportProgress(0, row);
getBlocksFromXML(threadNode, 0, threadIndex);
threadIndex++;
// create rows
for (int i = 0; i < threadData.m_StackEnties.Count; ++i)
{
CBlockTag rowInfo = threadData.m_StackEnties[i];
// used for DrawFrectangles()
for (int j = 0; j < rowInfo.blocks.Count; ++j)
{
CBlockData data = rowInfo.blocks[j];
//rowInfo.blocksByColor[data.colorIdx].Add(data);
data.m_labelSize = g.MeasureString(data.name, m_barFont);
}
DataGridViewRow stackedRow = new DataGridViewRow();
DataGridViewCell stackedCell = new DataGridViewTextBoxCell();
stackedCell.Value = i;
stackedRow.Cells.Add(stackedCell);
stackedRow.Tag = rowInfo;
m_loadingWorker.ReportProgress(0, stackedRow);
}
}// foreach (XmlNode threadNode in rootNode)
}
catch (System.Exception ex)
{
m_sessionName = string.Empty;
MessageBox.Show(ex.Message);
}
g.Dispose();
};
}
private void OpenSession(string filename)
{
if (m_loadingWorker.IsBusy)
return;
// 0 seed for same colors
m_random = new Random(0);
dataGridView_Prof.Rows.Clear();
DataGridViewRow timeRow = new DataGridViewRow();
DataGridViewCell textCell = new DataGridViewTextBoxCell();
textCell.Value = "Time";
timeRow.Cells.Add(textCell);
timeRow.Height = 40;
timeRow.Tag = new CTimeTag();
timeRow.Frozen = true;
dataGridView_Prof.Rows.Add(timeRow);
progressBar1.Value = 0;
progressBar1.Visible = true;
progressBar1.Enabled = true;
menuStrip1.Enabled = false;
m_loadingWorker.RunWorkerAsync(filename);
}
private void getBlocksFromXML(XmlNode node, int depth, int threadIndex)
{
//if (depth == 3)
// return;
if (node.SelectSingleNode("block")!=null)
{
CThreadTag threadData = m_threadsData[threadIndex];
if (depth >= threadData.m_StackEnties.Count)
{
threadData.m_StackEnties.Add(new CBlockTag()); //new stack level
threadData.m_StackEnties[depth].threadIndex = threadIndex;
}
int prevClrIndex = 0;
foreach (XmlNode blockNode in node)
{
string name = blockNode.Attributes.GetNamedItem("name").Value;
float totalTimeMS = Single.Parse(blockNode.Attributes.GetNamedItem("totalTimeMS").Value, System.Globalization.CultureInfo.InvariantCulture);
long startTime = Convert.ToInt64(blockNode.Attributes.GetNamedItem("startTime").Value);
long stopTime = Convert.ToInt64(blockNode.Attributes.GetNamedItem("stopTime").Value);
XmlNode argsNode = blockNode.Attributes.GetNamedItem("args");
string args = "";
if(argsNode != null)
args = argsNode.Value;
int clrIndex = m_random.Next(0, s_clrBars.Length);
if (clrIndex == prevClrIndex)
{
clrIndex++;
if (clrIndex == s_clrBars.Length)
clrIndex = 0;
}
threadData.m_StackEnties[depth].blocks.Add(new CBlockData(name, totalTimeMS, startTime, stopTime, clrIndex, args));
prevClrIndex = clrIndex;
getBlocksFromXML(blockNode, depth+1, threadIndex);
}
}
}
private void dataGridView_Prof_MouseWheel(object sender, MouseEventArgs e)
{
if (ModifierKeys == Keys.Control)
{
if (!dataGridView_Prof.IsOffsetSet())
{
return;
}
int oldOffset = dataGridView_Prof.HorizontalScrollingOffset;
dataGridView_Prof.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
double zoomFactor = Math.Pow(2, (double)e.Delta * 0.001);
int newWidth = (int)(dataGridView_Prof.Columns[1].Width * zoomFactor);
bool clampWidth = newWidth < 0 || newWidth >= 65535;
newWidth = newWidth < 65535 ? newWidth : 65535;
newWidth = newWidth >= 0 ? newWidth : 0;
dataGridView_Prof.Columns[1].Width = newWidth;
int visibleColumn1Width = dataGridView_Prof.Width - dataGridView_Prof.Columns[0].Width;
int mouseColumn1Offset = e.X - dataGridView_Prof.Columns[0].Width;
double mousePrecentage = (double)mouseColumn1Offset / (double)visibleColumn1Width;
int newOffset = clampWidth ? oldOffset : ((int)((double)oldOffset * zoomFactor) + (int)(visibleColumn1Width * mousePrecentage * (double)e.Delta * 0.001));
newOffset = newOffset >= 0 ? newOffset : 0;
dataGridView_Prof.SetOffset(newOffset);
}
}
private void dataGridView_Prof_MouseClick(object sender, MouseEventArgs e)
{
if (ModifierKeys == Keys.Control)
{
dataGridView_Prof.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
if (e.Button == MouseButtons.Right)
{
if (m_hoveredData != null)
{
ContextMenu m = new ContextMenu();
MenuItem copyName = new MenuItem("Copy name");
copyName.Click += new System.EventHandler(
delegate(object o, EventArgs click_args)
{
Clipboard.SetText(m_hoveredData.name);
}
);
m.MenuItems.Add(copyName);
MenuItem copyArgs = new MenuItem("Copy arguments");
copyArgs.Click += new System.EventHandler(
delegate(object o, EventArgs click_args)
{
Clipboard.SetText(m_hoveredData.args);
}
);
m.MenuItems.Add(copyArgs);
m.Show(dataGridView_Prof, e.Location);
}
}
}
private void dataGridView_Prof_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex < 0 || e.ColumnIndex < 0)
return;
if (e.ColumnIndex != 1)
return;
object tag = dataGridView_Prof.Rows[e.RowIndex].Tag;
if (tag != null)
{
IRowTag rowTag = tag as IRowTag;
if (rowTag != null)
rowTag.Draw(ref e, this);
}
else
{
//DRAW timeline
}
}
private int binarySearch(List<CBlockData> list, int value)
{
int min = 0;
int max = list.Count - 1;
while(min <= max)
{
int mid = min + (max - min)/2;
if (value >= list[mid].drawXStart)
{
if (value <= list[mid].drawXEnd)
return mid;
else
min = mid + 1;
}
else
max = mid - 1;
}
return -1;
}
private Point m_PrevMousePos;
private CBlockData m_hoveredData;
private void dataGridView_Prof_MouseMove(object sender, MouseEventArgs e)
{
if (m_loadingWorker.IsBusy)
return;
if (m_PrevMousePos != e.Location)
{
m_PrevMousePos = e.Location;
DataGridView.HitTestInfo hittest = dataGridView_Prof.HitTest(e.Location.X, e.Location.Y);
m_tooltip.RemoveAll();
if (hittest.ColumnIndex == 1 && hittest.RowIndex >= 0 && hittest.RowIndex < dataGridView_Prof.RowCount)
{
object tag = dataGridView_Prof.Rows[hittest.RowIndex].Tag;
if (tag != null)
{
CBlockTag info = tag as CBlockTag;
if (info != null)
{
List<CBlockData> filteredBlocks = info.blocks.Where(x => x.visible).ToList();
int idx = binarySearch(filteredBlocks, e.Location.X);
if (idx != -1)
{
m_hoveredData = filteredBlocks[idx];
m_tooltip.Hide(dataGridView_Prof);
Rectangle columnRect = info.cellBounds;
float threadMS = m_threadsData[0].m_Times.timeMS;
float mouseRel = ((e.X - columnRect.X) * 100.0f) / columnRect.Width;
float mouseRelMS = (mouseRel / 100.0f) * threadMS;
string text;
m_tooltip.ToolTipTitle = filteredBlocks[idx].name;
if (filteredBlocks[idx].args.Length > 0)
{
text = string.Format("totalTime = {0} ms\ncursorAbsolute = {1} ms ({2:##.##} %)\nargs = {3}",
filteredBlocks[idx].timeMS, mouseRelMS, mouseRel, filteredBlocks[idx].args);
}
else
{
text = string.Format("totalTime = {0} ms\ncursorAbsolute = {1} ms ({2:##.##} %)",
filteredBlocks[idx].timeMS, mouseRelMS, mouseRel);
}
m_tooltip.Show(text, dataGridView_Prof, e.X + 20, e.Y + 20);
}
else
{
m_hoveredData = null;
m_tooltip.Hide(dataGridView_Prof);
}
}
CThreadTag td = tag as CThreadTag;
if (td != null)
{
m_tooltip.Hide(dataGridView_Prof);
m_tooltip.Show(string.Format("time = {0} ms", td.m_Times.timeMS), dataGridView_Prof, e.X + 20, e.Y + 20);
m_tooltip.ToolTipTitle = td.m_Times.name;
}
}
else
{
m_hoveredData = null;
m_tooltip.Hide(dataGridView_Prof);
}
}
else
{
m_hoveredData = null;
m_tooltip.Hide(dataGridView_Prof);
}
}
// base.OnMouseMove(e);
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files.Length == 1)
{
OpenSession(files[0]);
}
}
}
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Move;
}
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog myDialog = new OpenFileDialog();
myDialog.Filter = "All files (*.xml)|*.xml";
myDialog.CheckFileExists = true;
myDialog.Multiselect = false;
if (myDialog.ShowDialog() == DialogResult.OK)
{
OpenSession(myDialog.FileName);
}
}
private void reloadToolStripMenuItem_Click(object sender, EventArgs e)
{
if (m_sessionName != null)
OpenSession(m_sessionName);
}
private void searchTextBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
m_filter = searchTextBox.Text;
for (int i = 0; i < m_threadsData.Count; ++i)
{
m_threadsData[i].UpdateFilter();
}
dataGridView_Prof.Refresh();
}
}
}
public class DGW : DataGridView
{
private bool reapplyOffset = false;
private int deferredOffset = 0;
public DGW()
{
this.DoubleBuffered = true;
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
if (reapplyOffset)
{
HorizontalScrollingOffset = deferredOffset;
reapplyOffset = false;
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (reapplyOffset)
{
HorizontalScrollingOffset = deferredOffset;
reapplyOffset = false;
Invalidate(true);
}
base.OnPaint(e);
}
public bool IsOffsetSet()
{
return !reapplyOffset;
}
public void SetOffset(int offset)
{
if (reapplyOffset)
{
deferredOffset += offset;
return;
}
reapplyOffset = true;
deferredOffset = offset;
}
}
public class CBlockData
{
public string name;
public float timeMS;
public long timeStart;
public long timeEnd;
public long time;
public string args;
public int drawXStart;
public int drawXEnd;
public int colorIdx;
public bool visible;
public SizeF m_labelSize;
public CBlockData(string n, float t, long start, long end, int clrIdx, string a)
{
name = n;
timeMS = t;
timeStart = start;
timeEnd = end;
time = end - start;
args = a;
colorIdx = clrIdx;
visible = true;
}
}
public abstract class IRowTag
{
public abstract void Draw(ref DataGridViewCellPaintingEventArgs e, Form1 form);
}
public class CBlockTag : IRowTag
{
public int threadIndex;
public List<CBlockData> blocks;
public Rectangle cellBounds;
public CBlockTag()
{
blocks = new List<CBlockData>();
}
public void UpdateFilter()
{
for (int i = 0; i < blocks.Count; i++)
{
CBlockData blockData = blocks[i];
blockData.visible = Form1.m_filter == ""
|| (blockData.name.ToLower().Contains(Form1.m_filter.ToLower())
|| blockData.args.ToLower().Contains(Form1.m_filter.ToLower()));
}
}
public override void Draw(ref DataGridViewCellPaintingEventArgs e, Form1 form)
{
e.Handled = true;
Graphics g = e.Graphics;
cellBounds = e.CellBounds;
const int offset = 2;
Rectangle cellBound = new Rectangle(e.CellBounds.X, e.CellBounds.Y + offset,
e.CellBounds.Width, e.CellBounds.Height - offset - 1);
using (Brush backBrush = new SolidBrush(Form1.s_clrBack[e.RowIndex % 2]))
{
using (Pen borderPen = new Pen(Color.Black, 1))
{
g.FillRectangle(backBrush, e.CellBounds);
g.DrawRectangle(borderPen, e.CellBounds);
borderPen.Dispose();
}
}
CThreadTag threadData = form.m_threadsData[threadIndex];
CBlockData threadTime = threadData.m_Times;
long ttime = threadTime.time; // 100%
bool smallBlock = false;
int smallBlockPos = 0;
for (int i = 0; i < blocks.Count; i++)
{
CBlockData blockData = blocks[i];
if (!blockData.visible)
{
continue;
}
long blockWidth = e.CellBounds.Width * blockData.time / ttime;
cellBound.Width = (int)blockWidth;
long blockStart = (blockData.timeStart - threadTime.timeStart) * e.CellBounds.Width / ttime;
cellBound.X = e.CellBounds.X + (int)blockStart;
blockData.drawXStart = cellBound.X;
blockData.drawXEnd = cellBound.X + cellBound.Width;
if (smallBlock && smallBlockPos != cellBound.X)
{
Rectangle rec = new Rectangle(smallBlockPos, e.CellBounds.Y + offset,
1, e.CellBounds.Height - offset - 1);
g.FillRectangle(form.m_BarsBrushes[blocks[i - 1].colorIdx], rec);
smallBlock = false;
}
if (cellBound.Width < 1)
{
//cellBound.Width = 1;
smallBlockPos = cellBound.X;
smallBlock = true;
continue;
}
if ((cellBound.X < e.ClipBounds.X + e.ClipBounds.Width) &&
(cellBound.X + cellBound.Width > e.ClipBounds.X))
{
g.FillRectangle(form.m_BarsBrushes[blockData.colorIdx], cellBound);
SizeF textSize = blockData.m_labelSize;
if (textSize.Width < cellBound.Width - 3)
{
StringFormat formatter = new StringFormat();
formatter.LineAlignment = StringAlignment.Center;
formatter.Alignment = StringAlignment.Center;
float tx = cellBound.X + cellBound.Width / 2;
float ty = cellBound.Y + cellBound.Height / 2;
g.DrawString(blockData.name, form.m_barFont, Brushes.White, tx, ty, formatter);
}
}
} //for (int i = 0; i < info.blocks.Count; i++)
if (smallBlock)
{
Rectangle rec = new Rectangle(smallBlockPos, e.CellBounds.Y + offset,
1, e.CellBounds.Height - offset - 1);
g.FillRectangle(form.m_BarsBrushes[blocks[blocks.Count - 1].colorIdx], rec);
}
}
}
public class CThreadTag : IRowTag
{
public CBlockData m_Times;
public List<CBlockTag> m_StackEnties; //each stack level have a list of blocks
public CThreadTag()
{
m_StackEnties = new List<CBlockTag>();
}
public void UpdateFilter()
{
for (int i = 0; i < m_StackEnties.Count; i++)
{
CBlockTag tag = m_StackEnties[i];
tag.UpdateFilter();
}
}
public override void Draw(ref DataGridViewCellPaintingEventArgs e, Form1 form)
{
e.Handled = true;
Graphics g = e.Graphics;
const int offset = 2;
Rectangle cellBound = new Rectangle(e.CellBounds.X, e.CellBounds.Y + offset,
e.CellBounds.Width, e.CellBounds.Height - offset - 1);
string threadText = "Thread: " + m_Times.name;
g.FillRectangle(Brushes.DimGray, e.CellBounds);
Font font = new Font(e.CellStyle.Font, FontStyle.Bold);
SizeF textSize = g.MeasureString(threadText, font);
if (textSize.Width < cellBound.Width - 3)
{
StringFormat formatter = new StringFormat();
formatter.LineAlignment = StringAlignment.Center;
formatter.Alignment = StringAlignment.Center;
float tx = cellBound.X + cellBound.Width / 2;
float ty = cellBound.Y + cellBound.Height / 2;
g.DrawString(threadText, font, Brushes.White, tx, ty, formatter);
}
}
}
public class CTimeTag : IRowTag
{
private void DrawLinesBetween(ref DataGridViewCellPaintingEventArgs e, Pen pen, Font font, int pos1, int pos2, float pos2MS, float blockMS, int markerHeight)
{
int blockThreshold = 200; // in pixels
//int blockDivCount = 5;
int diff = pos2 - pos1;
if (diff != 0 && diff > blockThreshold && markerHeight > 1)
{
Graphics g = e.Graphics;
int X = e.CellBounds.X;
int Y = e.CellBounds.Y + e.CellBounds.Height - 1;
int linePos = pos1 + (pos2 - pos1)/2;
int DrawPos = X + linePos;
float pos1MS = pos2MS - blockMS;
float lineMS = pos1MS + (pos2MS - pos1MS) / 2.0f;
//if (DrawPos > e.ClipBounds.X && DrawPos < e.ClipBounds.X + e.ClipBounds.Width) //artifacts ;(
{
g.DrawLine(pen, DrawPos, Y - markerHeight, DrawPos, Y);
string s = string.Format("{0:##.##}", (lineMS));
StringFormat formatter = new StringFormat();
formatter.LineAlignment = StringAlignment.Center;
formatter.Alignment = StringAlignment.Center;
g.DrawString(s, font, Brushes.Black, DrawPos, Y - markerHeight - 5, formatter);
blockMS = pos2MS - lineMS;
DrawLinesBetween(ref e, pen, font, pos1, linePos, lineMS, blockMS, markerHeight - 2);
DrawLinesBetween(ref e, pen, font, linePos, pos2, pos2MS, blockMS, markerHeight - 2);
}
}
}
public override void Draw(ref DataGridViewCellPaintingEventArgs e, Form1 form)
{
e.Handled = true;
Graphics g = e.Graphics;
g.FillRectangle(Brushes.White, e.CellBounds);
if (form.m_threadsData != null && form.m_threadsData.Count > 0)
{
Int32 cellWidth = e.CellBounds.Width;
Int32 lineYpos = e.CellBounds.Y + e.CellBounds.Height - 1;
using (Pen btPen = new Pen(Color.Black, 2))
{
// bottom line
g.DrawLine(btPen,
e.CellBounds.X, lineYpos,
e.CellBounds.X + cellWidth, lineYpos);
btPen.Dispose();
}
//form.dataGridView_Prof.PointToScreen()
g.DrawString("Milliseconds", form.m_barFont, Brushes.Black, e.CellBounds.X + 1 , e.CellBounds.Y + 1);
Pen pen = new Pen(Color.Black, 1);
CThreadTag threadData = form.m_threadsData[0];
CBlockData threadTime = threadData.m_Times;
float totalTimeMS = threadTime.timeMS;
//long totalTime = threadTime.time;
int marketHeight = 16;
int[] blocks = {10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1};
float timeMSperBlock = 0.0f;
int blockInx = 0;
while (timeMSperBlock < 10.0f || blockInx > blocks.Count())
{
timeMSperBlock = totalTimeMS / blocks[blockInx++];
}
if (blockInx != 0 )
--blockInx;
float blockMS = blocks[blockInx];
int linePx = 0;
int linePrevPx = 0;
int i = 0;
while (linePx < cellWidth)
{
float lineMS = i *blockMS;
++i;
linePrevPx = linePx;
linePx = Convert.ToInt32(Math.Round((float)(lineMS * cellWidth) / totalTimeMS));
DrawLinesBetween(ref e, pen, form.m_barFont, linePrevPx, linePx, lineMS, blockMS, marketHeight);
int Xpos = e.CellBounds.X + linePx;
g.DrawLine(pen, Xpos, lineYpos - marketHeight, Xpos, lineYpos);
string s = string.Format("{0}", Convert.ToInt32(lineMS));
StringFormat formatter = new StringFormat();
formatter.LineAlignment = StringAlignment.Center;
formatter.Alignment = StringAlignment.Center;
float tx = Xpos;
float ty = lineYpos - marketHeight - 5;
g.DrawString(s, form.m_barFont, Brushes.Black, tx, ty, formatter);
}
pen.Dispose();
}
}
}
}
-123
View File
@@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>
-129
View File
@@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{3AF4C1C6-4DF2-4F3E-9997-B2844D461DF8}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ProfVis</RootNamespace>
<AssemblyName>ProfVis</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.ReportViewer.Common, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
<Reference Include="Microsoft.ReportViewer.WinForms, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Web.Services" />
<Reference Include="System.Windows.Forms.DataVisualization" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\DataSources\Form1.datasource" />
<None Include="Properties\DataSources\ProfVis.Properties.Resources.datasource" />
<None Include="Properties\DataSources\ProfVis.Properties.Settings.datasource" />
<None Include="Properties\DataSources\Program.datasource" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
-35
View File
@@ -1,35 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ProfVis
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(args));
}
}
}
@@ -1,49 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ProfVis")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ProfVis")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c15ff8ac-4f15-4960-96a2-1cc11631169d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="Form1" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>ProfVis.Form1, ProfVis, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="Resources" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>ProfVis.Properties.Resources, ProfVis, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="Settings" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>ProfVis.Properties.Settings, ProfVis, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="Program" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>ProfVis.Program, ProfVis, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>
@@ -1,72 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.17929
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
// Modifications copyright Amazon.com, Inc. or its affiliates
namespace ProfVis.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ProfVis.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}
@@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -1,31 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.17929
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
// Modifications copyright Amazon.com, Inc. or its affiliates
namespace ProfVis.Properties
{
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
return defaultInstance;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More