diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 74038c8aed..6b89b8c044 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -57,6 +57,7 @@ namespace AZ::Internal // and avoid all this logic. using namespace AZ::SettingsRegistryMergeUtils; + using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; AZ::IO::FixedMaxPath engineRoot; if (auto engineManifestPath = AZ::Utils::GetEngineManifestPath(); !engineManifestPath.empty()) @@ -72,45 +73,16 @@ namespace AZ::Internal struct EngineInfo { AZ::IO::FixedMaxPath m_path; - AZ::SettingsRegistryInterface::FixedValueString m_moniker; + FixedValueString m_moniker; }; struct EnginePathsVisitor : public AZ::SettingsRegistryInterface::Visitor { void Visit( - [[maybe_unused]] AZStd::string_view path, [[maybe_unused]] AZStd::string_view valueName, + [[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, [[maybe_unused]] AZ::SettingsRegistryInterface::Type type, AZStd::string_view value) override { - m_enginePaths.emplace_back(EngineInfo{AZ::IO::FixedMaxPath{value}.LexicallyNormal(), {}}); - } - - AZ::SettingsRegistryInterface::VisitResponse Traverse( - [[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, - AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type type) override - { - auto response = AZ::SettingsRegistryInterface::VisitResponse::Continue; - if (action == AZ::SettingsRegistryInterface::VisitAction::Begin) - { - if (type == AZ::SettingsRegistryInterface::Type::Array) - { - if (valueName.compare("engines") != 0) - { - response = AZ::SettingsRegistryInterface::VisitResponse::Skip; - } - } - } - else if (action == AZ::SettingsRegistryInterface::VisitAction::Value) - { - if (type == AZ::SettingsRegistryInterface::Type::String) - { - if (valueName.compare("path") != 0) - { - response = AZ::SettingsRegistryInterface::VisitResponse::Skip; - } - } - } - - return response; + m_enginePaths.emplace_back(EngineInfo{ AZ::IO::FixedMaxPath{value}.LexicallyNormal(), FixedValueString{valueName} }); } AZStd::vector m_enginePaths{}; @@ -119,11 +91,11 @@ namespace AZ::Internal EnginePathsVisitor pathVisitor; if (manifestLoaded) { - auto enginePathsKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engines", EngineManifestRootKey); + auto enginePathsKey = FixedValueString::format("%s/engines_path", EngineManifestRootKey); settingsRegistry.Visit(pathVisitor, enginePathsKey); } - const auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine_name", EngineSettingsRootKey); + const auto engineMonikerKey = FixedValueString::format("%s/engine_name", EngineSettingsRootKey); AZStd::set projectPathsNotFound; @@ -135,7 +107,15 @@ namespace AZ::Internal if (settingsRegistry.MergeSettingsFile( engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey)) { - settingsRegistry.Get(engineInfo.m_moniker, engineMonikerKey); + 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(), engineManifestPath.c_str(), + engineName.c_str()) + engineInfo.m_moniker = engineName; } } diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index 5bc6b95358..7e504d29cd 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -236,15 +236,13 @@ def get_gems_from_subdirectories(external_subdirs: list) -> list: # Data query methods -def get_this_engine() -> dict: - json_data = load_o3de_manifest() - engine_data = find_engine_data(json_data) - return engine_data - - def get_engines() -> list: json_data = load_o3de_manifest() - return json_data['engines'] if 'engines' in json_data else [] + engine_list = json_data['engines'] if 'engines' in json_data else [] + # Convert each engine dict entry into a string entry + return list(map( + lambda engine_object: engine_object.get('path', '') if isinstance(engine_object, dict) else engine_object, + engine_list)) def get_projects() -> list: @@ -424,20 +422,6 @@ def get_templates_for_generic_creation(): # temporary until we have a better wa return list(filter(filter_project_and_gem_templates_out, get_all_templates())) -def find_engine_data(json_data: dict, - engine_path: str or pathlib.Path = None) -> dict or None: - if not engine_path: - engine_path = get_this_engine_path() - engine_path = pathlib.Path(engine_path).resolve() - - for engine_object in json_data['engines']: - engine_object_path = pathlib.Path(engine_object['path']).resolve() - if engine_path == engine_object_path: - return engine_object - - return None - - def get_engine_json_data(engine_name: str = None, engine_path: str or pathlib.Path = None) -> dict or None: if not engine_name and not engine_path: @@ -639,8 +623,13 @@ def get_registered(engine_name: str = None, # check global first then this engine if isinstance(engine_name, str): - for engine in json_data['engines']: - engine_path = pathlib.Path(engine['path']).resolve() + engines = get_engines() + for engine in engines: + if isinstance(engine, dict): + engine_path = pathlib.Path(engine['path']).resolve() + else: + engine_path = pathlib.Path(engine_object).resolve() + engine_json = engine_path / 'engine.json' with engine_json.open('r') as f: try: diff --git a/scripts/o3de/o3de/print_registration.py b/scripts/o3de/o3de/print_registration.py index 10316cc4a8..b164243b7c 100644 --- a/scripts/o3de/o3de/print_registration.py +++ b/scripts/o3de/o3de/print_registration.py @@ -40,61 +40,67 @@ def get_project_path(project_path: pathlib.Path, project_name: str) -> pathlib.P def print_this_engine(verbose: int) -> int: - engine_data = manifest.get_this_engine() - print(json.dumps(engine_data, indent=4)) - result = True + this_engine_path = manifest.get_this_engine_path() + print(f'This Engine:\n{json.dumps(str(this_engine_path), indent=4)}') if verbose > 0: - result = print_manifest_json_data(engine_data, 'engine.json', 'This Engine', + return print_manifest_json_data([this_engine_path], 'This Engine', manifest.get_engine_json_data, 'engine_path') - return 0 if result else 1 + return 0 def print_engines(verbose: int) -> None: engines_data = manifest.get_engines() - print(json.dumps(engines_data, indent=4)) + print(f'Engine Paths:\n{json.dumps(engines_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engines_data, 'engine.json', 'Engines', + return print_manifest_json_data(engines_data, 'Engine Jsons', manifest.get_engine_json_data, 'engine_path') return 0 def print_projects(verbose: int) -> int: projects_data = manifest.get_projects() - print(json.dumps(projects_data, indent=4)) + print(f'Project Paths:\n{json.dumps(projects_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(projects_data, 'project.json', 'Projects', + return print_manifest_json_data(projects_data, 'Project Jsons', manifest.get_project_json_data, 'project_path') return 0 def print_gems(verbose: int) -> int: gems_data = manifest.get_gems() - print(json.dumps(gems_data, indent=4)) + print(f'Gem Paths:\n{json.dumps(gems_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(gems_data, 'Gem Jsons', manifest.get_gem_json_data, 'gem_path') return 0 +def print_external_subdirectories(verbose: int) -> int: + external_subdirs_data = manifest.get_external_subdirectories() + print(f'External Subdirectories:\n{json.dumps(external_subdirs_data, indent=4)}') + return 0 + + + def print_templates(verbose: int) -> int: templates_data = manifest.get_templates() - print(json.dumps(templates_data, indent=4)) + print(f'Template Paths:\n{json.dumps(templates_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(templates_data, 'template.json', 'Templates', + return print_manifest_json_data(templates_data, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 def print_restricted(verbose: int) -> int: restricted_data = manifest.get_restricted() - print(json.dumps(restricted_data, indent=4)) + print(f'Restricted Paths:\n{json.dumps(restricted_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(restricted_data, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 @@ -102,47 +108,47 @@ def print_restricted(verbose: int) -> int: # Engine output methods def print_engine_projects(verbose: int) -> int: engine_projects_data = manifest.get_engine_projects() - print(json.dumps(engine_projects_data, indent=4)) + print(f'Project Paths:\n{json.dumps(engine_projects_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_projects_data, 'project.json', 'Projects', + return print_manifest_json_data(engine_projects_data, 'Project Jsons', manifest.get_project_json_data, 'project_path') return 0 def print_engine_gems(verbose: int) -> int: engine_gems_data = manifest.get_engine_gems() - print(json.dumps(engine_gems_data, indent=4)) + print(f'Gem Paths:\n{json.dumps(engine_gems_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(engine_gems_data, 'Gem Jsons', manifest.get_gem_json_data, 'gem_path') return 0 def print_engine_templates(verbose: int) -> int: engine_templates_data = manifest.get_engine_templates() - print(json.dumps(engine_templates_data, indent=4)) + print(f'Template Paths:\n{json.dumps(engine_templates_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_templates_data, 'template.json', 'Templates', + return print_manifest_json_data(engine_templates_data, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 def print_engine_restricted(verbose: int) -> int: engine_restricted_data = manifest.get_engine_restricted() - print(json.dumps(engine_restricted_data, indent=4)) + print(f'Restricted Paths:\n{json.dumps(engine_restricted_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(engine_restricted_data, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 -def print_engine_external_subdirectories() -> int: +def print_engine_external_subdirectories(verbose: int) -> int: external_subdirs_data = manifest.get_engine_external_subdirectories() - print(json.dumps(external_subdirs_data, indent=4)) + print(f'External Subdirectories:\n{json.dumps(external_subdirs_data, indent=4)}') return 0 @@ -153,21 +159,21 @@ def print_project_gems(verbose: int, project_path: pathlib.Path, project_name: s return 1 project_gems_data = manifest.get_project_gems(project_path) - print(json.dumps(project_gems_data, indent=4)) + print(f'Gem Paths:\n{json.dumps(project_gems_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(project_gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(project_gems_data, 'Gems Jsons', manifest.get_gem_json_data, 'gem_path') return 0 -def print_project_external_subdirectories(project_path: pathlib.Path, project_name: str) -> int: +def print_project_external_subdirectories(verbose: int, project_path: pathlib.Path, project_name: str) -> int: project_path = get_project_path(project_path, project_name) if not project_path: return 1 external_subdirs_data = manifest.get_project_external_subdirectories(project_path) - print(json.dumps(external_subdirs_data, indent=4)) + print(f'External Subdirectories:\n{json.dumps(external_subdirs_data, indent=4)}') return 0 @@ -177,9 +183,9 @@ def print_project_templates(verbose: int, project_path: pathlib.Path, project_na return 1 project_templates_data = manifest.get_project_templates(project_path) - print(json.dumps(project_templates_data, indent=4)) + print(f'Template Paths:\n{json.dumps(project_templates_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(project_templates_data, 'template.json', 'Templates', + return print_manifest_json_data(project_templates_data, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 @@ -190,73 +196,118 @@ def print_project_restricted(verbose: int, project_path: pathlib.Path, project_n return 1 project_restricted_data = manifest.get_project_restricted(project_path) - print(json.dumps(project_restricted_data, indent=4)) + print(f'Restricted Paths:\n{json.dumps(project_restricted_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(project_restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(project_restricted_data, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 def print_all_projects(verbose: int) -> int: all_projects_data = manifest.get_all_projects() - print(json.dumps(all_projects_data, indent=4)) + print(f'Project Paths:\n{json.dumps(all_projects_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_projects_data, 'project.json', 'Projects', + return print_manifest_json_data(all_projects_data, 'Project Jsons', manifest.get_project_json_data, 'project_path') return 0 -def print_all_gems(verbose: int) -> int: - all_gems_data = manifest.get_all_gems() - print(json.dumps(all_gems_data, indent=4)) +def print_all_gems(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_gems = manifest.get_gems() + all_gems.extend(manifest.get_engine_gems()) + + # If a project path or project name is supplied query the gems from that project, otherwise query the gems from + # all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_gems.extend(manifest.get_project_gems(project)) + + # Filter out duplicates + all_gems = list(dict.fromkeys(all_gems)) + print(f'Gem Paths:\n{json.dumps(all_gems, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(all_gems, 'Gem Jsons', manifest.get_gem_json_data, 'gem_path') return 0 -def print_all_external_subdirectories() -> int: - all_external_subdirectories_data = manifest.get_all_external_subdirectories() - print(json.dumps(all_external_subdirectories_data, indent=4)) +def print_all_external_subdirectories(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_external_subdirectories = manifest.get_external_subdirectories() + all_external_subdirectories.extend(manifest.get_engine_external_subdirectories()) + + # If a project path or project name is supplied query the external subdirectories from that project, + # otherwise query the external subdirectories from all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_external_subdirectories.extend(manifest.get_project_external_subdirectories(project)) + + # Filter out duplicates + all_external_subdirectories = list(dict.fromkeys(all_external_subdirectories)) + print(f'External Subdirectories:\n{json.dumps(all_external_subdirectories, indent=4)}') return 0 -def print_all_templates(verbose: int) -> int: - all_templates_data = manifest.get_all_templates() - print(json.dumps(all_templates_data, indent=4)) + +def print_all_templates(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_templates = manifest.get_templates() + all_templates.extend(manifest.get_engine_templates()) + + # If a project path or project name is supplied query the templates from that project, + # otherwise query the templates from all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_templates.extend(manifest.get_project_templates(project)) + + # Filter out duplicates + all_templates = list(dict.fromkeys(all_templates)) + print(f'Template Paths:\n{json.dumps(all_templates, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_templates_data, 'template.json', 'Templates', + return print_manifest_json_data(all_templates, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 -def print_all_restricted(verbose: int) -> int: - all_restricted_data = manifest.get_all_restricted() - print(json.dumps(all_restricted_data, indent=4)) +def print_all_restricted(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_restricted = manifest.get_restricted() + all_restricted.extend(manifest.get_engine_restricted()) + + # If a project path or project name is supplied query the restricted from that project, + # otherwise query the restricted from all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_restricted.extend(manifest.get_project_restricted(project)) + + # Filter out duplicates + all_restricted = list(dict.fromkeys(all_restricted)) + print(f'Restricted Paths:\n{json.dumps(all_restricted, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(all_restricted, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 -def print_manifest_json_data(uri_json_data: dict, json_filename: str, +def print_manifest_json_data(uri_json_data: list, print_prefix: str, get_json_func: callable, get_json_data_kw: str) -> int: print('\n') print(f"{print_prefix}================================================") for manifest_uri in uri_json_data: # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(manifest_uri) + parsed_uri = urllib.parse.urlparse(pathlib.Path(manifest_uri).as_posix()) if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: repo_sha256 = hashlib.sha256(manifest_uri.encode()) cache_folder = manifest.get_o3de_cache_folder() manifest_json_path = cache_folder / str(repo_sha256.hexdigest() + '.json') else: - manifest_json_path = pathlib.Path(manifest_uri).resolve() / json_filename + manifest_json_path = pathlib.Path(manifest_uri).resolve() - json_data = get_json_func(**{get_json_data_kwargs: manifest_json_path}) + json_data = get_json_func(**{get_json_data_kw: manifest_json_path}) if json_data: print(manifest_json_path) print(json.dumps(json_data, indent=4) + '\n') @@ -284,29 +335,30 @@ def print_repos_data(repos_data: dict) -> int: return 0 -def register_show_repos(verbose: int) -> None: +def print_repos(verbose: int) -> int: repos_data = manifest.get_repos() print(json.dumps(repos_data, indent=4)) if verbose > 0: - return print_repos_data(repos_data) == 0 + return print_repos_data(repos_data) return 0 -def register_show(verbose: int) -> None: +def register_show(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: json_data = manifest.load_o3de_manifest() print(f"{manifest.get_o3de_manifest()}:") print(json.dumps(json_data, indent=4)) - result = True + result = 0 if verbose > 0: - result = print_manifest_json_data(manifest.get_engines()) == 0 and result - result = print_manifest_json_data(manifest.get_all_projects()) == 0 and result - result = print_manifest_json_data(manifest.get_gems()) == 0 and result - result = print_manifest_json_data(manifest.get_all_templates()) == 0 and result - result = print_manifest_json_data(manifest.get_all_restricted()) == 0 and result - result = print_repos_data(manifest.get_repos()) == 0 and result - return 0 if result else 1 + result = print_engines(verbose) or result + result = print_all_projects(verbose) or result + result = print_all_gems(verbose, project_path, project_name) or result + result = print_all_templates(verbose, project_path, project_name) or result + result = print_all_restricted(verbose, project_path, project_name) or result + result = print_repos(verbose) or result + + return result def _run_register_show(args: argparse) -> int: @@ -321,6 +373,8 @@ def _run_register_show(args: argparse) -> int: return print_projects(args.verbose) elif args.gems: return print_gems(args.verbose) + elif args.external_subdirectories: + return print_external_subdirectories(args.verbose) elif args.templates: return print_templates(args.verbose) elif args.repos: @@ -333,7 +387,7 @@ def _run_register_show(args: argparse) -> int: elif args.engine_gems: return print_engine_gems(args.verbose) elif args.engine_external_subdirectories: - return print_engine_external_subdirectories() + return print_engine_external_subdirectories(args.verbose) elif args.engine_templates: return print_engine_templates(args.verbose) elif args.engine_restricted: @@ -342,7 +396,7 @@ def _run_register_show(args: argparse) -> int: elif args.project_gems: return print_project_gems(args.verbose, args.project_path, args.project_name) elif args.project_external_subdirectories: - return print_project_external_subdirectories(args.project_path, args.project_name) + return print_project_external_subdirectories(args.verbose, args.project_path, args.project_name) elif args.project_templates: return print_project_templates(args.verbose, args.project_path, args.project_name) elif args.project_restricted: @@ -351,16 +405,16 @@ def _run_register_show(args: argparse) -> int: elif args.all_projects: return print_all_projects(args.verbose) elif args.all_gems: - return print_all_gems(args.verbose) + return print_all_gems(args.verbose, args.project_path, args.project_name) elif args.all_external_subdirectories: - return print_all_external_subdirectories() + return print_all_external_subdirectories(args.verbose, args.project_path, args.project_name) elif args.all_templates: - return print_all_templates(args.verbose) + return print_all_templates(args.verbose, args.project_path, args.project_name) elif args.all_restricted: - return print_all_restricted(args.verbose) + return print_all_restricted(args.verbose, args.project_path, args.project_name) else: - return register_show(args.verbose) + return register_show(args.verbose, args.project_path, args.project_name) def add_parser_args(parser): @@ -393,6 +447,9 @@ def add_parser_args(parser): group.add_argument('-rs', '--restricted', action='store_true', required=False, default=False, help='Output the restricted directories registered in the global ~/.o3de/o3de_manifest.json.') + group.add_argument('-es', '--external-subdirectories', action='store_true', required=False, + default=False, + help='Output the external subdirectories registered in the global ~/.o3de/o3de_manifest.json.') group.add_argument('-ep', '--engine-projects', action='store_true', required=False, default=False, @@ -428,16 +485,28 @@ def add_parser_args(parser): help='Output all projects registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.') group.add_argument('-ag', '--all-gems', action='store_true', required=False, default=False, - help='Output all gems registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos') + help='Output all gems registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name option is supplied, outputs gems registered in' + ' that project\'s project.json otherwise outputs registered gems from all registered projects.' + ' Ignores repos') group.add_argument('-at', '--all-templates', action='store_true', required=False, default=False, - help='Output all templates registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.') + help='Output all templates registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name option is supplied, outputs templates registered in' + ' that project\'s project.json otherwise outputs registered templates from all registered' + ' projects. Ignores repos') group.add_argument('-ares', '--all-restricted', action='store_true', required=False, default=False, - help='Output all restricted directory registered in the ~/.o3de/o3de_manifest.json and the current engine.json.') + help='Output all restricted directory registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name option is supplied, outputs restricted' + ' directories registered in that project\'s project.json otherwise outputs restricted' + ' directories registered from all registered projects. Ignores repos') group.add_argument('-aes', '--all-external-subdirectories', action='store_true', default=False, - help='Output all external subdirectories registered in the ~/.o3de/o3de_manifest.json and the current engine.json.') + help='Output all external subdirectories registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name options is supplied, outputs external' + ' subdirectories registered in that project\'s project.json otherwise outputs external' + ' subdirectories registered from all registered projects. Ignores repos') parser.add_argument('-v', '--verbose', action='count', required=False, default=0, diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index fb91cb5bea..8481c5fae0 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -261,43 +261,6 @@ def add_engine_name_to_path(json_data: dict, engine_path: pathlib.Path, force: b return 0 -def register_engine_path(json_data: dict, - engine_path: pathlib.Path, - remove: bool = False, - force: bool = False) -> int: - if not engine_path: - logger.error(f'Engine path cannot be empty.') - return 1 - engine_path = pathlib.Path(engine_path).resolve() - - for engine_object in json_data.get('engines', []): - if isinstance(engine_object, dict): - engine_object_path = pathlib.Path(engine_object['path']).resolve() - else: - engine_object_path = pathlib.Path(engine_object).resolve() - if engine_object_path == engine_path: - json_data['engines'].remove(engine_object) - - if remove: - return remove_engine_name_to_path(json_data, engine_path) - - if not engine_path.is_dir(): - logger.error(f'Engine path {engine_path} does not exist.') - return 1 - - engine_json = engine_path / 'engine.json' - if not validation.valid_o3de_engine_json(engine_json): - logger.error(f'Engine json {engine_json} is not valid.') - return 1 - - engine_object = {} - engine_object.update({'path': engine_path.as_posix()}) - - json_data.setdefault('engines', []).insert(0, engine_object) - - return add_engine_name_to_path(json_data, engine_path, force) - - def register_o3de_object_path(json_data: dict, o3de_object_path: str or pathlib.Path, o3de_object_key: str, @@ -343,7 +306,7 @@ def register_o3de_object_path(json_data: dict, try: paths_to_remove.append(o3de_object_path.relative_to(save_path.parent)) except ValueError: - pass # It is OK relative path cannot be formed + pass # It is not an error if a relative path cannot be formed manifest_data[o3de_object_key] = list(filter(lambda p: pathlib.Path(p) not in paths_to_remove, manifest_data.setdefault(o3de_object_key, []))) @@ -358,7 +321,7 @@ def register_o3de_object_path(json_data: dict, manifest_json_path = o3de_object_path / o3de_json_filename if validation_func and not validation_func(manifest_json_path): - logger.error(f'o3de json {manifest_json_path} is not valid.') + logger.error(f'Manifest at path {manifest_json_path} is not valid.') return 1 # if there is a save path make it relative the directory containing o3de object json file @@ -374,6 +337,27 @@ def register_o3de_object_path(json_data: dict, return 0 +def register_engine_path(json_data: dict, + engine_path: pathlib.Path, + remove: bool = False, + force: bool = False) -> int: + # If the o3de_manifest.json 'engines' key is list containing dictionary entries, transform it to a list of strings + engine_list = json_data.get('engines', []) + + def transform_engine_dict_to_string(engine): return engine.get('path', '') if isinstance(engine, dict) else engine + json_data['engines'] = list(map(transform_engine_dict_to_string, engine_list)) + + result = register_o3de_object_path(json_data, engine_path, 'engines', 'engine.json', + validation.valid_o3de_engine_json, remove) + if result != 0: + return result + + if remove: + return remove_engine_name_to_path(json_data, engine_path) + + return add_engine_name_to_path(json_data, engine_path, force) + + def register_external_subdirectory(json_data: dict, external_subdir_path: pathlib.Path, remove: bool = False, @@ -677,37 +661,30 @@ def remove_invalid_o3de_projects(manifest_path: pathlib.Path = None) -> int: return result def remove_invalid_o3de_objects() -> None: - json_data = manifest.load_o3de_manifest() - - for engine_object in json_data.get('engines', []): - engine_path = engine_object.get('path', '') + for engine_path in manifest.get_engines(): if not validation.valid_o3de_engine_json(pathlib.Path(engine_path).resolve() / 'engine.json'): logger.warn(f"Engine path {engine_path} is invalid.") register(engine_path=engine_path, remove=True) remove_invalid_o3de_projects() - for gem in json_data.get('gems', []): - if not validation.valid_o3de_gem_json(pathlib.Path(gem).resolve() / 'gem.json'): - logger.warn(f"Gem path {gem} is invalid.") - register(gem_path=gem, remove=True) - - for external in json_data.get('external_subdirectories', []): + for external in manifest.get_external_subdirectories(): external = pathlib.Path(external).resolve() if not external.is_dir(): logger.warn(f"External subdirectory {external} is invalid.") register(engine_path=engine_path, external_subdir_path=external, remove=True) - for template in json_data.get('templates', []): + for template in manifest.get_templates(): if not validation.valid_o3de_template_json(pathlib.Path(template).resolve() / 'template.json'): logger.warn(f"Template path {template} is invalid.") register(template_path=template, remove=True) - for restricted in json_data.get('restricted', []): + for restricted in manifest.get_restricted(): if not validation.valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): logger.warn(f"Restricted path {restricted} is invalid.") register(restricted_path=restricted, remove=True) + json_data = manifest.load_o3de_manifest() default_engines_folder = pathlib.Path(json_data.get('default_engines_folder', manifest.get_o3de_engines_folder())).resolve() if not default_engines_folder.is_dir(): new_default_engines_folder = manifest.get_o3de_folder() / 'Engines' diff --git a/scripts/o3de/tests/CMakeLists.txt b/scripts/o3de/tests/CMakeLists.txt index 00d0774c45..1cd6eac7ee 100644 --- a/scripts/o3de/tests/CMakeLists.txt +++ b/scripts/o3de/tests/CMakeLists.txt @@ -65,4 +65,11 @@ ly_add_pytest( PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_engine_template.py TEST_SUITE smoke EXCLUDE_TEST_RUN_TARGET_FROM_IDE -) \ No newline at end of file +) + +ly_add_pytest( + NAME o3de_register_show + PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_print_registration.py + TEST_SUITE smoke + EXCLUDE_TEST_RUN_TARGET_FROM_IDE +) diff --git a/scripts/o3de/tests/unit_test_print_registration.py b/scripts/o3de/tests/unit_test_print_registration.py new file mode 100644 index 0000000000..7e3e75edcd --- /dev/null +++ b/scripts/o3de/tests/unit_test_print_registration.py @@ -0,0 +1,383 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +import argparse +import json +import logging +import pytest +import pathlib +from unittest.mock import patch + +from o3de import print_registration + + +TEST_PROJECT_JSON_PAYLOAD = ''' +{ + "project_name": "MinimalProject", + "origin": "The primary repo for MinimalProject goes here: i.e. http://www.mydomain.com", + "license": "What license MinimalProject uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "MinimalProject", + "summary": "A short description of MinimalProject.", + "canonical_tags": [ + "Project" + ], + "user_tags": [ + "MinimalProject" + ], + "icon_path": "preview.png", + "engine": "o3de-install", + "external_subdirectories": [ + "D:/TestGem" + ] +} +''' + +TEST_ENGINE_JSON_PAYLOAD = ''' +{ + "engine_name": "o3de", + "restricted_name": "o3de", + "FileVersion": 1, + "O3DEVersion": "0.0.0.0", + "O3DECopyrightYear": 2021, + "O3DEBuildNumber": 0, + "external_subdirectories": [ + "Gems/TestGem2" + ], + "projects": [ + ], + "templates": [ + "Templates/MinimalProject" + ] +} +''' + +TEST_GEM_JSON_PAYLOAD = ''' +{ + "gem_name": "TestGem", + "display_name": "TestGem", + "license": "What license TestGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "origin": "The primary repo for TestGem goes here: i.e. http://www.mydomain.com", + "type": "Code", + "summary": "A short description of TestGem.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "TestGem" + ], + "icon_path": "preview.png", + "requirements": "" +} +''' + +TEST_TEMPLATE_JSON_PAYLOAD = ''' +{ + "template_name": "AssetGem", + "origin": "The primary repo for AssetGem goes here: i.e. http://www.mydomain.com", + "license": "What license AssetGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "AssetGem", + "summary": "A short description of AssetGem template.", + "canonical_tags": [], + "user_tags": [ + "AssetGem" + ], + "icon_path": "preview.png", + "copyFiles": [ + { + "file": "CMakeLists.txt", + "origin": "CMakeLists.txt", + "isTemplated": true, + "isOptional": false + }, + { + "file": "gem.json", + "origin": "gem.json", + "isTemplated": true, + "isOptional": false + }, + { + "file": "preview.png", + "origin": "preview.png", + "isTemplated": false, + "isOptional": false + } + ], + "createDirectories": [ + { + "dir": "Assets", + "origin": "Assets" + } + ] +} +''' + +TEST_RESTRICTED_JSON_PAYLOAD = ''' +{ + "restricted_name": "o3de" +} +''' + +TEST_O3DE_MANIFEST_JSON_PAYLOAD = ''' +{ + "o3de_manifest_name": "testuser", + "origin": "C:/Users/testuser/.o3de", + "default_engines_folder": "C:/Users/testuser/.o3de/Engines", + "default_projects_folder": "C:/Users/testuser/.o3de/Projects", + "default_gems_folder": "C:/Users/testuser/.o3de/Gems", + "default_templates_folder": "C:/Users/testuser/.o3de/Templates", + "default_restricted_folder": "C:/Users/testuser/.o3de/Restricted", + "default_third_party_folder": "C:/Users/testuser/.o3de/3rdParty", + "projects": [ + "D:/MinimalProject" + ], + "external_subdirectories": [], + "templates": [], + "restricted": [], + "repos": [], + "engines": [ + "D:/o3de/o3de" + ], + "engines_path": { + "o3de": "D:/o3de/o3de" + } +} +''' + +class TestPrintRegistration: + @staticmethod + def load_manifest_json(): + return json.loads(TEST_O3DE_MANIFEST_JSON_PAYLOAD) + + @staticmethod + def get_engine_json_data(engine_path: pathlib.Path = None): + return json.loads(TEST_ENGINE_JSON_PAYLOAD) + + @staticmethod + def get_project_json_data(project_path: pathlib.Path = None): + return json.loads(TEST_PROJECT_JSON_PAYLOAD) + + @staticmethod + def get_gem_json_data(gem_path: pathlib.Path = None): + return json.loads(TEST_GEM_JSON_PAYLOAD) + + @staticmethod + def get_template_json_data(template_path: pathlib.Path = None): + return json.loads(TEST_TEMPLATE_JSON_PAYLOAD) + + @staticmethod + def get_restricted_json_data(restricted_path: pathlib.Path = None): + return json.loads(TEST_RESTRICTED_JSON_PAYLOAD) + + @pytest.mark.parametrize("project_path, verbose", [ + pytest.param(None, 0), + pytest.param(None, 1), + pytest.param(pathlib.Path("D:/MinimalProject"), 0), + pytest.param(pathlib.Path("D:/MinimalProject"), 1) + ]) + def test_print_registration_no_option(self, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_engine_json_data', + side_effect=self.get_engine_json_data) as get_engine_json_data_patch, \ + patch('o3de.manifest.get_project_json_data', + side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.manifest.get_gem_json_data', side_effect=self.get_gem_json_data) as get_gem_json_patch, \ + patch('o3de.manifest.get_template_json_data', side_effect=self.get_template_json_data) as get_template_json_patch, \ + patch('o3de.manifest.get_restricted_json_data', side_effect=self.get_restricted_json_data) as get_json_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("engine_arg_option, verbose", [ + pytest.param("--this-engine", 0), + pytest.param("--this-engine", 1), + pytest.param("--engines", 0), + pytest.param("--engines", 1) + ]) + def test_print_engine_registration(self, engine_arg_option, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [engine_arg_option] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_engine_json_data', side_effect=self.get_engine_json_data) as get_engine_json_data_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, verbose", [ + pytest.param("--projects", 0), + pytest.param("--projects", 1), + pytest.param("--engine-projects", 0), + pytest.param("--engine-projects", 1), + pytest.param("--all-projects", 0), + pytest.param("--all-projects", 1) + ]) + def test_print_project_registration(self, arg_option, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_json_data_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--gems", None, 0), + pytest.param("--gems", None, 1), + pytest.param("--engine-gems", None, 0), + pytest.param("--engine-gems", None, 1), + pytest.param("--project-gems", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--project-gems", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-gems", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-gems", None, 0), + pytest.param("--all-gems", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-gems", None, 1) + ]) + def test_print_gem_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + # Patch the manifest.py function to locate gem.json files in external subdirectories + # to just return a fake path to a single test gem + def get_gems_from_subdirectories(external_subdirs: list) -> list: + return ["D:/TestGem"] + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_gem_json_data', side_effect=self.get_gem_json_data) as get_json_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.manifest.get_gems_from_subdirectories', side_effect=get_gems_from_subdirectories) as get_gems_from_subdirs_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--templates", None, 0), + pytest.param("--templates", None, 1), + pytest.param("--engine-templates", None, 0), + pytest.param("--engine-templates", None, 1), + pytest.param("--project-templates", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--project-templates", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-templates", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-templates", None, 0), + pytest.param("--all-templates", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-templates", None, 1) + ]) + def test_print_template_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_template_json_data', side_effect=self.get_template_json_data) as get_json_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--restricted", None, 0), + pytest.param("--restricted", None, 1), + pytest.param("--engine-restricted", None, 0), + pytest.param("--engine-restricted", None, 1), + pytest.param("--project-restricted", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--project-restricted", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-restricted", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-restricted", None, 0), + pytest.param("--all-restricted", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-restricted", None, 1) + ]) + def test_print_restricted_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_restricted_json_data', side_effect=self.get_restricted_json_data) as get_json_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + # Setting --verbose with the --*external-subdirectories option doesn't result in any additional output + # So it is only parameterized as 0 + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--external-subdirectories", None, 0), + pytest.param("--engine-external-subdirectories", None, 0), + pytest.param("--project-external-subdirectories", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-external-subdirectories", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-external-subdirectories", None, 0), + ]) + def test_print_external_subdirectories_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_project_json_data', + side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0