Adding o3de.py subcommands for engine.json and gem.json modification (#2411)

The new commands are edit-engine-properties and edit-gem-properties
They can modify specific fields within these manifest files, that should
affect build system.

More importantly, this commands can be used to modify the "engine_name"
field in the engine.json and the "gem_name" field in the gem.json

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

@ -28,7 +28,7 @@ def add_args(parser, subparsers) -> None:
o3de_package_dir = (script_dir / 'o3de').resolve()
# add the scripts/o3de directory to the front of the sys.path
sys.path.insert(0, str(o3de_package_dir))
from o3de import engine_template, global_project, register, print_registration, get_registration, \
from o3de import engine_properties, engine_template, gem_properties, global_project, register, print_registration, get_registration, \
enable_gem, disable_gem, project_properties, sha256
# Remove the temporarily added path
sys.path = sys.path[1:]
@ -52,9 +52,15 @@ def add_args(parser, subparsers) -> None:
# remove a gem from a project
disable_gem.add_args(subparsers)
# modify project properties
# modify engine properties
engine_properties.add_args(subparsers)
# modify project properties
project_properties.add_args(subparsers)
# modify gem properties
gem_properties.add_args(subparsers)
# sha256
sha256.add_args(subparsers)

@ -0,0 +1,82 @@
#
# 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 os
import pathlib
import sys
import logging
from o3de import manifest, utils
logger = logging.getLogger()
logging.basicConfig()
def edit_engine_props(engine_path: pathlib.Path = None,
engine_name: str = None,
new_name: str = None,
new_version: str = None) -> int:
if not engine_path and not engine_name:
logger.error(f'Either a engine path or a engine name must be supplied to lookup engine.json')
return 1
if not engine_path:
engine_path = manifest.get_registered(engine_name=engine_name)
if not engine_path:
logger.error(f'Error unable locate engine path: No engine with name {engine_name} is registered in any manifest')
return 1
engine_json_data = manifest.get_engine_json_data(engine_path=engine_path)
if not engine_json_data:
return 1
if new_name:
if not utils.validate_identifier(new_name):
logger.error(f'Engine name must be fewer than 64 characters, contain only alphanumeric, "_" or "-"'
f' characters, and start with a letter. {new_name}')
return 1
engine_json_data['engine_name'] = new_name
if new_version:
engine_json_data['O3DEVersion'] = new_version
return 0 if manifest.save_o3de_manifest(engine_json_data, pathlib.Path(engine_path) / 'engine.json') else 1
def _edit_engine_props(args: argparse) -> int:
return edit_engine_props(args.engine_path,
args.engine_name,
args.engine_new_name,
args.engine_version)
def add_parser_args(parser):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-ep', '--engine-path', type=pathlib.Path, required=False,
help='The path to the engine.')
group.add_argument('-en', '--engine-name', type=str, required=False,
help='The name of the engine.')
group = parser.add_argument_group('properties', 'arguments for modifying individual engine properties.')
group.add_argument('-enn', '--engine-new-name', type=str, required=False,
help='Sets the name for the engine.')
group.add_argument('-ev', '--engine-version', type=str, required=False,
help='Sets the version for the engine.')
parser.set_defaults(func=_edit_engine_props)
def add_args(subparsers) -> None:
enable_engine_props_subparser = subparsers.add_parser('edit-engine-properties')
add_parser_args(enable_engine_props_subparser)
def main():
the_parser = argparse.ArgumentParser()
add_parser_args(the_parser)
the_args = the_parser.parse_args()
ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1
sys.exit(ret)
if __name__ == "__main__":
main()

