Added new feature to the register command to auto detect manifest file based on input path (#2967)

* Added new feature to the register command to auto detect the correct
o3de manifest file to write to based on the supplied registration path

Updated the create-gem command to register the gem on creation.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added new feature to the register command to auto detect the correct
o3de manifest file to write to based on the supplied registration path

Updated the create-gem command to register the gem on creation.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fix incorrect variable reference in register_project_path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed o3de python package unit test
The enable_gem, register and engine template test have been updated to
account for the logic to register a gem after creation

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added a --no-register arg to engine-template.py
This allows registration of gems/projects using the create-project and
create-gem commands to be skipped

Prevented registration of a gems and projects in the engine_template
command test

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Wrapped first parameter to find_ancestor_dir_containing_file with
pathlib.PurePath

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
monroegm-disable-blank-issue-2
lumberyard-employee-dm 4 years ago committed by GitHub
parent c1a0b5c686
commit 9afbd07a88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1299,6 +1299,7 @@ def create_project(project_path: pathlib.Path,
keep_license_text: bool = False,
replace: list = None,
force: bool = False,
no_register: bool = False,
system_component_class_id: str = None,
editor_system_component_class_id: str = None,
module_id: str = None) -> int:
@ -1657,9 +1658,10 @@ def create_project(project_path: pathlib.Path,
d.write('# {END_LICENSE}\n')
# Register the project with the global o3de_manifest.json and set the project.json "engine" field to match the
# Register the project with the either o3de_manifest.json or engine.json
# and set the project.json "engine" field to match the
# engine.json "engine_name" field
return register.register(project_path=project_path)
return register.register(project_path=project_path) if not no_register else 0
def create_gem(gem_path: pathlib.Path,
@ -1676,6 +1678,7 @@ def create_gem(gem_path: pathlib.Path,
keep_license_text: bool = False,
replace: list = None,
force: bool = False,
no_register: bool = False,
system_component_class_id: str = None,
editor_system_component_class_id: str = None,
module_id: str = None) -> int:
@ -2035,7 +2038,8 @@ def create_gem(gem_path: pathlib.Path,
d.write('#\n')
d.write('# SPDX-License-Identifier: Apache-2.0 OR MIT\n')
d.write('# {END_LICENSE}\n')
return 0
# Register the gem with the either o3de_manifest.json, engine.json or project.json based on the gem path
return register.register(gem_path=gem_path) if not no_register else 0
def _run_create_template(args: argparse) -> int:
@ -2086,6 +2090,7 @@ def _run_create_project(args: argparse) -> int:
args.keep_license_text,
args.replace,
args.force,
args.no_register,
args.system_component_class_id,
args.editor_system_component_class_id,
args.module_id)
@ -2106,6 +2111,7 @@ def _run_create_gem(args: argparse) -> int:
args.keep_license_text,
args.replace,
args.force,
args.no_register,
args.system_component_class_id,
args.editor_system_component_class_id,
args.module_id)
@ -2370,6 +2376,9 @@ def add_args(subparsers) -> None:
' uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_project_subparser.add_argument('-f', '--force', action='store_true', default=False,
help='Copies over instantiated template directory even if it exist.')
create_project_subparser.add_argument('--no-register', action='store_true', default=False,
help='If the project template is instantiated successfully, it will not register the'
' project with the global or engine manifest file.')
create_project_subparser.set_defaults(func=_run_create_project)
# creation of a gem from a template (like create from template but makes gem assumptions)
@ -2463,6 +2472,9 @@ def add_args(subparsers) -> None:
' default is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_gem_subparser.add_argument('-f', '--force', action='store_true', default=False,
help='Copies over instantiated template directory even if it exist.')
create_gem_subparser.add_argument('--no-register', action='store_true', default=False,
help='If the gem template is instantiated successfully, it will not register the'
' gem with the global, project or engine manifest file.')
create_gem_subparser.set_defaults(func=_run_create_gem)

