SettingsRegistryBuilder.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <limits>
  9. #include <AssetBuilderSDK/AssetBuilderSDK.h>
  10. #include <AzCore/Component/ComponentApplicationBus.h>
  11. #include <AzCore/Settings/SettingsRegistryImpl.h>
  12. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  13. #include <AzCore/Utils/Utils.h>
  14. #include <AzFramework/Platform/PlatformDefaults.h>
  15. #include <AzFramework/StringFunc/StringFunc.h>
  16. #include <native/InternalBuilders/SettingsRegistryBuilder.h>
  17. #include <native/utilities/PlatformConfiguration.h>
  18. namespace AssetProcessor
  19. {
  20. SettingsRegistryBuilder::SettingsRegistryBuilder()
  21. : m_builderId("{1BB18B28-2953-4922-A80B-E7375FCD7FC1}")
  22. , m_assetType("{FEBB3C7B-9C8B-46C3-8AAF-3D132D811087}")
  23. {
  24. AssetBuilderSDK::AssetBuilderCommandBus::Handler::BusConnect(m_builderId);
  25. }
  26. bool SettingsRegistryBuilder::Initialize()
  27. {
  28. AssetBuilderSDK::AssetBuilderDesc builderDesc;
  29. builderDesc.m_name = "Settings Registry Builder";
  30. builderDesc.m_patterns.emplace_back("*/engine.json", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard);
  31. builderDesc.m_builderType = AssetBuilderSDK::AssetBuilderDesc::AssetBuilderType::Internal;
  32. builderDesc.m_busId = m_builderId;
  33. builderDesc.m_createJobFunction = AZStd::bind(&SettingsRegistryBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
  34. builderDesc.m_processJobFunction = AZStd::bind(&SettingsRegistryBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
  35. builderDesc.m_version = 3;
  36. AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDesc);
  37. return true;
  38. }
  39. void SettingsRegistryBuilder::Uninitialize() {}
  40. void SettingsRegistryBuilder::ShutDown()
  41. {
  42. m_isShuttingDown = true;
  43. }
  44. void SettingsRegistryBuilder::CreateJobs(
  45. const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  46. {
  47. if (m_isShuttingDown)
  48. {
  49. response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
  50. return;
  51. }
  52. for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
  53. {
  54. AssetBuilderSDK::JobDescriptor job;
  55. job.m_jobKey = "Settings Registry";
  56. // The settings are the very first thing the game reads so needs to available before anything else.
  57. job.m_priority = std::numeric_limits<decltype(job.m_priority)>::max();
  58. job.m_critical = true;
  59. job.SetPlatformIdentifier(info.m_identifier.c_str());
  60. response.m_createJobOutputs.push_back(AZStd::move(job));
  61. }
  62. AZ::IO::Path settingsRegistryWildcard = AZStd::string_view(AZ::Utils::GetEnginePath());
  63. settingsRegistryWildcard /= AZ::SettingsRegistryInterface::RegistryFolder;
  64. settingsRegistryWildcard /= "*.setreg";
  65. response.m_sourceFileDependencyList.emplace_back(AZStd::move(settingsRegistryWildcard.Native()), AZ::Uuid::CreateNull(),
  66. AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards);
  67. auto projectPath = AZ::IO::Path(AZStd::string_view(AZ::Utils::GetProjectPath()));
  68. response.m_sourceFileDependencyList.emplace_back(
  69. AZStd::move((projectPath / AZ::SettingsRegistryInterface::RegistryFolder / "*.setreg").Native()),
  70. AZ::Uuid::CreateNull(),
  71. AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards);
  72. response.m_sourceFileDependencyList.emplace_back(
  73. AZStd::move((projectPath / AZ::SettingsRegistryInterface::DevUserRegistryFolder / "*.setreg").Native()),
  74. AZ::Uuid::CreateNull(),
  75. AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards);
  76. if (auto settingsRegistry = AZ::Interface<AZ::SettingsRegistryInterface>::Get(); settingsRegistry != nullptr)
  77. {
  78. AZStd::vector<AzFramework::GemInfo> gemInfos;
  79. if (AzFramework::GetGemsInfo(gemInfos, *settingsRegistry))
  80. {
  81. // Gather unique list of Settings Registry wildcard directories
  82. AZStd::vector<AZ::IO::Path> gemSettingsRegistryWildcards;
  83. for (const AzFramework::GemInfo& gemInfo : gemInfos)
  84. {
  85. for (const AZ::IO::Path& absoluteSourcePath : gemInfo.m_absoluteSourcePaths)
  86. {
  87. auto gemSettingsRegistryWildcard = absoluteSourcePath / AZ::SettingsRegistryInterface::RegistryFolder / "*.setreg";
  88. if (auto foundIt = AZStd::find(gemSettingsRegistryWildcards.begin(), gemSettingsRegistryWildcards.end(), gemSettingsRegistryWildcard);
  89. foundIt == gemSettingsRegistryWildcards.end())
  90. {
  91. gemSettingsRegistryWildcards.emplace_back(gemSettingsRegistryWildcard);
  92. }
  93. }
  94. }
  95. // Add to the Source File Dependency list
  96. for (AZ::IO::Path& gemSettingsRegistryWildcard : gemSettingsRegistryWildcards)
  97. {
  98. response.m_sourceFileDependencyList.emplace_back(
  99. AZStd::move(gemSettingsRegistryWildcard.Native()), AZ::Uuid::CreateNull(),
  100. AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards);
  101. }
  102. }
  103. }
  104. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  105. }
  106. void SettingsRegistryBuilder::ProcessJob(
  107. const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
  108. {
  109. if (m_isShuttingDown)
  110. {
  111. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  112. return;
  113. }
  114. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
  115. AZStd::vector<AZStd::string> excludes = ReadExcludesFromRegistry();
  116. // Exclude the AssetProcessor settings from the game registry
  117. excludes.emplace_back(AssetProcessor::AssetProcessorSettingsKey);
  118. AZStd::vector<char> scratchBuffer;
  119. scratchBuffer.reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer;
  120. AZStd::fixed_vector<AZStd::string_view, AzFramework::MaxPlatformCodeNames> platformCodes;
  121. AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, request.m_platformInfo.m_identifier);
  122. AZ_Assert(platformCodes.size() <= 1, "A one-to-one mapping of asset type platform identifier"
  123. " to platform codename is required in the SettingsRegistryBuilder."
  124. " The bootstrap.<launcher-type>.<config>.setreg is now only produced per launcher type + build configuration and doesn't take into account"
  125. " different platforms names");
  126. const AZStd::string& assetPlatformIdentifier = request.m_jobDescription.GetPlatformIdentifier();
  127. // Determines the suffix that will be used for the launcher based on processing server vs non-server assets
  128. const char* launcherType = assetPlatformIdentifier != AzFramework::PlatformHelper::GetPlatformName(AzFramework::PlatformId::SERVER)
  129. ? "_GameLauncher" : "_ServerLauncher";
  130. AZ::SettingsRegistryInterface::FilenameTags specializations[] =
  131. {
  132. { AZStd::string_view{"client"}, AZStd::string_view{"release"} },
  133. { AZStd::string_view{"client"} , AZStd::string_view{"profile"} },
  134. { AZStd::string_view{"client"}, AZStd::string_view{"debug"} },
  135. { AZStd::string_view{"server"}, AZStd::string_view{"release"} },
  136. { AZStd::string_view{"server"} , AZStd::string_view{"profile"} },
  137. { AZStd::string_view{"server"}, AZStd::string_view{"debug"} },
  138. { AZStd::string_view{"unified"}, AZStd::string_view{"release"} },
  139. { AZStd::string_view{"unified"} , AZStd::string_view{"profile"} },
  140. { AZStd::string_view{"unified"}, AZStd::string_view{"debug"} }
  141. };
  142. constexpr size_t LauncherTypeIndex = 0;
  143. constexpr size_t BuildConfigIndex = 1;
  144. // Append the specialization filename tag of "launcher" get all the `<filename>.*.launcher.*.setreg` files
  145. // to be merged into the aggregate Settings Registry
  146. constexpr AZStd::string_view LauncherFilenameTag = "launcher";
  147. for (AZ::SettingsRegistryInterface::FilenameTags& specialization : specializations)
  148. {
  149. specialization.Append(LauncherFilenameTag);
  150. // Also add the "game" tag for backwards compatibility with any existing
  151. // `<filename>.*.game.*.setreg` files
  152. specialization.Append("game");
  153. }
  154. // Add the project specific specializations
  155. auto projectName = AZ::Utils::GetProjectName();
  156. if (!projectName.empty())
  157. {
  158. for (AZ::SettingsRegistryInterface::FilenameTags& specialization : specializations)
  159. {
  160. specialization.Append(projectName);
  161. // The Game Launcher normally has a build target name of <ProjectName>Launcher
  162. // Add that as a specialization to pick up the gem dependencies files that are specialized
  163. // on a the Game Launcher target if the asset platform isn't "server"
  164. specialization.Append(projectName + launcherType);
  165. }
  166. }
  167. AZ::IO::Path outputPath = AZ::IO::Path(request.m_tempDirPath) / "bootstrap.";
  168. size_t extensionOffset = outputPath.Native().size();
  169. if (!platformCodes.empty())
  170. {
  171. // Setting up the Dumper settings for exporting the settings registry to a file
  172. AZStd::string outputBuffer;
  173. outputBuffer.reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer;
  174. AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings;
  175. dumperSettings.m_includeFilter = [&excludes](AZStd::string_view jsonKeyPath)
  176. {
  177. auto ExcludeField = [&jsonKeyPath](AZStd::string_view excludePath)
  178. {
  179. return AZ::SettingsRegistryMergeUtils::IsPathDescendantOrEqual(excludePath, jsonKeyPath);
  180. };
  181. // Include a path only if it is not equal or a suffix of any paths of the exclude vector
  182. return AZStd::ranges::find_if(excludes, ExcludeField) == AZStd::ranges::end(excludes);
  183. };
  184. AZStd::string_view platform = platformCodes.front();
  185. for (size_t i = 0; i < AZStd::size(specializations); ++i)
  186. {
  187. const AZ::SettingsRegistryInterface::FilenameTags& specialization = specializations[i];
  188. if (m_isShuttingDown)
  189. {
  190. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
  191. return;
  192. }
  193. using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
  194. AZ::SettingsRegistryImpl registry;
  195. // Seed the local settings registry using the AssetProcessor Settings Registry
  196. if (auto settingsRegistry = AZ::Interface<AZ::SettingsRegistryInterface>::Get(); settingsRegistry != nullptr)
  197. {
  198. AZStd::array settingsToCopy{
  199. AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey),
  200. AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_BinaryFolder},
  201. AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder},
  202. AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath},
  203. AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder},
  204. AZStd::string{AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder},
  205. };
  206. for (const auto& settingsKey : settingsToCopy)
  207. {
  208. FixedValueString settingsValue;
  209. [[maybe_unused]] bool settingsCopied = settingsRegistry->Get(settingsValue, settingsKey)
  210. && registry.Set(settingsKey, settingsValue);
  211. AZ_Warning("Settings Registry Builder", settingsCopied, "Unable to copy setting %s from AssetProcessor settings registry"
  212. " to local settings registry", settingsKey.c_str());
  213. }
  214. // The purpose of this section is to copy the active gems entry and manifest gems entries
  215. // to a local SettingsRegistry.
  216. // The reason this is needed is so that the call to
  217. // `MergeSettingsToRegistry_GemRegistries` below is able to locate each gems root directory
  218. // that will be merged into the bootstrap.<launcher-type>.<configuration>.setreg file
  219. // This is used by the GameLauncher applications to read from a single merged .setreg file
  220. // containing the settings needed to run a game/simulation without have access to the source code base registry
  221. auto CopySettingsToLocalRegistry = [&registry, settingsRegistry, copiedSettings = AZStd::string()]
  222. (AZStd::string_view copyFieldKey) mutable
  223. {
  224. // Copy Settings at the specified field key recursively to the local settings registry
  225. copiedSettings.clear();
  226. AZ::IO::ByteContainerStream copiedSettingsStream(&copiedSettings);
  227. AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings;
  228. AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(*settingsRegistry, copyFieldKey,
  229. copiedSettingsStream, dumperSettings);
  230. registry.MergeSettings(copiedSettings, AZ::SettingsRegistryInterface::Format::JsonMergePatch, copyFieldKey);
  231. };
  232. CopySettingsToLocalRegistry(AZ::SettingsRegistryMergeUtils::ActiveGemsRootKey);
  233. CopySettingsToLocalRegistry(AZ::SettingsRegistryMergeUtils::ManifestGemsRootKey);
  234. }
  235. AZ::SettingsRegistryInterface::MergeSettingsResult mergeResult;
  236. mergeResult.Combine(AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_EngineRegistry(registry, platform, specialization, &scratchBuffer));
  237. mergeResult.Combine(AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_GemRegistries(registry, platform, specialization, &scratchBuffer));
  238. mergeResult.Combine(AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, platform, specialization, &scratchBuffer));
  239. // Output any Settings Registry Merge result messages using the info log level if not empty
  240. if (auto& operationMessages = mergeResult.GetMessages();
  241. !operationMessages.empty())
  242. {
  243. [[maybe_unused]] AZStd::string_view launcherString = specialization.GetSpecialization(LauncherTypeIndex);
  244. [[maybe_unused]] AZStd::string_view buildConfiguration = specialization.GetSpecialization(BuildConfigIndex);
  245. AZ_Info("Settings Registry Builder", R"(Launcher Type: "%.*s", Build configuration: "%.*s")" "\n"
  246. "Merging the Engine, Gem, Project Registry directories resulted in the following messages:\n%s\n",
  247. AZ_STRING_ARG(launcherString), AZ_STRING_ARG(buildConfiguration),
  248. operationMessages.c_str());
  249. }
  250. // The Gem Root Key and Manifest Gems Root is removed now that each gems "<gem-root>/Registry" directory
  251. // have been merged to the local Settings Registry
  252. registry.Remove(AZ::SettingsRegistryMergeUtils::ActiveGemsRootKey);
  253. registry.Remove(AZ::SettingsRegistryMergeUtils::ManifestGemsRootKey);
  254. AZ::CommandLine* commandLine{};
  255. AZ::ComponentApplicationBus::Broadcast([&commandLine](AZ::ComponentApplicationRequests* appRequests)
  256. {
  257. commandLine = appRequests->GetAzCommandLine();
  258. });
  259. if (commandLine)
  260. {
  261. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, *commandLine, {});
  262. }
  263. if (AZ::IO::ByteContainerStream outputStream(&outputBuffer);
  264. AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(registry, "", outputStream, dumperSettings))
  265. {
  266. AZStd::string_view specializationString(specialization.GetSpecialization(LauncherTypeIndex));
  267. outputPath.Native() += specializationString; // Append launcher type (client, server, or unified)
  268. specializationString = specialization.GetSpecialization(BuildConfigIndex);
  269. outputPath.Native() += '.';
  270. outputPath.Native() += specializationString; // Append configuration
  271. outputPath.Native() += ".setreg";
  272. AZ::IO::SystemFile file;
  273. if (!file.Open(outputPath.c_str(),
  274. AZ::IO::SystemFile::OpenMode::SF_OPEN_CREATE | AZ::IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
  275. {
  276. AZ_Error("Settings Registry Builder", false, R"(Failed to open file "%s" for writing.)", outputPath.c_str());
  277. return;
  278. }
  279. if (file.Write(outputBuffer.data(), outputBuffer.size()) != outputBuffer.size())
  280. {
  281. AZ_Error("Settings Registry Builder", false, R"(Failed to write settings registry to file "%s".)", outputPath.c_str());
  282. return;
  283. }
  284. file.Close();
  285. // Hash only the launcher type and build config specializations tags
  286. size_t hashedSpecialization{};
  287. // Get the launcher type specialization tag
  288. AZStd::hash_combine(hashedSpecialization, specialization.GetSpecialization(LauncherTypeIndex));
  289. // Get the build config specialization tag
  290. AZStd::hash_combine(hashedSpecialization, specialization.GetSpecialization(BuildConfigIndex));
  291. AZ_Assert(hashedSpecialization != 0, "Product ID generation failed for specialization %.*s."
  292. " This can result in a product ID collision with other builders for this asset.",
  293. AZ_STRING_ARG(specializationString));
  294. auto setregSubId = static_cast<AZ::u32>(hashedSpecialization);
  295. response.m_outputProducts.emplace_back(outputPath.Native(), m_assetType, setregSubId);
  296. response.m_outputProducts.back().m_dependenciesHandled = true;
  297. outputPath.Native().erase(extensionOffset);
  298. }
  299. // Clear the output buffer, to make sure previous loop iterations settings are not being appended
  300. outputBuffer.clear();
  301. }
  302. }
  303. response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
  304. }
  305. AZStd::vector<AZStd::string> SettingsRegistryBuilder::ReadExcludesFromRegistry() const
  306. {
  307. AZStd::vector<AZStd::string> excludes;
  308. auto builderRegistry = AZ::SettingsRegistry::Get();
  309. AZStd::string path = "/Amazon/AssetBuilder/SettingsRegistry/Excludes/";
  310. size_t offset = path.length();
  311. size_t counter = 0;
  312. do
  313. {
  314. path += AZStd::to_string(counter);
  315. AZStd::string exclude;
  316. if (builderRegistry->Get(exclude, path))
  317. {
  318. excludes.push_back(AZStd::move(exclude));
  319. }
  320. else
  321. {
  322. return excludes;
  323. }
  324. counter++;
  325. path.erase(offset);
  326. } while (true);
  327. }
  328. } // namespace AssetProcessor