@ -0,0 +1,163 @@
#
# 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 os
import pathlib
import sys
import logging
from o3de import manifest, utils
logger = logging.getLogger()
logging.basicConfig()
def update_values_in_key_list(existing_values: list, new_values: list or str, remove_values: list or str,
replace_values: list or str):
"""
Updates values within a list by first appending values in the new_values list, removing values in the remove_values
list and then replacing values in the replace_values list
:param existing_values list with existing values to modify
:param new_values list with values to add to the existing value list
:param remove_values list with values to remove from the existing value list
:param replace_values list with values to replace in the existing value list
returns updated existing value list
"""
if new_values:
new_values = new_values.split() if isinstance(new_values, str) else new_values
existing_values.extend(new_values)
if remove_values:
remove_values = remove_values.split() if isinstance(remove_values, str) else remove_values
existing_values = list(filter(lambda value: value not in remove_values, existing_values))
if replace_values:
replace_values = replace_values.split() if isinstance(replace_values, str) else replace_values
existing_values = replace_values
return existing_values
def edit_gem_props(gem_path: pathlib.Path = None,
gem_name: str = None,
new_name: str = None,
new_display: str = None,
new_origin: str = None,
new_type: str = None,
new_summary: str = None,
new_icon: str = None,
new_requirements: str = None,
new_tags: list or str = None,
remove_tags: list or str = None,
replace_tags: list or str = None,
) -> int:
if not gem_path and not gem_name:
logger.error(f'Either a gem path or a gem name must be supplied to lookup gem.json')
return 1
if not gem_path:
gem_path = manifest.get_registered(gem_name=gem_name)
if not gem_path:
logger.error(f'Error unable locate gem path: No gem with name {gem_name} is registered in any manifest')
return 1
gem_json_data = manifest.get_gem_json_data(gem_path=gem_path)
if not gem_json_data:
return 1
update_key_dict = {}
if new_name:
if not utils.validate_identifier(new_name):
logger.error(f'Engine name must be fewer than 64 characters, contain only alphanumeric, "_" or "-"'
f' characters, and start with a letter. {new_name}')
return 1
update_key_dict['gem_name'] = new_name
if new_display:
update_key_dict['display_name'] = new_display
if new_origin:
update_key_dict['origin'] = new_origin
if new_type:
update_key_dict['type'] = new_type
if new_summary:
update_key_dict['summary'] = new_summary
if new_icon:
update_key_dict['icon_path'] = new_icon
if new_requirements:
update_key_dict['icon_requirements'] = new_requirements
update_key_dict['user_tags'] = update_values_in_key_list(gem_json_data.get('user_tags', []), new_tags,
remove_tags, replace_tags)
gem_json_data.update(update_key_dict)
return 0 if manifest.save_o3de_manifest(gem_json_data, pathlib.Path(gem_path) / 'gem.json') else 1
def _edit_gem_props(args: argparse) -> int:
return edit_gem_props(args.gem_path,
args.gem_name,
args.gem_new_name,
args.gem_display,
args.gem_origin,
args.gem_type,
args.gem_summary,
args.gem_icon,
args.gem_requirements,
args.add_tags,
args.remove_tags,
args.replace_tags)
def add_parser_args(parser):
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-gp', '--gem-path', type=pathlib.Path, required=False,
help='The path to the gem.')
group.add_argument('-gn', '--gem-name', type=str, required=False,
help='The name of the gem.')
group = parser.add_argument_group('properties', 'arguments for modifying individual gem properties.')
group.add_argument('-gnn', '--gem-new-name', type=str, required=False,
help='Sets the name for the gem.')
group.add_argument('-gd', '--gem-display', type=str, required=False,
help='Sets the gem display name.')
group.add_argument('-go', '--gem-origin', type=str, required=False,
help='Sets description for gem origin.')
group.add_argument('-gt', '--gem-type', type=str, required=False, choices=['Code', 'Tool', 'Asset'],
help='Sets the gem type. Can only be one of the selected choices')
group.add_argument('-gs', '--gem-summary', type=str, required=False,
help='Sets the summary description of the gem.')
group.add_argument('-gi', '--gem-icon', type=str, required=False,
help='Sets the path to the projects icon resource.')
group.add_argument('-gr', '--gem-requirements', type=str, required=False,
help='Sets the description of the requirements needed to use the gem')
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('-at', '--add-tags', type=str, nargs='*', required=False,
help='Adds tag(s) to user_tags property. Can be specified multiple times')
group.add_argument('-dt', '--remove-tags', type=str, nargs='*', required=False,
help='Removes tag(s) from the user_tags property. Can be specified multiple times')
group.add_argument('-rt', '--replace-tags', type=str, nargs='*', required=False,
help='Replace tag(s) in user_tags property. Can be specified multiple times')
parser.set_defaults(func=_edit_gem_props)
def add_args(subparsers) -> None:
enable_gem_props_subparser = subparsers.add_parser('edit-gem-properties')
add_parser_args(enable_gem_props_subparser)
def main():
the_parser = argparse.ArgumentParser()
add_parser_args(the_parser)
the_args = the_parser.parse_args()
ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1
sys.exit(ret)
if __name__ == "__main__":
main()