@ -285,14 +285,14 @@ def register_o3de_object_path(json_data: dict,
manifest_data = None
if engine_path:
manifest_data = manifest.get_engine_json_data(None, engine_path)
manifest_data = manifest.get_engine_json_data(engine_path=engine_path)
if not manifest_data:
logger.error(f'Cannot load engine.json data at path {engine_path}')
return 1
save_path = engine_path / 'engine.json'
elif project_path:
manifest_data = manifest.get_project_json_data(None, project_path)
manifest_data = manifest.get_project_json_data(project_path=project_path)
if not manifest_data:
logger.error(f'Cannot load project.json data at path {project_path}')
return 1
@ -367,6 +367,11 @@ def register_external_subdirectory(json_data: dict,
:return An integer return code indicating whether registration or removal of the external subdirectory
completed successfully
"""
# If a project path or engine path has not been supplied auto detect which manifest to register the input path with
if not project_path and not engine_path:
project_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('project.json'), external_subdir_path)
if not project_path:
engine_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('engine.json'), external_subdir_path)
return register_o3de_object_path(json_data, external_subdir_path, 'external_subdirectories', '', None, remove,
engine_path, project_path)
@ -376,6 +381,11 @@ def register_gem_path(json_data: dict,
remove: bool = False,
engine_path: pathlib.Path = None,
project_path: pathlib.Path = None) -> int:
# If a project path or engine path has not been supplied auto detect which manifest to register the input path with
if not project_path and not engine_path:
project_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('project.json'), gem_path)
if not project_path:
engine_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('engine.json'), gem_path)
return register_o3de_object_path(json_data, gem_path, 'external_subdirectories', 'gem.json',
validation.valid_o3de_gem_json, remove, engine_path, project_path)
@ -384,6 +394,11 @@ def register_project_path(json_data: dict,
project_path: pathlib.Path,
remove: bool = False,
engine_path: pathlib.Path = None) -> int:
# If an engine path has not been supplied auto detect if the project should be register with the engine.json
# or the ~/.o3de/o3de_manifest.json
if not engine_path:
engine_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('engine.json'), project_path)
result = register_o3de_object_path(json_data, project_path, 'projects', 'project.json',
validation.valid_o3de_project_json, remove, engine_path, None)
@ -419,6 +434,11 @@ def register_template_path(json_data: dict,
template_path: pathlib.Path,
remove: bool = False,
engine_path: pathlib.Path = None) -> int:
# If a project path or engine path has not been supplied auto detect which manifest to register the input path
if not project_path and not engine_path:
project_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('project.json'), template_path)
if not project_path:
engine_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('engine.json'), template_path)
return register_o3de_object_path(json_data, template_path, 'templates', 'template.json',
validation.valid_o3de_template_json, remove, engine_path, None)
@ -427,6 +447,11 @@ def register_restricted_path(json_data: dict,
restricted_path: pathlib.Path,
remove: bool = False,
engine_path: pathlib.Path = None) -> int:
# If a project path or engine path has not been supplied auto detect which manifest to register the input path
if not project_path and not engine_path:
project_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('project.json'), restricted_path)
if not project_path:
engine_path = utils.find_ancestor_dir_containing_file(pathlib.PurePath('engine.json'), restricted_path)
return register_o3de_object_path(json_data, restricted_path, 'restricted', 'restricted.json',
validation.valid_o3de_restricted_json, remove, engine_path, None)

@ -8,7 +8,7 @@
"""
This file contains utility functions
"""
import sys
import uuid
import pathlib
import shutil
@ -123,4 +123,51 @@ def download_zip_file(parsed_uri, download_zip_path: pathlib.Path) -> int:
download_zip_path.unlink()
return 1
return 0
return 0
def find_ancestor_file(target_file_name: pathlib.PurePath, start_path: pathlib.Path,
max_scan_up_range: int=0) -> pathlib.Path or None:
"""
Find a file with the given name in the ancestor directories by walking up the starting path until the file is found.
:param target_file_name: Name of the file to find.
:param start_path: path to start looking for the file.
:param max_scan_up_range: maximum number of directories to scan upwards when searching for target file
if the value is 0, then there is no max
:return: Path to the file or None if not found.
"""
current_path = pathlib.Path(start_path)
candidate_path = current_path / target_file_name
max_scan_up_range = max_scan_up_range if max_scan_up_range else sys.maxsize
# Limit the number of directories to traverse, to avoid infinite loop in path cycles
for _ in range(max_scan_up_range):
if candidate_path.exists():
# Found the file we wanted
break
parent_path = current_path.parent
if parent_path == current_path:
# Only true when we are at the directory root, can't keep searching
break
candidate_path = parent_path / target_file_name
current_path = parent_path
return candidate_path if candidate_path.exists() else None
def find_ancestor_dir_containing_file(target_file_name: pathlib.PurePath, start_path: pathlib.Path,
max_scan_up_range: int=0) -> pathlib.Path or None:
"""
Find nearest ancestor directory that contains the file with the given name by walking up
from the starting path.
:param target_file_name: Name of the file to find.
:param start_path: path to start looking for the file.
:param max_scan_up_range: maximum number of directories to scan upwards when searching for target file
if the value is 0, then there is no max
:return: Path to the directory containing file or None if not found.
"""
ancestor_file = find_ancestor_file(target_file_name, start_path, max_scan_up_range)
return ancestor_file.parent if ancestor_file else None

@ -124,7 +124,7 @@ class TestEnableGemCommand:
return json.loads(TEST_O3DE_MANIFEST_JSON_PAYLOAD)
return None
def get_project_json_data(json_data: pathlib.Path, project_path: pathlib.Path):
def get_project_json_data(project_path: pathlib.Path):
return self.enable_gem.project_data
def get_gem_json_data(gem_path: pathlib.Path, project_path: pathlib.Path):

@ -263,6 +263,7 @@ class TestCreateTemplate:
s.write(templated_contents)
template_dest_path = engine_root / instantiated_name
# Skip registeration in test
with patch('uuid.uuid4', return_value=uuid.uuid5(uuid.NAMESPACE_DNS, instantiated_name)) as uuid4_mock:
result = create_from_template_func(template_dest_path, template_path=template_default_folder, force=True,
keep_license_text=keep_license_text, **create_from_template_kwargs)
@ -345,7 +346,7 @@ class TestCreateTemplate:
template_json_contents = json.dumps(template_json_dict, indent=4)
self.instantiate_template_wrapper(tmpdir, engine_template.create_project, 'TestProject', concrete_contents,
templated_contents, keep_license_text, force, expect_failure,
template_json_contents, template_file_map, project_name='TestProject')
template_json_contents, template_file_map, project_name='TestProject', no_register=True)
@pytest.mark.parametrize(
@ -379,6 +380,8 @@ class TestCreateTemplate:
"isTemplated": True,
"isOptional": False
})
#Convert dict back to string
template_json_contents = json.dumps(template_json_dict, indent=4)
self.instantiate_template_wrapper(tmpdir, engine_template.create_gem, 'TestGem', concrete_contents,
templated_contents, keep_license_text, force, expect_failure,
template_json_contents, gem_name='TestGem')
template_json_contents, template_file_map, gem_name='TestGem', no_register=True)

@ -112,3 +112,139 @@ class TestRegisterThisEngine:
result = register._run_register(args)
assert result == expected_result
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_PROJECT_JSON_PAYLOAD = '''
{
"project_name": "TestProject",
"engine": "o3de",
"external_subdirectories": []
}
'''
TEST_ENGINE_JSON_PAYLOAD = '''
{
"engine_name": "o3de",
"external_subdirectories": [],
"projects": [],
"templates": []
}
'''
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": [],
"external_subdirectories": [],
"templates": [],
"restricted": [],
"repos": [],
"engines": [],
"engines_path": {}
}
'''
@pytest.fixture(scope='class')
def init_register_gem_data(request):
request.cls.o3de_manifest_data = json.loads(TEST_O3DE_MANIFEST_JSON_PAYLOAD)
request.cls.project_data = json.loads(TEST_PROJECT_JSON_PAYLOAD)
request.cls.engine_data = json.loads(TEST_ENGINE_JSON_PAYLOAD)
@pytest.mark.usefixtures('init_register_gem_data')
class TestRegisterGem:
engine_path = pathlib.PurePath('o3de')
project_path = pathlib.PurePath('TestProject')
@staticmethod
def get_gem_json_data(gem_path: pathlib.Path = None):
return json.loads(TEST_GEM_JSON_PAYLOAD)
@pytest.mark.parametrize("gem_path, expected_manifest_file, expected_result", [
pytest.param(pathlib.PurePath('TestGem'), pathlib.PurePath('o3de_manifest.json'), 0),
pytest.param(project_path / 'TestGem', pathlib.PurePath('project.json'), 0),
pytest.param(engine_path / 'TestGem', pathlib.PurePath('engine.json'), 0),
])
def test_register_gem_auto_detects_manifest_update(self, gem_path, expected_manifest_file,expected_result):
def save_o3de_manifest(manifest_data: dict, manifest_path: pathlib.Path = None) -> bool:
if manifest_path == TestRegisterGem.project_path / 'project.json':
self.project_data = manifest_data
elif manifest_path == TestRegisterGem.engine_path / 'engine.json':
self.engine_data = manifest_data
else:
self.o3de_manifest_data = manifest_data
return True
def load_o3de_manifest(manifest_path: pathlib.Path = None) -> dict:
if manifest_path == TestRegisterGem.project_path:
return self.project_data
elif manifest_path == TestRegisterGem.engine_path:
return self.engine_data
return self.o3de_manifest_data
def get_engine_json_data(engine_path: pathlib.Path = None):
return json.loads(TEST_ENGINE_JSON_PAYLOAD)
def get_project_json_data(project_path: pathlib.Path = None):
return json.loads(TEST_PROJECT_JSON_PAYLOAD)
def find_ancestor_dir(target_file_name: pathlib.PurePath, start_path: pathlib.Path):
try:
if target_file_name == pathlib.PurePath('project.json')\
and start_path.relative_to(TestRegisterGem.project_path):
return TestRegisterGem.project_path
except ValueError:
pass
try:
if target_file_name == pathlib.PurePath('engine.json')\
and start_path.relative_to(TestRegisterGem.engine_path):
return TestRegisterGem.engine_path
except ValueError:
pass
return None
with patch('o3de.manifest.load_o3de_manifest', side_effect=load_o3de_manifest) as _1,\
patch('o3de.manifest.save_o3de_manifest', side_effect=save_o3de_manifest) as _2,\
patch('o3de.manifest.get_engine_json_data', side_effect=get_engine_json_data) as _3,\
patch('o3de.manifest.get_project_json_data', side_effect=get_project_json_data) as _4,\
patch('o3de.manifest.get_gem_json_data', side_effect=TestRegisterGem.get_gem_json_data) as _5,\
patch('o3de.utils.find_ancestor_dir_containing_file', side_effect=find_ancestor_dir) as _6,\
patch('pathlib.Path.is_dir', return_value=True) as _7,\
patch('o3de.validation.valid_o3de_gem_json', return_value=True) as _8:
result = register.register(gem_path=gem_path)
assert result == expected_result
if expected_manifest_file == pathlib.PurePath('o3de_manifest.json'):
assert gem_path in map(lambda subdir: pathlib.PurePath(subdir),
self.o3de_manifest_data.get('external_subdirectories', []))
elif expected_manifest_file == pathlib.PurePath('project.json'):
assert gem_path in map(lambda subdir: pathlib.PurePath(TestRegisterGem.project_path) / subdir,
self.project_data.get('external_subdirectories', []))
elif expected_manifest_file == pathlib.PurePath('engine.json'):
assert gem_path in map(lambda subdir: pathlib.PurePath(TestRegisterGem.engine_path) / subdir,
self.engine_data.get('external_subdirectories', []))

Loading…
Cancel
Save