|
@@ -22,6 +22,8 @@
|
|
#include <AzCore/Settings/SettingsRegistryVisitorUtils.h>
|
|
#include <AzCore/Settings/SettingsRegistryVisitorUtils.h>
|
|
#include <AzCore/std/string/conversions.h>
|
|
#include <AzCore/std/string/conversions.h>
|
|
#include <AzCore/Utils/Utils.h>
|
|
#include <AzCore/Utils/Utils.h>
|
|
|
|
+#include <AzCore/Dependency/Dependency.h>
|
|
|
|
+#include <AzCore/Outcome/Outcome.h>
|
|
|
|
|
|
#include <cinttypes>
|
|
#include <cinttypes>
|
|
#include <locale>
|
|
#include <locale>
|
|
@@ -43,6 +45,11 @@ namespace AZ::Internal
|
|
//! This is /Users/<username>/.o3de/Registry on MacOS = $HOME
|
|
//! This is /Users/<username>/.o3de/Registry on MacOS = $HOME
|
|
static constexpr AZStd::string_view SetregFileProjectRootKey{ "/Amazon/AzCore/Bootstrap/project_path" };
|
|
static constexpr AZStd::string_view SetregFileProjectRootKey{ "/Amazon/AzCore/Bootstrap/project_path" };
|
|
|
|
|
|
|
|
+ //! References the settings key to set the engine path via *.setreg(patch) file
|
|
|
|
+ //! Lowest Priority: Will be overridden by the Engine Scan Up Key value
|
|
|
|
+ //! This setting shouldn't be used be at all - see note on project path root key above
|
|
|
|
+ static constexpr AZStd::string_view SetregFileEngineRootKey{ "/Amazon/AzCore/Bootstrap/engine_path" };
|
|
|
|
+
|
|
//! Represents the settings key storing the value of locating the project path by scanning upwards
|
|
//! Represents the settings key storing the value of locating the project path by scanning upwards
|
|
//! Middle Priority: Overrides any project path set via .setreg(patch) file
|
|
//! Middle Priority: Overrides any project path set via .setreg(patch) file
|
|
//!
|
|
//!
|
|
@@ -50,13 +57,23 @@ namespace AZ::Internal
|
|
//! without the need to specify the --project-path argument.
|
|
//! without the need to specify the --project-path argument.
|
|
static constexpr AZStd::string_view ScanUpProjectRootKey{ "/O3DE/Runtime/Internal/project_root_scan_up_path" };
|
|
static constexpr AZStd::string_view ScanUpProjectRootKey{ "/O3DE/Runtime/Internal/project_root_scan_up_path" };
|
|
|
|
|
|
|
|
+ //! Represents the settings key storing the value of locating the engine path by scanning upwards
|
|
|
|
+ //! Middle Priority: Overrides any engine path set via .setreg(patch) file
|
|
|
|
+ //!
|
|
|
|
+ //! This setting is used when running in an engine-centric workflow to locate the engine root directory
|
|
|
|
+ //! without the need to specify the --engine-path argument.
|
|
|
|
+ static constexpr AZStd::string_view ScanUpEngineRootKey{ "/O3DE/Runtime/Internal/engine_root_scan_up_path" };
|
|
|
|
+
|
|
//! References the settings key where the command line value for the --project-path option would be stored
|
|
//! References the settings key where the command line value for the --project-path option would be stored
|
|
//! Highest Priority: Overrides any project paths specified in the "/Amazon/AzCore/Bootstrap/project_path" key
|
|
//! Highest Priority: Overrides any project paths specified in the "/Amazon/AzCore/Bootstrap/project_path" key
|
|
//! or found via scanning upwards from the nearest executable directory to locate a project.json file
|
|
//! or found via scanning upwards from the nearest executable directory to locate a project.json file
|
|
//!
|
|
//!
|
|
//! This setting should be used when using running an O3DE application from a location where a project.json file
|
|
//! This setting should be used when using running an O3DE application from a location where a project.json file
|
|
//! cannot be found by scanning upwards.
|
|
//! cannot be found by scanning upwards.
|
|
- static constexpr AZStd::string_view CommandLineProjectRootKey{ "/O3DE/Runtime/CommandLine" };
|
|
|
|
|
|
+ static constexpr AZStd::string_view CommandLineKey{ "/O3DE/Runtime/CommandLine" };
|
|
|
|
+
|
|
|
|
+ static constexpr AZStd::string_view CommandLineEngineOptionName{ "engine-path" };
|
|
|
|
+ static constexpr AZStd::string_view CommandLineProjectOptionName{ "project-path" };
|
|
|
|
|
|
static constexpr AZStd::string_view EngineJsonFilename = "engine.json";
|
|
static constexpr AZStd::string_view EngineJsonFilename = "engine.json";
|
|
static constexpr AZStd::string_view GemJsonFilename = "gem.json";
|
|
static constexpr AZStd::string_view GemJsonFilename = "gem.json";
|
|
@@ -65,159 +82,426 @@ namespace AZ::Internal
|
|
|
|
|
|
static constexpr const char* ProductCacheDirectoryName = "Cache";
|
|
static constexpr const char* ProductCacheDirectoryName = "Cache";
|
|
|
|
|
|
- AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject(
|
|
|
|
- SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectJsonPath)
|
|
|
|
|
|
+ AZ::Outcome<void, AZStd::string> MergeEngineAndProjectSettings(
|
|
|
|
+ AZ::SettingsRegistryInterface& settingsRegistry,
|
|
|
|
+ const AZ::IO::FixedMaxPath& engineJsonPath,
|
|
|
|
+ const AZ::IO::FixedMaxPath& projectJsonPath,
|
|
|
|
+ const AZ::IO::FixedMaxPath& projectUserJsonPath = {})
|
|
|
|
+ {
|
|
|
|
+ static constexpr AZStd::string_view InternalProjectJsonPathKey{ "/O3DE/Runtime/Internal/project_json_path" };
|
|
|
|
+
|
|
|
|
+ using namespace AZ::SettingsRegistryMergeUtils;
|
|
|
|
+ constexpr auto format = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
|
|
|
|
+
|
|
|
|
+ if (auto outcome = settingsRegistry.MergeSettingsFile(engineJsonPath.LexicallyNormal().c_str(), format, EngineSettingsRootKey);
|
|
|
|
+ !outcome)
|
|
|
|
+ {
|
|
|
|
+ return outcome;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Check if the currently merged project is the same
|
|
|
|
+ AZ::IO::FixedMaxPath mergedProjectJsonPath;
|
|
|
|
+ if (settingsRegistry.Get(mergedProjectJsonPath.Native(), InternalProjectJsonPathKey); mergedProjectJsonPath == projectJsonPath)
|
|
|
|
+ {
|
|
|
|
+ return AZ::Success();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (auto outcome = settingsRegistry.MergeSettingsFile(projectJsonPath.LexicallyNormal().c_str(), format, ProjectSettingsRootKey);
|
|
|
|
+ !outcome)
|
|
|
|
+ {
|
|
|
|
+ return outcome;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // '<project-root>/user/project.json' file overrides are optional
|
|
|
|
+ if (!projectUserJsonPath.empty())
|
|
|
|
+ {
|
|
|
|
+ settingsRegistry.MergeSettingsFile(projectUserJsonPath.LexicallyNormal().c_str(), format, ProjectSettingsRootKey);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Set the InternalProjectJsonPathKey to avoid loading again
|
|
|
|
+ settingsRegistry.Set(InternalProjectJsonPathKey, projectJsonPath.Native());
|
|
|
|
+
|
|
|
|
+ return AZ::Success();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ AZ::IO::FixedMaxPath GetCommandLineOption(
|
|
|
|
+ AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view optionName)
|
|
|
|
+ {
|
|
|
|
+ using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
|
|
|
+ AZ::IO::FixedMaxPath optionPath;
|
|
|
|
+
|
|
|
|
+ // Parse Command Line
|
|
|
|
+ auto VisitCommandLineOptions = [&optionPath, optionName](const AZ::SettingsRegistryInterface::VisitArgs& visitArgs)
|
|
|
|
+ {
|
|
|
|
+ // Lookup the "/O3DE/Runtime/CommandLine/%u/Option" for each command line parameter to
|
|
|
|
+ // see if the key and value are available, and if they are, retrieve the value.
|
|
|
|
+ auto cmdPathKey = FixedValueString::format("%.*s/Option", AZ_STRING_ARG(visitArgs.m_jsonKeyPath));
|
|
|
|
+ if (FixedValueString cmdOptionName;
|
|
|
|
+ visitArgs.m_registry.Get(cmdOptionName, cmdPathKey) && cmdOptionName == optionName)
|
|
|
|
+ {
|
|
|
|
+ // Updated the existing cmdPathKey to read the value from the command line
|
|
|
|
+ cmdPathKey = FixedValueString::format("%.*s/Value", AZ_STRING_ARG(visitArgs.m_jsonKeyPath));
|
|
|
|
+ visitArgs.m_registry.Get(optionPath.Native(), cmdPathKey);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Continue to visit command line parameters, in case there is a additional matching options
|
|
|
|
+ return AZ::SettingsRegistryInterface::VisitResponse::Skip;
|
|
|
|
+ };
|
|
|
|
+ SettingsRegistryVisitorUtils::VisitArray(settingsRegistry, VisitCommandLineOptions, Internal::CommandLineKey);
|
|
|
|
+
|
|
|
|
+ return optionPath;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ AZ::IO::FixedMaxPath ScanUpRootLocator(AZStd::string_view rootFileToLocate)
|
|
|
|
+ {
|
|
|
|
+ AZ::IO::FixedMaxPath rootCandidate{ AZ::Utils::GetExecutableDirectory() };
|
|
|
|
+
|
|
|
|
+ bool rootPathVisited = false;
|
|
|
|
+ do
|
|
|
|
+ {
|
|
|
|
+ if (AZ::IO::SystemFile::Exists((rootCandidate / rootFileToLocate).c_str()))
|
|
|
|
+ {
|
|
|
|
+ return rootCandidate;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Note for posix filesystems the parent directory of '/' is '/' and for windows
|
|
|
|
+ // the parent directory of 'C:\\' is 'C:\\'
|
|
|
|
+
|
|
|
|
+ // Validate that the parent directory isn't itself, that would imply
|
|
|
|
+ // that it is the filesystem root path
|
|
|
|
+ AZ::IO::PathView parentPath = rootCandidate.ParentPath();
|
|
|
|
+ rootPathVisited = (rootCandidate == parentPath);
|
|
|
|
+ // Recurse upwards one directory
|
|
|
|
+ rootCandidate = AZStd::move(parentPath);
|
|
|
|
+
|
|
|
|
+ } while (!rootPathVisited);
|
|
|
|
+
|
|
|
|
+ return {};
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void SetScanUpRootKey(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view key, AZStd::string_view fileLocator)
|
|
|
|
+ {
|
|
|
|
+ using Type = SettingsRegistryInterface::Type;
|
|
|
|
+ if (settingsRegistry.GetType(key) == Type::NoType)
|
|
|
|
+ {
|
|
|
|
+ // We can scan up from exe directory to find fileLocator file, use that for the root if it exists.
|
|
|
|
+ AZ::IO::FixedMaxPath rootPath = Internal::ScanUpRootLocator(fileLocator);
|
|
|
|
+ if (!rootPath.empty() && rootPath.IsRelative())
|
|
|
|
+ {
|
|
|
|
+ if (auto rootAbsPath = AZ::Utils::ConvertToAbsolutePath(rootPath.Native()); rootAbsPath.has_value())
|
|
|
|
+ {
|
|
|
|
+ rootPath = AZStd::move(*rootAbsPath);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ settingsRegistry.Set(key, rootPath.Native());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ AZ::Outcome<void, AZStd::string> EngineIsCompatible(
|
|
|
|
+ AZ::SettingsRegistryInterface& settingsRegistry,
|
|
|
|
+ const AZ::IO::FixedMaxPath& engineJsonPath,
|
|
|
|
+ const AZ::IO::FixedMaxPath& projectJsonPath,
|
|
|
|
+ const AZ::IO::FixedMaxPath& projectUserJsonPath = {}
|
|
|
|
+ )
|
|
{
|
|
{
|
|
- // projectPath needs to be an absolute path here.
|
|
|
|
|
|
+ using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
|
using namespace AZ::SettingsRegistryMergeUtils;
|
|
using namespace AZ::SettingsRegistryMergeUtils;
|
|
- bool projectJsonMerged = settingsRegistry.MergeSettingsFile(
|
|
|
|
- projectJsonPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ProjectSettingsRootKey);
|
|
|
|
|
|
|
|
- AZ::SettingsRegistryInterface::FixedValueString engineMoniker;
|
|
|
|
- if (projectJsonMerged)
|
|
|
|
|
|
+ if (auto outcome = MergeEngineAndProjectSettings(settingsRegistry, engineJsonPath,
|
|
|
|
+ projectJsonPath, projectUserJsonPath);
|
|
|
|
+ !outcome)
|
|
|
|
+ {
|
|
|
|
+ return outcome;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // In project.json look for the "engine" key.
|
|
|
|
+ FixedValueString projectEngineMoniker;
|
|
|
|
+ const auto projectEngineKey = FixedValueString::format("%s/engine", ProjectSettingsRootKey);
|
|
|
|
+ if (!settingsRegistry.Get(projectEngineMoniker, projectEngineKey) || projectEngineMoniker.empty())
|
|
|
|
+ {
|
|
|
|
+ return AZ::Failure("Could not find an 'engine' key in 'project.json'.\n"
|
|
|
|
+ "Please verify the project is registered with a compatible engine.");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ FixedValueString engineName;
|
|
|
|
+ const auto engineNameKey = FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
|
|
|
|
+ if (!settingsRegistry.Get(engineName, engineNameKey) || engineName.empty())
|
|
|
|
+ {
|
|
|
|
+ return AZ::Failure(AZStd::string::format(
|
|
|
|
+ "Could not find an 'engine_name' key in '%s'.\n"
|
|
|
|
+ "Please verify the 'engine.json' file is not corrupt "
|
|
|
|
+ "and that the engine is installed and registered.",
|
|
|
|
+ engineJsonPath.c_str()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ FixedValueString projectEngineMonikerWithSpecifier = projectEngineMoniker;
|
|
|
|
+
|
|
|
|
+ // Extract dependency specifier from engine moniker
|
|
|
|
+ AZ::Dependency<SemanticVersion::parts_count> engineDependency;
|
|
|
|
+ if (engineDependency.ParseVersions({ projectEngineMoniker.c_str() }) &&
|
|
|
|
+ !engineDependency.GetName().empty())
|
|
{
|
|
{
|
|
- // In project.json look for the "engine" key.
|
|
|
|
- auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine", ProjectSettingsRootKey);
|
|
|
|
- settingsRegistry.Get(engineMoniker, engineMonikerKey);
|
|
|
|
|
|
+ projectEngineMoniker = engineDependency.GetName();
|
|
}
|
|
}
|
|
|
|
|
|
- return engineMoniker;
|
|
|
|
|
|
+ if (projectEngineMoniker != engineName)
|
|
|
|
+ {
|
|
|
|
+ return AZ::Failure(AZStd::string::format(
|
|
|
|
+ "Engine name '%s' in project.json does not match the engine_name '%s' found in '%s'.\n"
|
|
|
|
+ "Please verify the project has been registered to the correct engine.",
|
|
|
|
+ projectEngineMoniker.c_str(), engineName.c_str(), engineJsonPath.c_str()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (engineDependency.GetBounds().empty())
|
|
|
|
+ {
|
|
|
|
+ // There is no version specifier to satisfy
|
|
|
|
+ return AZ::Success();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // If the engine has no version information or is not known incompatible then assume compatible
|
|
|
|
+ const auto engineVersionKey = FixedValueString::format("%s/version", EngineSettingsRootKey);
|
|
|
|
+ if(FixedValueString engineVersionValue; settingsRegistry.Get(engineVersionValue, engineVersionKey))
|
|
|
|
+ {
|
|
|
|
+ using SemanticSpecifier = AZ::Specifier<AZ::SemanticVersion::parts_count>;
|
|
|
|
+ AZ::SemanticVersion engineVersion;
|
|
|
|
+ if (auto parseOutcome = AZ::SemanticVersion::ParseFromString(engineVersionValue.c_str()); parseOutcome)
|
|
|
|
+ {
|
|
|
|
+ engineVersion = parseOutcome.TakeValue();
|
|
|
|
+ if(!engineDependency.IsFullfilledBy(SemanticSpecifier(AZ::Uuid::CreateNull(), engineVersion)))
|
|
|
|
+ {
|
|
|
|
+ return AZ::Failure(AZStd::string::format(
|
|
|
|
+ "Engine version '%s' in '%s' does not satisfy the project.json engine constraints '%s'.\n"
|
|
|
|
+ "Please verify you have a compatible engine installed and that the project is registered "
|
|
|
|
+ " to the correct engine.",
|
|
|
|
+ engineVersionValue.c_str(), engineJsonPath.c_str(), projectEngineMonikerWithSpecifier.c_str()));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return AZ::Success();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ AZ::Outcome<AZ::IO::FixedMaxPath, AZStd::string> ReconcileEngineRootFromProjectUserPath(
|
|
|
|
+ SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
|
|
|
|
+ {
|
|
|
|
+ using namespace AZ::SettingsRegistryMergeUtils;
|
|
|
|
+ using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
|
|
|
+ constexpr auto format = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
|
|
|
|
+ AZ::IO::FixedMaxPath engineRoot{};
|
|
|
|
+
|
|
|
|
+ AZ::IO::FixedMaxPath projectUserPath;
|
|
|
|
+ if (!settingsRegistry.Get(projectUserPath.Native(), FilePathKey_ProjectUserPath) ||
|
|
|
|
+ projectUserPath.empty())
|
|
|
|
+ {
|
|
|
|
+ return AZ::Success(engineRoot);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const auto projectUserJsonPath = (projectUserPath / Internal::ProjectJsonFilename).LexicallyNormal();
|
|
|
|
+ if (auto outcome = settingsRegistry.MergeSettingsFile(projectUserJsonPath.c_str(), format, ProjectSettingsRootKey);
|
|
|
|
+ !outcome)
|
|
|
|
+ {
|
|
|
|
+ return AZ::Success(engineRoot);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ settingsRegistry.Get(engineRoot.Native(), FixedValueString::format("%s/engine_path", ProjectSettingsRootKey));
|
|
|
|
+ if (!engineRoot.empty())
|
|
|
|
+ {
|
|
|
|
+ if (engineRoot.IsRelative())
|
|
|
|
+ {
|
|
|
|
+ if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native());
|
|
|
|
+ engineRootAbsPath.has_value())
|
|
|
|
+ {
|
|
|
|
+ engineRoot = AZStd::move(*engineRootAbsPath);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ AZ::IO::FixedMaxPath engineJsonPath{ engineRoot / Internal::EngineJsonFilename };
|
|
|
|
+ AZ::IO::FixedMaxPath projectJsonPath{ projectPath / Internal::ProjectJsonFilename };
|
|
|
|
+ if (auto isCompatible = Internal::EngineIsCompatible(settingsRegistry, engineJsonPath,
|
|
|
|
+ projectJsonPath, projectUserJsonPath);
|
|
|
|
+ !isCompatible)
|
|
|
|
+ {
|
|
|
|
+ return AZ::Failure(isCompatible.GetError());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return AZ::Success(engineRoot);
|
|
}
|
|
}
|
|
|
|
|
|
AZ::IO::FixedMaxPath ReconcileEngineRootFromProjectPath(SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
|
|
AZ::IO::FixedMaxPath ReconcileEngineRootFromProjectPath(SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
|
|
{
|
|
{
|
|
- // Find the engine root via the engine manifest file and project.json
|
|
|
|
|
|
+ // Find the engine root via '<project-root>/user/project.json', engine manifest and project.json
|
|
// Locate the engine manifest file and merge it to settings registry.
|
|
// Locate the engine manifest file and merge it to settings registry.
|
|
// Visit over the engine paths list and merge the engine.json files to settings registry.
|
|
// Visit over the engine paths list and merge the engine.json files to settings registry.
|
|
- // Merge project.json to settings registry. That will give us an "engine" key.
|
|
|
|
- // When we find a match for "engine_name" value against the "engine" value from before, we can stop and use that engine root.
|
|
|
|
- // Finally set the BootstrapSettingsRootKey/engine_path setting so that subsequent calls to GetEngineRoot will use that
|
|
|
|
- // and avoid all this logic.
|
|
|
|
|
|
+ // Merge project.json to settings registry. The "engine" key contains the engine name and optional version specifier.
|
|
|
|
+ // When we find a match for "engine_name" value against the "engine" we check if the engine "version" is compatible
|
|
|
|
+ // with any version specifier in the project's "engine" key.
|
|
|
|
+ // If the engine is compatible we check if it is more compatible than the previously found most compatible engine.
|
|
|
|
+ // Finally, merge in the engine and project settings for the most compatible engine into the registry and
|
|
|
|
+ // return the path of the most compatible engine
|
|
|
|
|
|
using namespace AZ::SettingsRegistryMergeUtils;
|
|
using namespace AZ::SettingsRegistryMergeUtils;
|
|
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
|
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
|
|
|
|
|
AZ::IO::FixedMaxPath engineRoot;
|
|
AZ::IO::FixedMaxPath engineRoot;
|
|
- if (auto o3deManifestPath = AZ::Utils::GetO3deManifestPath(); !o3deManifestPath.empty())
|
|
|
|
|
|
+ if (auto o3deManifestPath = AZ::Utils::GetO3deManifestPath(&settingsRegistry); !o3deManifestPath.empty())
|
|
{
|
|
{
|
|
- bool manifestLoaded{false};
|
|
|
|
-
|
|
|
|
- if (AZ::IO::SystemFile::Exists(o3deManifestPath.c_str()))
|
|
|
|
- {
|
|
|
|
- manifestLoaded = settingsRegistry.MergeSettingsFile(
|
|
|
|
- o3deManifestPath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, O3deManifestSettingsRootKey);
|
|
|
|
- }
|
|
|
|
|
|
+ const auto manifestLoaded = settingsRegistry.MergeSettingsFile(o3deManifestPath,
|
|
|
|
+ AZ::SettingsRegistryInterface::Format::JsonMergePatch, O3deManifestSettingsRootKey);
|
|
|
|
|
|
struct EngineInfo
|
|
struct EngineInfo
|
|
{
|
|
{
|
|
AZ::IO::FixedMaxPath m_path;
|
|
AZ::IO::FixedMaxPath m_path;
|
|
- FixedValueString m_moniker;
|
|
|
|
|
|
+ FixedValueString m_name;
|
|
|
|
+ AZ::SemanticVersion m_version;
|
|
};
|
|
};
|
|
|
|
|
|
- struct EnginePathsVisitor : public AZ::SettingsRegistryInterface::Visitor
|
|
|
|
|
|
+ AZStd::set<AZ::IO::FixedMaxPath> missingProjectJsonPaths;
|
|
|
|
+ AZStd::vector<EngineInfo> searchedEngineInfo;
|
|
|
|
+
|
|
|
|
+ if (manifestLoaded)
|
|
{
|
|
{
|
|
- using AZ::SettingsRegistryInterface::Visitor::Visit;
|
|
|
|
- void Visit(
|
|
|
|
- const AZ::SettingsRegistryInterface::VisitArgs& visitArgs, AZStd::string_view value) override
|
|
|
|
- {
|
|
|
|
- m_enginePaths.emplace_back(EngineInfo{ AZ::IO::FixedMaxPath{value}.LexicallyNormal(), FixedValueString{visitArgs.m_fieldName} });
|
|
|
|
- // Make sure any engine paths read from the manifest are absolute
|
|
|
|
- AZ::IO::FixedMaxPath& recentEnginePath = m_enginePaths.back().m_path;
|
|
|
|
- if (recentEnginePath.IsRelative())
|
|
|
|
|
|
+ const auto engineVersionKey = FixedValueString::format("%s/version", EngineSettingsRootKey);
|
|
|
|
+ const auto engineNameKey = FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
|
|
|
|
+ const auto enginesKey = FixedValueString::format("%s/engines", O3deManifestSettingsRootKey);
|
|
|
|
+
|
|
|
|
+ // Avoid modifying the SettingsRegistry while visiting which may invalidate iterators and cause crashes
|
|
|
|
+ AZ::SettingsRegistryVisitorUtils::VisitArray(settingsRegistry,
|
|
|
|
+ [&](const AZ::SettingsRegistryInterface::VisitArgs& visitArgs)
|
|
{
|
|
{
|
|
- if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(recentEnginePath.Native());
|
|
|
|
- engineRootAbsPath.has_value())
|
|
|
|
|
|
+ EngineInfo engineInfo;
|
|
|
|
+ visitArgs.m_registry.Get(engineInfo.m_path.Native(), visitArgs.m_jsonKeyPath);
|
|
|
|
+ if (engineInfo.m_path.IsRelative())
|
|
{
|
|
{
|
|
- recentEnginePath = AZStd::move(*engineRootAbsPath);
|
|
|
|
|
|
+ if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineInfo.m_path.Native());
|
|
|
|
+ engineRootAbsPath.has_value())
|
|
|
|
+ {
|
|
|
|
+ engineInfo.m_path = AZStd::move(*engineRootAbsPath);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ searchedEngineInfo.emplace_back(engineInfo);
|
|
|
|
+ return AZ::SettingsRegistryInterface::VisitResponse::Continue;
|
|
|
|
+ },
|
|
|
|
+ enginesKey);
|
|
|
|
+
|
|
|
|
+ EngineInfo mostCompatibleEngineInfo;
|
|
|
|
+ AZ::IO::FixedMaxPath projectUserPath;
|
|
|
|
+ settingsRegistry.Get(projectUserPath.Native(), FilePathKey_ProjectUserPath);
|
|
|
|
+
|
|
|
|
+ AZ::SettingsRegistryImpl scratchSettingsRegistry;
|
|
|
|
+
|
|
|
|
+ // Look through the manifest engines for the most compatible engine
|
|
|
|
+ for (auto& engineInfo : searchedEngineInfo)
|
|
|
|
+ {
|
|
|
|
+ AZ::IO::FixedMaxPath projectJsonPath{projectPath / ProjectJsonFilename};
|
|
|
|
+ AZ::IO::FixedMaxPath projectUserJsonPath{projectUserPath / ProjectJsonFilename};
|
|
|
|
+ AZ::IO::FixedMaxPath engineJsonPath{engineInfo.m_path / EngineJsonFilename};
|
|
|
|
|
|
- AZStd::vector<EngineInfo> m_enginePaths{};
|
|
|
|
- };
|
|
|
|
|
|
+ auto isCompatible = Internal::EngineIsCompatible(scratchSettingsRegistry, engineJsonPath, projectJsonPath, projectUserJsonPath);
|
|
|
|
|
|
- EnginePathsVisitor pathVisitor;
|
|
|
|
- if (manifestLoaded)
|
|
|
|
- {
|
|
|
|
- auto enginePathsKey = FixedValueString::format("%s/engines_path", O3deManifestSettingsRootKey);
|
|
|
|
- settingsRegistry.Visit(pathVisitor, enginePathsKey);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const auto engineMonikerKey = FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
|
|
|
|
|
|
+ // get the engine name and version
|
|
|
|
+ scratchSettingsRegistry.Get(engineInfo.m_name, engineNameKey);
|
|
|
|
|
|
- AZStd::set<AZ::IO::FixedMaxPath> projectPathsNotFound;
|
|
|
|
|
|
+ FixedValueString engineVersion;
|
|
|
|
+ if (scratchSettingsRegistry.Get(engineVersion, engineVersionKey); !engineVersion.empty())
|
|
|
|
+ {
|
|
|
|
+ if (auto parseOutcome = AZ::SemanticVersion::ParseFromString(engineVersion.c_str());
|
|
|
|
+ parseOutcome)
|
|
|
|
+ {
|
|
|
|
+ engineInfo.m_version = parseOutcome.TakeValue();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- for (EngineInfo& engineInfo : pathVisitor.m_enginePaths)
|
|
|
|
- {
|
|
|
|
- if (auto engineSettingsPath = AZ::IO::FixedMaxPath{engineInfo.m_path} / EngineJsonFilename;
|
|
|
|
- AZ::IO::SystemFile::Exists(engineSettingsPath.c_str()))
|
|
|
|
- {
|
|
|
|
- if (settingsRegistry.MergeSettingsFile(
|
|
|
|
- engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey))
|
|
|
|
|
|
+ if (isCompatible)
|
|
{
|
|
{
|
|
- FixedValueString engineName;
|
|
|
|
- settingsRegistry.Get(engineName, engineMonikerKey);
|
|
|
|
- AZ_Warning("SettingsRegistryMergeUtils", engineInfo.m_moniker == engineName,
|
|
|
|
- R"(The engine name key "%s" mapped to engine path "%s" within the global manifest of "%s")"
|
|
|
|
- R"( does not match the "engine_name" field "%s" in the engine.json)" "\n"
|
|
|
|
- "This engine should be re-registered.",
|
|
|
|
- engineInfo.m_moniker.c_str(), engineInfo.m_path.c_str(), o3deManifestPath.c_str(),
|
|
|
|
- engineName.c_str());
|
|
|
|
- engineInfo.m_moniker = engineName;
|
|
|
|
|
|
+ if (mostCompatibleEngineInfo.m_path.empty())
|
|
|
|
+ {
|
|
|
|
+ mostCompatibleEngineInfo = engineInfo;
|
|
|
|
+ }
|
|
|
|
+ else if (!engineInfo.m_version.IsZero())
|
|
|
|
+ {
|
|
|
|
+ AZ_Warning("SettingsRegistryMergeUtils", engineInfo.m_version != mostCompatibleEngineInfo.m_version,
|
|
|
|
+ "Not using the engine at '%s' because another engine with the same name '%s' and version '%s' was already found at '%s'",
|
|
|
|
+ engineInfo.m_path.c_str(), engineInfo.m_name.c_str(), engineInfo.m_version.ToString().c_str(),
|
|
|
|
+ mostCompatibleEngineInfo.m_path.c_str());
|
|
|
|
+
|
|
|
|
+ // is it more compatible?
|
|
|
|
+ if (engineInfo.m_version > mostCompatibleEngineInfo.m_version)
|
|
|
|
+ {
|
|
|
|
+ mostCompatibleEngineInfo = engineInfo;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- if (auto projectJsonPath = (engineInfo.m_path / projectPath / ProjectJsonFilename).LexicallyNormal();
|
|
|
|
- AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
|
|
|
|
- {
|
|
|
|
- if (auto engineMoniker = Internal::GetEngineMonikerForProject(settingsRegistry, projectJsonPath);
|
|
|
|
- !engineMoniker.empty() && engineMoniker == engineInfo.m_moniker)
|
|
|
|
|
|
+ // Remove engine settings which will be different for each engine but keep the
|
|
|
|
+ // project settings (ProjectSettingsRootKey) in case the project path is the
|
|
|
|
+ // same to avoid re-loading them.
|
|
|
|
+ scratchSettingsRegistry.Remove(EngineSettingsRootKey);
|
|
|
|
+
|
|
|
|
+ if (!AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
|
|
{
|
|
{
|
|
- engineRoot = engineInfo.m_path;
|
|
|
|
- break;
|
|
|
|
|
|
+ // add the project path where we looked for 'project.json'
|
|
|
|
+ missingProjectJsonPaths.insert((projectPath).LexicallyNormal());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+
|
|
|
|
+ if (!mostCompatibleEngineInfo.m_path.empty())
|
|
{
|
|
{
|
|
- projectPathsNotFound.insert(projectJsonPath);
|
|
|
|
|
|
+ engineRoot = mostCompatibleEngineInfo.m_path;
|
|
|
|
+
|
|
|
|
+ // merge in the engine and project settings with overrides
|
|
|
|
+ Internal::MergeEngineAndProjectSettings(settingsRegistry,
|
|
|
|
+ (engineRoot / EngineJsonFilename),
|
|
|
|
+ (projectPath / ProjectJsonFilename),
|
|
|
|
+ (engineRoot / projectUserPath / ProjectJsonFilename)
|
|
|
|
+ );
|
|
}
|
|
}
|
|
-
|
|
|
|
- // Continue looking for candidates, remove the previous engine and project settings that were merged above.
|
|
|
|
- settingsRegistry.Remove(ProjectSettingsRootKey);
|
|
|
|
- settingsRegistry.Remove(EngineSettingsRootKey);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if (engineRoot.empty())
|
|
if (engineRoot.empty())
|
|
{
|
|
{
|
|
AZStd::string errorStr;
|
|
AZStd::string errorStr;
|
|
- if (!projectPathsNotFound.empty())
|
|
|
|
|
|
+ if (!missingProjectJsonPaths.empty())
|
|
{
|
|
{
|
|
// This case is usually encountered when a project path is given as a relative path,
|
|
// This case is usually encountered when a project path is given as a relative path,
|
|
// which is assumed to be relative to an engine root.
|
|
// which is assumed to be relative to an engine root.
|
|
// When no project.json files are found this way, dump this error message about
|
|
// When no project.json files are found this way, dump this error message about
|
|
// which project paths were checked.
|
|
// which project paths were checked.
|
|
AZStd::string projectPathsTested;
|
|
AZStd::string projectPathsTested;
|
|
- for (const auto& path : projectPathsNotFound)
|
|
|
|
|
|
+ for (const auto& path : missingProjectJsonPaths)
|
|
{
|
|
{
|
|
projectPathsTested.append(AZStd::string::format(" %s\n", path.c_str()));
|
|
projectPathsTested.append(AZStd::string::format(" %s\n", path.c_str()));
|
|
}
|
|
}
|
|
- errorStr = AZStd::string::format("No valid project was found at these locations:\n%s"
|
|
|
|
|
|
+ errorStr = AZStd::string::format(
|
|
|
|
+ "No valid project was found (missing 'project.json') at these locations:\n%s"
|
|
"Please supply a valid --project-path to the application.",
|
|
"Please supply a valid --project-path to the application.",
|
|
projectPathsTested.c_str());
|
|
projectPathsTested.c_str());
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
|
|
+ FixedValueString projectEngineMoniker;
|
|
|
|
+ const auto projectEngineKey = FixedValueString::format("%s/engine", ProjectSettingsRootKey);
|
|
|
|
+ if (!settingsRegistry.Get(projectEngineMoniker, projectEngineKey) || projectEngineMoniker.empty())
|
|
|
|
+ {
|
|
|
|
+ projectEngineMoniker = "missing";
|
|
|
|
+ }
|
|
|
|
+
|
|
// The other case is that a project.json was found, but after checking all the registered engines
|
|
// The other case is that a project.json was found, but after checking all the registered engines
|
|
// none of them matched the engine moniker.
|
|
// none of them matched the engine moniker.
|
|
AZStd::string enginePathsChecked;
|
|
AZStd::string enginePathsChecked;
|
|
- for (const auto& engineInfo : pathVisitor.m_enginePaths)
|
|
|
|
|
|
+ for (const auto& engineInfo : searchedEngineInfo)
|
|
{
|
|
{
|
|
- enginePathsChecked.append(AZStd::string::format(" %s (%s)\n", engineInfo.m_path.c_str(), engineInfo.m_moniker.c_str()));
|
|
|
|
|
|
+ enginePathsChecked.append(AZStd::string::format(" %s (%s %s)\n", engineInfo.m_path.c_str(),
|
|
|
|
+ engineInfo.m_name.c_str(), engineInfo.m_version.ToString().c_str()));
|
|
}
|
|
}
|
|
errorStr = AZStd::string::format(
|
|
errorStr = AZStd::string::format(
|
|
- "No engine was found in o3de_manifest.json with a name that matches the one set in the project.json.\n"
|
|
|
|
- "Engines that were checked:\n%s"
|
|
|
|
- "Please check that your engine and project have both been registered with scripts/o3de.py.", enginePathsChecked.c_str()
|
|
|
|
|
|
+ "No engine was found in o3de_manifest.json that is compatible with the one set in the project.json '%s'\n"
|
|
|
|
+ "If that's not the engine qualifier you expected, please check if the engine field is being overridden in 'user/project.json'.\n\n"
|
|
|
|
+ "Engines that were checked:\n%s\n"
|
|
|
|
+ "Please check that your engine and project have both been registered with scripts/o3de.py.",
|
|
|
|
+ projectEngineMoniker.c_str(), enginePathsChecked.c_str()
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -227,56 +511,6 @@ namespace AZ::Internal
|
|
|
|
|
|
return engineRoot;
|
|
return engineRoot;
|
|
}
|
|
}
|
|
-
|
|
|
|
- AZ::IO::FixedMaxPath ScanUpRootLocator(AZStd::string_view rootFileToLocate)
|
|
|
|
- {
|
|
|
|
- AZ::IO::FixedMaxPath rootCandidate{ AZ::Utils::GetExecutableDirectory() };
|
|
|
|
-
|
|
|
|
- bool rootPathVisited = false;
|
|
|
|
- do
|
|
|
|
- {
|
|
|
|
- if (AZ::IO::SystemFile::Exists((rootCandidate / rootFileToLocate).c_str()))
|
|
|
|
- {
|
|
|
|
- return rootCandidate;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Note for posix filesystems the parent directory of '/' is '/' and for windows
|
|
|
|
- // the parent directory of 'C:\\' is 'C:\\'
|
|
|
|
-
|
|
|
|
- // Validate that the parent directory isn't itself, that would imply
|
|
|
|
- // that it is the filesystem root path
|
|
|
|
- AZ::IO::PathView parentPath = rootCandidate.ParentPath();
|
|
|
|
- rootPathVisited = (rootCandidate == parentPath);
|
|
|
|
- // Recurse upwards one directory
|
|
|
|
- rootCandidate = AZStd::move(parentPath);
|
|
|
|
-
|
|
|
|
- } while (!rootPathVisited);
|
|
|
|
-
|
|
|
|
- return {};
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- enum class InjectLocation : bool
|
|
|
|
- {
|
|
|
|
- Front,
|
|
|
|
- Back
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- void InjectSettingToCommandLine(AZ::SettingsRegistryInterface& settingsRegistry,
|
|
|
|
- AZStd::string_view path, AZStd::string_view value,
|
|
|
|
- InjectLocation injectLocation = InjectLocation::Front)
|
|
|
|
- {
|
|
|
|
- AZ::CommandLine commandLine;
|
|
|
|
- AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(settingsRegistry, commandLine);
|
|
|
|
- AZ::CommandLine::ParamContainer paramContainer;
|
|
|
|
- commandLine.Dump(paramContainer);
|
|
|
|
-
|
|
|
|
- auto projectPathOverride = AZStd::string::format(R"(--regset="%.*s=%.*s")",
|
|
|
|
- aznumeric_cast<int>(path.size()), path.data(), aznumeric_cast<int>(value.size()), value.data());
|
|
|
|
- auto emplaceIter = injectLocation == InjectLocation::Front ? paramContainer.begin() : paramContainer.end();
|
|
|
|
- paramContainer.emplace(emplaceIter, AZStd::move(projectPathOverride));
|
|
|
|
- commandLine.Parse(paramContainer);
|
|
|
|
- AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(settingsRegistry, commandLine);
|
|
|
|
- }
|
|
|
|
} // namespace AZ::Internal
|
|
} // namespace AZ::Internal
|
|
|
|
|
|
namespace AZ::SettingsRegistryMergeUtils
|
|
namespace AZ::SettingsRegistryMergeUtils
|
|
@@ -313,139 +547,83 @@ namespace AZ::SettingsRegistryMergeUtils
|
|
|
|
|
|
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry)
|
|
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry)
|
|
{
|
|
{
|
|
- static constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Runtime/Internal/engine_root_scan_up_path" };
|
|
|
|
- using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
|
|
|
- using Type = SettingsRegistryInterface::Type;
|
|
|
|
-
|
|
|
|
- AZ::IO::FixedMaxPath engineRoot;
|
|
|
|
- // This is the 'external' engine root key, as in passed from command-line or .setreg files.
|
|
|
|
- constexpr auto engineRootKey = FixedValueString(BootstrapSettingsRootKey) + "/engine_path";
|
|
|
|
-
|
|
|
|
// Step 1 Run the scan upwards logic once to find the location of the engine.json if it exist
|
|
// Step 1 Run the scan upwards logic once to find the location of the engine.json if it exist
|
|
// Once this step is run the {InternalScanUpEngineRootKey} is set in the Settings Registry
|
|
// Once this step is run the {InternalScanUpEngineRootKey} is set in the Settings Registry
|
|
- // to have this scan logic only run once InternalScanUpEngineRootKey the supplied registry
|
|
|
|
- if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == Type::NoType)
|
|
|
|
|
|
+ // and this logic will not run again for this Settings Registry instance
|
|
|
|
+ Internal::SetScanUpRootKey(settingsRegistry, Internal::ScanUpEngineRootKey, Internal::EngineJsonFilename);
|
|
|
|
+
|
|
|
|
+ // Check for the engine path to use in priority of
|
|
|
|
+ // 1. command line
|
|
|
|
+ // 2. <project-root>/user/project.json "engine_path"
|
|
|
|
+ // 3. first compatible engine based on project.json "engine"
|
|
|
|
+ // 4. First engine.json found by scanning upwards from the current executable directory
|
|
|
|
+ // 5. Bootstrap engine_path from .setreg file
|
|
|
|
+ AZ::IO::FixedMaxPath engineRoot = Internal::GetCommandLineOption(settingsRegistry, Internal::CommandLineEngineOptionName);
|
|
|
|
+
|
|
|
|
+ // Note: the projectRoot should be absolute because of FindProjectRoot
|
|
|
|
+ AZ::IO::FixedMaxPath projectRoot;
|
|
|
|
+ settingsRegistry.Get(projectRoot.Native(), FilePathKey_ProjectPath);
|
|
|
|
+
|
|
|
|
+ if (engineRoot.empty() && !projectRoot.empty())
|
|
{
|
|
{
|
|
- // We can scan up from exe directory to find engine.json, use that for engine root if it exists.
|
|
|
|
- engineRoot = Internal::ScanUpRootLocator(Internal::EngineJsonFilename);
|
|
|
|
- // The Internal ScanUp Engine Root Key will be set as an absolute path
|
|
|
|
- if (!engineRoot.empty())
|
|
|
|
|
|
+ // Step 2 Check for alternate 'engine_path' setting in '<project-root>/user/project.json'
|
|
|
|
+ if (auto outcome = Internal::ReconcileEngineRootFromProjectUserPath(settingsRegistry, projectRoot); !outcome)
|
|
{
|
|
{
|
|
- if (engineRoot.IsRelative())
|
|
|
|
- {
|
|
|
|
- if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native());
|
|
|
|
- engineRootAbsPath.has_value())
|
|
|
|
- {
|
|
|
|
- engineRoot = AZStd::move(*engineRootAbsPath);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ // An error occurred that needs to be shown the the user, possibly an invalid engine name or path
|
|
|
|
+ settingsRegistry.Set(FilePathKey_ErrorText, outcome.GetError().c_str());
|
|
|
|
+ return {};
|
|
}
|
|
}
|
|
-
|
|
|
|
- // Set the {InternalScanUpEngineRootKey} to make sure this code path isn't called again for this settings registry
|
|
|
|
- settingsRegistry.Set(InternalScanUpEngineRootKey, engineRoot.Native());
|
|
|
|
- if (!engineRoot.empty())
|
|
|
|
|
|
+ else
|
|
{
|
|
{
|
|
- settingsRegistry.Set(engineRootKey, engineRoot.Native());
|
|
|
|
- // Inject the engine root to the front of the command line settings
|
|
|
|
- Internal::InjectSettingToCommandLine(settingsRegistry, engineRootKey, engineRoot.Native());
|
|
|
|
- return engineRoot;
|
|
|
|
|
|
+ engineRoot = outcome.TakeValue();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Step 2 check if the engine_path key has been supplied
|
|
|
|
- if (settingsRegistry.Get(engineRoot.Native(), engineRootKey); !engineRoot.empty())
|
|
|
|
|
|
+ if (engineRoot.empty() && !projectRoot.empty())
|
|
{
|
|
{
|
|
- if (engineRoot.IsRelative())
|
|
|
|
- {
|
|
|
|
- if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native());
|
|
|
|
- engineRootAbsPath.has_value())
|
|
|
|
- {
|
|
|
|
- engineRoot = AZStd::move(*engineRootAbsPath);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return engineRoot;
|
|
|
|
|
|
+ // 3. Locate the project root and attempt to find the most compatible engine
|
|
|
|
+ // using the engine name and optional version in project.json
|
|
|
|
+ engineRoot = Internal::ReconcileEngineRootFromProjectPath(settingsRegistry, projectRoot);
|
|
}
|
|
}
|
|
|
|
|
|
- // Step 3 locate the project root and attempt to find the engine root using the registered engine
|
|
|
|
- // for the project in the project.json file
|
|
|
|
- AZ::IO::FixedMaxPath projectRoot;
|
|
|
|
- settingsRegistry.Get(projectRoot.Native(), FilePathKey_ProjectPath);
|
|
|
|
- if (projectRoot.empty())
|
|
|
|
|
|
+ if (engineRoot.empty())
|
|
{
|
|
{
|
|
- return {};
|
|
|
|
|
|
+ // 3. Use the engine scan up result
|
|
|
|
+ settingsRegistry.Get(engineRoot.Native(), Internal::ScanUpEngineRootKey);
|
|
}
|
|
}
|
|
|
|
|
|
- // Use the project.json and engine manifest to locate the engine root.
|
|
|
|
- if (engineRoot = Internal::ReconcileEngineRootFromProjectPath(settingsRegistry, projectRoot); !engineRoot.empty())
|
|
|
|
|
|
+ if (engineRoot.empty())
|
|
{
|
|
{
|
|
- settingsRegistry.Set(engineRootKey, engineRoot.c_str());
|
|
|
|
- return engineRoot;
|
|
|
|
|
|
+ // 4. Use the bootstrap setting
|
|
|
|
+ settingsRegistry.Get(engineRoot.Native(), Internal::SetregFileEngineRootKey);
|
|
}
|
|
}
|
|
|
|
|
|
- // Fall back to using the project root as the engine root if the engine path could not be reconciled
|
|
|
|
- // by checking the project.json "engine" string within o3de_manifest.json "engine_paths" object
|
|
|
|
- return projectRoot;
|
|
|
|
|
|
+ // Make the engine root an absolute path if it is not empty
|
|
|
|
+ if (!engineRoot.empty() && engineRoot.IsRelative())
|
|
|
|
+ {
|
|
|
|
+ if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native());
|
|
|
|
+ engineRootAbsPath.has_value())
|
|
|
|
+ {
|
|
|
|
+ engineRoot = AZStd::move(*engineRootAbsPath);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return engineRoot;
|
|
}
|
|
}
|
|
|
|
|
|
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry)
|
|
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry)
|
|
{
|
|
{
|
|
- using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
|
|
|
- using Type = SettingsRegistryInterface::Type;
|
|
|
|
-
|
|
|
|
// Run the scan upwards logic one time only for the supplied Settings Registry instance
|
|
// Run the scan upwards logic one time only for the supplied Settings Registry instance
|
|
// to find the location of the closest ancestor project.json
|
|
// to find the location of the closest ancestor project.json
|
|
- // Once this step is run the {SetregFileProjectRootKey} is set in the Settings Registry
|
|
|
|
|
|
+ // Once this step is run the {ScanUpProjectRootKey} is set in the Settings Registry
|
|
// and this logic will not run again for this Settings Registry instance
|
|
// and this logic will not run again for this Settings Registry instance
|
|
|
|
+ Internal::SetScanUpRootKey(settingsRegistry, Internal::ScanUpProjectRootKey, Internal::ProjectJsonFilename);
|
|
|
|
|
|
- // SettingsRegistryInterface::GetType is used to check if a key is set
|
|
|
|
- if (settingsRegistry.GetType(Internal::SetregFileProjectRootKey) == Type::NoType)
|
|
|
|
- {
|
|
|
|
- AZ::IO::FixedMaxPath scanUpProjectRoot = Internal::ScanUpRootLocator(Internal::ProjectJsonFilename);
|
|
|
|
- // Convert the path to an absolute path before adding it as a setting to the
|
|
|
|
- // InternalScanUpProjectRootKey
|
|
|
|
- if (!scanUpProjectRoot.empty())
|
|
|
|
- {
|
|
|
|
- if (scanUpProjectRoot.IsRelative())
|
|
|
|
- {
|
|
|
|
- if (auto projectAbsPath = AZ::Utils::ConvertToAbsolutePath(scanUpProjectRoot.Native());
|
|
|
|
- projectAbsPath.has_value())
|
|
|
|
- {
|
|
|
|
- scanUpProjectRoot = AZStd::move(*projectAbsPath);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Set the {SetregFileProjectRootKey} to make sure this code path isn't called again for this instance
|
|
|
|
- settingsRegistry.Set(Internal::ScanUpProjectRootKey, scanUpProjectRoot.Native());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Check for the project path to used in priority of
|
|
|
|
|
|
+ // Check for the project path to use in priority of
|
|
// 1. command-line
|
|
// 1. command-line
|
|
// 2. First project.json found by scanning upwards from the current executable directory
|
|
// 2. First project.json found by scanning upwards from the current executable directory
|
|
// 3. "/Amazon/AzCore/Bootstrap/project_path" key set in .setreg file
|
|
// 3. "/Amazon/AzCore/Bootstrap/project_path" key set in .setreg file
|
|
-
|
|
|
|
- AZ::IO::FixedMaxPath projectRoot;
|
|
|
|
-
|
|
|
|
- // 1. Parse Command Line
|
|
|
|
- auto VisitCommandLineOptions = [&projectRoot](const AZ::SettingsRegistryInterface::VisitArgs& visitArgs)
|
|
|
|
- {
|
|
|
|
- constexpr AZStd::string_view ProjectPathOptionName = "project-path";
|
|
|
|
- // Lookup the "/O3DE/Runtime/CommandLine/%u/Option" for each command line parameter to see if
|
|
|
|
- // the project-path key is available
|
|
|
|
- // Check if the option value --project-path has been found key has been found
|
|
|
|
- auto cmdProjectPathKey = FixedValueString::format("%.*s/Option", AZ_STRING_ARG(visitArgs.m_jsonKeyPath));
|
|
|
|
- if (FixedValueString optionName;
|
|
|
|
- visitArgs.m_registry.Get(optionName, cmdProjectPathKey) && optionName == ProjectPathOptionName)
|
|
|
|
- {
|
|
|
|
- // Updated the existing cmdProjectPathKey to read the value from the command line
|
|
|
|
- cmdProjectPathKey = FixedValueString::format("%.*s/Value", AZ_STRING_ARG(visitArgs.m_jsonKeyPath));
|
|
|
|
- visitArgs.m_registry.Get(projectRoot.Native(), cmdProjectPathKey);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Continue to visit command line parmaetes, in case there is a second --project-path option
|
|
|
|
- return AZ::SettingsRegistryInterface::VisitResponse::Skip;
|
|
|
|
- };
|
|
|
|
- SettingsRegistryVisitorUtils::VisitArray(settingsRegistry, VisitCommandLineOptions, Internal::CommandLineProjectRootKey);
|
|
|
|
|
|
+ AZ::IO::FixedMaxPath projectRoot = Internal::GetCommandLineOption(settingsRegistry, Internal::CommandLineProjectOptionName);
|
|
|
|
|
|
if (projectRoot.empty())
|
|
if (projectRoot.empty())
|
|
{
|
|
{
|
|
@@ -828,22 +1006,35 @@ namespace AZ::SettingsRegistryMergeUtils
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- AZ_TracePrintf("SettingsRegistryMergeUtils",
|
|
|
|
- R"(Project path isn't set in the Settings Registry at "%.*s".)"
|
|
|
|
- " Project-related filepaths will be set relative to the executable directory\n",
|
|
|
|
- AZ_STRING_ARG(projectPathKey));
|
|
|
|
projectPath = exePath;
|
|
projectPath = exePath;
|
|
registry.Set(FilePathKey_ProjectPath, exePath.Native());
|
|
registry.Set(FilePathKey_ProjectPath, exePath.Native());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // User folder
|
|
|
|
+ AZ::IO::FixedMaxPath projectUserPath = FindProjectUserPath(registry, projectPath);
|
|
|
|
+ if (!projectUserPath.empty())
|
|
|
|
+ {
|
|
|
|
+ projectUserPath = projectUserPath.LexicallyNormal();
|
|
|
|
+ registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
|
|
|
|
+ }
|
|
|
|
+
|
|
// Engine root folder - corresponds to the @engroot@ alias
|
|
// Engine root folder - corresponds to the @engroot@ alias
|
|
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
|
|
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
|
|
|
|
+ if (engineRoot.empty())
|
|
|
|
+ {
|
|
|
|
+ // Use the project path if the engine root wasn't found, this can happen
|
|
|
|
+ // when running the game launcher with bundled assets which are not loaded
|
|
|
|
+ // until after the SettingsRegistry has determined file paths
|
|
|
|
+ engineRoot = projectPath;
|
|
|
|
+ }
|
|
|
|
+
|
|
if (!engineRoot.empty())
|
|
if (!engineRoot.empty())
|
|
{
|
|
{
|
|
engineRoot = engineRoot.LexicallyNormal();
|
|
engineRoot = engineRoot.LexicallyNormal();
|
|
registry.Set(FilePathKey_EngineRootFolder, engineRoot.Native());
|
|
registry.Set(FilePathKey_EngineRootFolder, engineRoot.Native());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
// Cache folder
|
|
// Cache folder
|
|
AZ::IO::FixedMaxPath projectCachePath = FindProjectCachePath(registry, projectPath).LexicallyNormal();
|
|
AZ::IO::FixedMaxPath projectCachePath = FindProjectCachePath(registry, projectPath).LexicallyNormal();
|
|
if (!projectCachePath.empty())
|
|
if (!projectCachePath.empty())
|
|
@@ -874,14 +1065,6 @@ namespace AZ::SettingsRegistryMergeUtils
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // User folder
|
|
|
|
- AZ::IO::FixedMaxPath projectUserPath = FindProjectUserPath(registry, projectPath);
|
|
|
|
- if (!projectUserPath.empty())
|
|
|
|
- {
|
|
|
|
- projectUserPath = projectUserPath.LexicallyNormal();
|
|
|
|
- registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
// Log folder
|
|
// Log folder
|
|
if (AZ::IO::FixedMaxPath projectLogPath = FindProjectLogPath(registry, projectUserPath); !projectLogPath.empty())
|
|
if (AZ::IO::FixedMaxPath projectLogPath = FindProjectLogPath(registry, projectUserPath); !projectLogPath.empty())
|
|
{
|
|
{
|