@ -26,7 +26,7 @@ def get_project_props(name: str = None, path: pathlib.Path = None) -> dict:
return None
return proj_json
def edit_project_props(proj_path: pathlib.Path,
def edit_project_props(proj_path: pathlib.Path = None,
proj_name: str = None,
new_name: str = None,
new_origin: str = None,
@ -71,8 +71,8 @@ def edit_project_props(proj_path: pathlib.Path,
tag_list = replace_tags.split() if isinstance(replace_tags, str) else replace_tags
proj_json['user_tags'] = tag_list
manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path) / 'project.json')
return 0
return 0 if manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path) / 'project.json') else 1
def _edit_project_props(args: argparse) -> int:
return edit_project_props(args.project_path,

@ -39,6 +39,13 @@ ly_add_pytest(
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
ly_add_pytest(
NAME o3de_engine_properties
PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_engine_properties.py
TEST_SUITE smoke
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
ly_add_pytest(
NAME o3de_project_properties
PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_project_properties.py
@ -46,6 +53,13 @@ ly_add_pytest(
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
ly_add_pytest(
NAME o3de_gem_properties
PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_gem_properties.py
TEST_SUITE smoke
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
ly_add_pytest(
NAME o3de_template
PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_engine_template.py

@ -0,0 +1,72 @@
#
# 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 json
import pytest
import pathlib
from unittest.mock import patch
from o3de import engine_properties
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"
]
}
'''
@pytest.fixture(scope='class')
def init_engine_json_data(request):
class EngineJsonData:
def __init__(self):
self.data = json.loads(TEST_ENGINE_JSON_PAYLOAD)
request.cls.engine_json = EngineJsonData()
@pytest.mark.usefixtures('init_engine_json_data')
class TestEditEngineProperties:
@pytest.mark.parametrize("engine_path, engine_name, engine_new_name, engine_version, expected_result", [
pytest.param(pathlib.PurePath('D:/o3de'), None, 'o3de-install', '1.0.0.0', 0),
pytest.param(None, 'o3de-install', 'o3de-sdk', '1.0.0.1', 0),
pytest.param(None, 'o3de-sdk', None, '2.0.0.0', 0),
]
)
def test_edit_engine_properties(self, engine_path, engine_name, engine_new_name, engine_version, expected_result):
def get_engine_json_data(engine_path: pathlib.Path) -> dict:
return self.engine_json.data
def get_engine_path(engine_name: str) -> pathlib.Path:
return pathlib.Path('D:/o3de')
def save_o3de_manifest(new_engine_data: dict, engine_path: pathlib.Path) -> bool:
self.engine_json.data = new_engine_data
return True
with patch('o3de.manifest.get_engine_json_data', side_effect=get_engine_json_data) as get_engine_json_data_patch, \
patch('o3de.manifest.save_o3de_manifest', side_effect=save_o3de_manifest) as save_o3de_manifest_patch, \
patch('o3de.manifest.get_registered', side_effect=get_engine_path) as get_registered_patch:
result = engine_properties.edit_engine_props(engine_path, engine_name, engine_new_name, engine_version)
assert result == expected_result
if engine_new_name:
assert self.engine_json.data.get('engine_name', '') == engine_new_name
if engine_version:
assert self.engine_json.data.get('O3DEVersion', '') == engine_version

@ -0,0 +1,99 @@
#
# 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 json
import pytest
import pathlib
from unittest.mock import patch
from o3de import gem_properties
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": ""
}
'''
@pytest.fixture(scope='class')
def init_gem_json_data(request):
class GemJsonData:
def __init__(self):
self.data = json.loads(TEST_GEM_JSON_PAYLOAD)
request.cls.gem_json = GemJsonData()
@pytest.mark.usefixtures('init_gem_json_data')
class TestEditGemProperties:
@pytest.mark.parametrize("gem_path, gem_name, gem_new_name, gem_display, gem_origin,\
gem_type, gem_summary, gem_icon, gem_requirements,\
add_tags, remove_tags, replace_tags, expected_tags, expected_result", [
pytest.param(pathlib.PurePath('D:/TestProject'),
None, 'TestGem2', 'New Gem Name', 'O3DE', 'Code', 'Gem that exercises Default Gem Template',
'preview.png', '',
['Physics', 'Rendering', 'Scripting'], None, None, ['TestGem', 'Physics', 'Rendering', 'Scripting'],
0),
pytest.param(None,
'TestGem2', None, 'New Gem Name', 'O3DE', 'Asset', 'Gem that exercises Default Gem Template',
'preview.png', '', None, ['Physics'], None, ['TestGem', 'Rendering', 'Scripting'], 0),
pytest.param(None,
'TestGem2', None, 'New Gem Name', 'O3DE', 'Tool', 'Gem that exercises Default Gem Template',
'preview.png', '', None, None, ['Animation', 'TestGem'], ['Animation', 'TestGem'], 0)
]
)
def test_edit_gem_properties(self, gem_path, gem_name, gem_new_name, gem_display, gem_origin,
gem_type, gem_summary, gem_icon, gem_requirements,
add_tags, remove_tags, replace_tags,
expected_tags, expected_result):
def get_gem_json_data(gem_path: pathlib.Path) -> dict:
return self.gem_json.data
def get_gem_path(gem_name: str) -> pathlib.Path:
return pathlib.Path('D:/TestProject')
def save_o3de_manifest(new_gem_data: dict, gem_path: pathlib.Path) -> bool:
self.gem_json.data = new_gem_data
return True
with patch('o3de.manifest.get_gem_json_data', side_effect=get_gem_json_data) as get_gem_json_data_patch, \
patch('o3de.manifest.save_o3de_manifest', side_effect=save_o3de_manifest) as save_o3de_manifest_patch, \
patch('o3de.manifest.get_registered', side_effect=get_gem_path) as get_registered_patch:
result = gem_properties.edit_gem_props(gem_path, gem_name, gem_new_name, gem_display, gem_origin,
gem_type, gem_summary, gem_icon, gem_requirements,
add_tags, remove_tags, replace_tags)
assert result == expected_result
if gem_new_name:
assert self.gem_json.data.get('gem_name', '') == gem_new_name
if gem_display:
assert self.gem_json.data.get('display_name', '') == gem_display
if gem_origin:
assert self.gem_json.data.get('origin', '') == gem_origin
if gem_type:
assert self.gem_json.data.get('type', '') == gem_type
if gem_summary:
assert self.gem_json.data.get('summary', '') == gem_summary
if gem_icon:
assert self.gem_json.data.get('icon_path', '') == gem_icon
if gem_requirements:
assert self.gem_json.data.get('requirments', '') == gem_requirements
assert set(self.gem_json.data.get('user_tags', [])) == set(expected_tags)

@ -58,8 +58,9 @@ class TestEditProjectProperties:
return None
return self.project_json.data
def save_o3de_manifest(new_proj_data: dict, project_path) -> None:
def save_o3de_manifest(new_proj_data: dict, project_path) -> bool:
self.project_json.data = new_proj_data
return True
with patch('o3de.manifest.get_project_json_data', side_effect=get_project_json_data) as get_project_json_data_patch, \
patch('o3de.manifest.save_o3de_manifest', side_effect=save_o3de_manifest) as save_o3de_manifest_patch:

Loading…
Cancel
Save