Updated the global_project.py script to be able to specify and output

path when setting the global project path
Also updated that script to support an input path when reading the
global project path.
Added a unit test for the global_projecy.py "set-global-project" command
This commit is contained in:
lumberyard-employee-dm
2021-05-27 21:20:19 -05:00
parent fc61ca2d0c
commit 74464afbf3
9 changed files with 192 additions and 204 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ def disable_gem_in_project(gem_name: str = None,
project_path = manifest.get_registered(project_name=project_name)
if not project_path:
logger.error(f'Unable to locate project path from the registered manifest.json files:'
f' {str(pathlib.Path.home() / ".o3de/manifest.json")}, engine.json')
f' {str(pathlib.Path("~/.o3de/o3de_manifest.json").expanduser())}, engine.json')
return 1
project_path = pathlib.Path(project_path).resolve()
+1 -1
View File
@@ -117,7 +117,7 @@ def enable_gem_in_project(gem_name: str = None,
gem_path = manifest.get_registered(gem_name=gem_name)
if not gem_path:
logger.error(f'Unable to locate gem path from the registered manifest.json files:'
f' {str(pathlib.Path.home() / ".o3de/manifest.json")},'
f' {str(pathlib.Path( "~/.o3de/o3de_manifest.json").expanduser())},'
f' {project_path / "project.json"}, engine.json')
return 1
+134 -87
View File
@@ -16,94 +16,122 @@ import sys
import re
import pathlib
import json
from o3de import manifest
from o3de import manifest, validation
logger = logging.getLogger()
logging.basicConfig()
DEFAULT_BOOTSTRAP_SETREG = pathlib.Path('~/.o3de/Registry/bootstrap.setreg').expanduser()
PROJECT_PATH_KEY = ('Amazon', 'AzCore', 'Bootstrap', 'project_path')
def set_global_project(project_name: str or None,
project_path: str or pathlib.Path or None) -> int:
def get_json_data(input_path: pathlib.Path):
setreg_json_data = {}
# If the output_path exist validate that it is a valid json file
if input_path.is_file():
with input_path.open('r') as f:
try:
setreg_json_data = json.load(f)
except json.JSONDecodeError as e:
logger.error(f'The file: {input_path} is not a valid json file: {str(e)}')
return setreg_json_data
def set_global_project(output_path: pathlib.Path,
project_name: str = None,
project_path: pathlib.Path = None,
force: bool = False) -> int:
"""
set what the current project is
:param project_name: the name of the project you want to set, resolves project_path
:param project_path: the path of the project you want to set
Adds a project path the a settings registry file in the users ~/.o3de/Registry directory
:param output_path: path to .setreg file to store project_path value into
:param project_name: name of the project to lookup path for
:param project_path: path to the project to add to .setreg file
:param force: if set, the project path will be set within the .setreg file regardless of if the path doesn't exist
:return: 0 for success or non 0 failure code
"""
if project_path and project_name:
logger.error(f'Project Name and Project Path provided, these are mutually exclusive.')
return 1
# we need either a project name or path
if not project_name and not project_path:
logger.error('Must specify either a Project name or Project Path.')
logger.error(f'Must either specify a Project path or Project Name.')
return 1
# if project name resolve it into a path
if project_name and not project_path:
project_path = manifest.get_registered(project_name=project_name)
if not project_path:
logger.error(f'Project Path {project_path} has not been registered.')
logger.error(
f'The project name has been supplied. Unable to locate project path from the registered manifest.json files:'
f' {str(pathlib.Path("~/.o3de/o3de_manifest.json").expanduser())}, engine.json\n'
'A The --project-path parameter can be used directly to skip checking the manifest')
return 1
project_path = pathlib.Path(project_path).resolve()
# Only perform project path validations when force=False
if not force:
if not project_path.is_dir():
logger.error(f'Project path {project_path} is not a folder.')
return 1
bootstrap_setreg_file = manifest.get_o3de_registry_folder() / 'bootstrap.setreg'
if bootstrap_setreg_file.is_file():
with bootstrap_setreg_file.open('r') as f:
# Validate that the supplied path points contains a valid project.json
if not validation.valid_o3de_project_json(project_path / 'project.json'):
logger.error(f'The supplied project path does not contain a valid project.json.\n'
f'The Path will not be set')
return 1
# If the output_path exist validate that it is a valid json file and read it's json data
setreg_json_data = get_json_data(output_path)
if output_path.is_file():
with output_path.open('r') as f:
try:
json_data = json.load(f)
except json.JSONDecodeError as e:
logger.error(f'Bootstrap.setreg failed to load: {str(e)}')
else:
try:
json_data["Amazon"]["AzCore"]["Bootstrap"]["project_path"] = project_path
except KeyError as e:
logger.error(f'Bootstrap.setreg failed to load: {str(e)}')
else:
try:
os.unlink(bootstrap_setreg_file)
except OSError as e:
logger.error(f'Failed to unlink bootstrap file {bootstrap_setreg_file}: {str(e)}')
return 1
else:
json_data = {}
json_data.update({"Amazon":{"AzCore":{"Bootstrap":{"project_path":project_path.as_posix()}}}})
setreg_json_data = json.load(f)
except (json.JSONDecodeError) as e:
logger.error(f'The output file: {output_path} is not a valid json file: {str(e)}')
return 1
with bootstrap_setreg_file.open('w') as s:
s.write(json.dumps(json_data, indent=4))
# Add a json dictionary that will be merged with any existing json data from the .setreg file
merge_json_data = {}
json_object_iter = merge_json_data
for json_key in PROJECT_PATH_KEY[:-1]:
# Add the parent json object for the key to update
json_object_iter = json_object_iter.setdefault(json_key, {})
# Set the project path value here
json_object_iter[PROJECT_PATH_KEY[-1]] = project_path.as_posix()
setreg_json_data.update(merge_json_data)
# Create the parent directories
if output_path.parent:
output_path.parent.mkdir(parents=True, exist_ok=True)
try:
with output_path.open('w') as s:
s.write(json.dumps(setreg_json_data, indent=4) + '\n')
except OSError as e:
logger.error(f'Failed to write project path {project_path} to file {output_path}: {str(e)}')
return 1
return 0
def get_global_project() -> pathlib.Path or None:
def get_global_project(input_path: pathlib.Path) -> pathlib.Path or None:
"""
get what the current project set is
Retrieves the /Amazon/AzCore/Bootstrap/project_path key from the supplied file path
:return: project_path or None on failure
"""
bootstrap_setreg_file = manifest.get_o3de_registry_folder() / 'bootstrap.setreg'
if not bootstrap_setreg_file.is_file():
logger.error(f'Bootstrap.setreg file {bootstrap_setreg_file} does not exist.')
return None
setreg_json_data = get_json_data(input_path)
with bootstrap_setreg_file.open('r') as f:
try:
json_data = json.load(f)
except json.JSONDecodeError as e:
logger.error(f'Bootstrap.setreg failed to load: {str(e)}')
else:
try:
project_path = json_data["Amazon"]["AzCore"]["Bootstrap"]["project_path"]
except KeyError as e:
logger.error(f'Bootstrap.setreg cannot find Amazon:AzCore:Bootstrap:project_path: {str(e)}')
else:
return pathlib.Path(project_path).resolve()
try:
# Iterate over each element of the tuple and read the json key from each successive json object
json_object_iter = setreg_json_data
for json_key in PROJECT_PATH_KEY:
json_object_iter = json_object_iter[json_key]
except KeyError as e:
logger.error(f'Cannot read key /{"/".join(PROJECT_PATH_KEY)} from file {input_path.as_posix()}: {str(e)}')
else:
project_path = json_object_iter
return pathlib.Path(project_path).resolve()
return None
def _run_get_global_project(args: argparse) -> int:
if args.override_home_folder:
manifest.override_home_folder = args.override_home_folder
project_path = get_global_project()
project_path = get_global_project(args.input_path)
if project_path:
print(project_path.as_posix())
return 0
@@ -111,51 +139,66 @@ def _run_get_global_project(args: argparse) -> int:
def _run_set_global_project(args: argparse) -> int:
if args.override_home_folder:
manifest.override_home_folder = args.override_home_folder
return set_global_project(args.output_path,
args.project_name,
args.project_path,
args.force)
return set_global_project(args.project_name,
args.project_path)
def add_parser_args(get_project_parser, set_project_parser):
"""
add_parser_args is called to add arguments to each command such that it can be
invoked locally or added by a central python file.
Ex. Directly run from this file alone with: python global_project.py --project-path "D:/TestProject"
:param parser: the caller passes an argparse parser like instance to this method
"""
# get-current-project
get_project_parser.add_argument('-i', '--input-path', type=pathlib.Path, required=False, default=DEFAULT_BOOTSTRAP_SETREG,
help=f'Optional path to file to read /{"/".join(PROJECT_PATH_KEY)} key from.'
f' If not supplied, then {DEFAULT_BOOTSTRAP_SETREG} is used instead')
get_project_parser.set_defaults(func=_run_get_global_project)
# set-current-project
group = set_project_parser.add_mutually_exclusive_group(required=True)
group.add_argument('-pp', '--project-path', type=pathlib.Path, required=False,
help='The path to the project.')
group.add_argument('-pn', '--project-name', type=str, required=False,
help='The name of the project.')
set_project_parser.add_argument('-o', '--output-path', type=pathlib.Path, required=False,
default=DEFAULT_BOOTSTRAP_SETREG,
help=f'Optional path to output file to write project_path key to. '
f'If not supplied, then {DEFAULT_BOOTSTRAP_SETREG} is used instead')
set_project_parser.add_argument('-f', '--force', action='store_true', default=False,
help=f'Force the setting of the project path in the supplied setreg file')
set_project_parser.set_defaults(func=_run_set_global_project)
def add_args(subparsers) -> None:
"""
add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be
invoked locally or aggregated by a central python file.
Ex. Directly run from this file alone with: python global_project.py set_global_project --project-name TestProject
OR
o3de.py can aggregate commands by importing global_project, call add_args and
execute: python o3de.py set_global_project --project-path C:/TestProject
add_args is called to add subparsers arguments to each command such that it can be
a central python file such as o3de.py.
It can be run from the o3de.py script as follows
call add_args and execute: python o3de.py set-global-project --project-path "D:/TestProject"
:param subparsers: the caller instantiates subparsers and passes it in here
"""
get_global_project_subparser = subparsers.add_parser('get-global-project')
get_global_project_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False,
help='By default the home folder is the user folder, override it to this folder.')
get_global_project_subparser.set_defaults(func=_run_get_global_project)
set_global_project_subparser = subparsers.add_parser('set-global-project')
group = set_global_project_subparser.add_mutually_exclusive_group(required=True)
group.add_argument('-pn', '--project-name', required=False,
help='The name of the project. If supplied this will resolve the --project-path.')
group.add_argument('-pp', '--project-path', required=False,
help='The path to the project')
set_global_project_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False,
help='By default the home folder is the user folder, override it to this folder.')
set_global_project_subparser.set_defaults(func=_run_set_global_project)
get_project_subparser = subparsers.add_parser('get-global-project')
set_project_subparser = subparsers.add_parser('set-global-project')
add_parser_args(get_project_subparser, set_project_subparser)
if __name__ == "__main__":
def main():
"""
Runs this script as standalone script
"""
# parse the command line args
the_parser = argparse.ArgumentParser()
# add subparsers
the_subparsers = the_parser.add_subparsers(help='sub-command help', dest='command', required=True)
project_subparsers = the_parser.add_subparsers(help="Commands for modifying the project path in the user's home"
" setreg files")
# add args to the parser
add_args(the_subparsers)
add_args(project_subparsers)
# parse args
the_args = the_parser.parse_args()
@@ -165,3 +208,7 @@ if __name__ == "__main__":
# return
sys.exit(ret)
if __name__ == "__main__":
main()
+6 -6
View File
@@ -142,7 +142,7 @@ def get_o3de_manifest() -> pathlib.Path:
with default_restricted_folder_json.open('w') as s:
restricted_json_data = {}
restricted_json_data.update({'restricted_name': 'o3de'})
s.write(json.dumps(restricted_json_data, indent=4))
s.write(json.dumps(restricted_json_data, indent=4) + '\n')
json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()})
default_projects_restricted_folder_json = default_projects_restricted_folder / 'restricted.json'
@@ -150,24 +150,24 @@ def get_o3de_manifest() -> pathlib.Path:
with default_projects_restricted_folder_json.open('w') as s:
restricted_json_data = {}
restricted_json_data.update({'restricted_name': 'projects'})
s.write(json.dumps(restricted_json_data, indent=4))
s.write(json.dumps(restricted_json_data, indent=4) + '\n')
default_gems_restricted_folder_json = default_gems_restricted_folder / 'restricted.json'
if not default_gems_restricted_folder_json.is_file():
with default_gems_restricted_folder_json.open('w') as s:
restricted_json_data = {}
restricted_json_data.update({'restricted_name': 'gems'})
s.write(json.dumps(restricted_json_data, indent=4))
s.write(json.dumps(restricted_json_data, indent=4) + '\n')
default_templates_restricted_folder_json = default_templates_restricted_folder / 'restricted.json'
if not default_templates_restricted_folder_json.is_file():
with default_templates_restricted_folder_json.open('w') as s:
restricted_json_data = {}
restricted_json_data.update({'restricted_name': 'templates'})
s.write(json.dumps(restricted_json_data, indent=4))
s.write(json.dumps(restricted_json_data, indent=4) + '\n')
with manifest_path.open('w') as s:
s.write(json.dumps(json_data, indent=4))
s.write(json.dumps(json_data, indent=4) + '\n')
return manifest_path
@@ -201,7 +201,7 @@ def save_o3de_manifest(json_data: dict, manifest_path: pathlib.Path = None) -> N
manifest_path = get_o3de_manifest()
with manifest_path.open('w') as s:
try:
s.write(json.dumps(json_data, indent=4))
s.write(json.dumps(json_data, indent=4) + '\n')
except OSError as e:
logger.error(f'Manifest json failed to save: {str(e)}')
+2 -6
View File
@@ -410,12 +410,8 @@ def register_project_path(json_data: dict,
if update_project_json:
project_json_data['engine'] = this_engine_json['engine_name']
utils.backup_file(project_json)
with project_json.open('w') as s:
try:
s.write(json.dumps(project_json_data, indent=4))
except OSError as e:
logger.error(f'Project json failed to save: {str(e)}')
return 1
if not manifest.save_o3de_manifest(project_json_data, project_path):
return 1
return 0
+1 -1
View File
@@ -51,7 +51,7 @@ def sha256(file_path: str or pathlib.Path,
utils.backup_file(json_path)
with json_path.open('w') as s:
try:
s.write(json.dumps(json_data, indent=4))
s.write(json.dumps(json_data, indent=4) + '\n')
except OSError as e:
logger.error(f'Failed to write Json path {json_path}: {str(e)}')
return 1
+7
View File
@@ -27,3 +27,10 @@ ly_add_pytest(
TEST_SUITE smoke
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
ly_add_pytest(
NAME o3de_global_project
PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_global_project.py
TEST_SUITE smoke
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
+40
View File
@@ -0,0 +1,40 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import io
import json
import logging
import pytest
import pathlib
from unittest.mock import patch
from o3de import global_project
logger = logging.getLogger()
logging.basicConfig()
DEFAULT_BOOTSTRAP_SETREG = pathlib.Path('~/.o3de/Registry/bootstrap.setreg').expanduser()
PROJECT_PATH_KEY = ('Amazon', 'AzCore', 'Bootstrap', 'project_path')
class TestSetGlobalProject:
@pytest.mark.parametrize(
"output_path, project_path, force, expected_result", [
pytest.param(pathlib.Path('~/.o3de/Registry/bootstrap.setreg'), pathlib.Path('A:/'), False, False),
pytest.param(pathlib.Path('~/.o3de/Registry/bootstrap.setreg'), pathlib.Path('A:/'), True, True)
]
)
def test_set_global_project_non_existent_project_path(self, output_path, project_path, force, expected_result):
with patch('pathlib.Path.open', return_value=io.StringIO()) as pathlib_open_mock:
result = global_project.set_global_project(output_path, project_path=project_path, force=force) == 0
assert result == expected_result
@@ -1,102 +0,0 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import os
import pytest
from . import current_project
TEST_BOOTSTRAP_CONTENT_1 = """
project_path = Game1
foo = bar
key1 = value1
key2 = value2
assets = pc
"""
TEST_BOOTSTRAP_CONTENT_2 = """
project_path=Game1
foo = bar
key1 = value1
key2 = value2
assets = pc
"""
TEST_BOOTSTRAP_CONTENT_3 = """
project_path= Game1
foo = bar
key1 = value1
key2 = value2
assets = pc
"""
TEST_BOOTSTRAP_CONTENT_4 = """
project_path =Game1
foo = bar
key1 = value1
key2 = value2
assets = pc
"""
TEST_BOOTSTRAP_CONTENT_5 = """
project_path = Game1
foo = bar
key1 = value1
key2 = value2
assets = pc
"""
@pytest.mark.parametrize(
"contents, expected_result", [
pytest.param(TEST_BOOTSTRAP_CONTENT_1, 'Game1'),
pytest.param(TEST_BOOTSTRAP_CONTENT_2, 'Game1'),
pytest.param(TEST_BOOTSTRAP_CONTENT_3, 'Game1'),
pytest.param(TEST_BOOTSTRAP_CONTENT_4, 'Game1'),
pytest.param(TEST_BOOTSTRAP_CONTENT_5, 'Game1'),
]
)
def test_get_current_project(tmpdir, contents, expected_result):
dev_root = str(tmpdir.join('dev').realpath()).replace('\\', '/')
os.makedirs(dev_root, exist_ok=True)
bootstrap_file = f'{dev_root}/bootstrap.cfg'
if os.path.isfile(bootstrap_file):
os.unlink(bootstrap_file)
with open(bootstrap_file, 'a') as s:
s.write(contents)
result = current_project.get_current_project(dev_root)
assert expected_result == result
@pytest.mark.parametrize(
"contents, project_to_set, expected_result", [
pytest.param(TEST_BOOTSTRAP_CONTENT_1, 'Test1', 0),
pytest.param(TEST_BOOTSTRAP_CONTENT_1, ' Test2', 0),
pytest.param(TEST_BOOTSTRAP_CONTENT_1, 'Test3 ', 0),
pytest.param(TEST_BOOTSTRAP_CONTENT_1, '/Test4', 1),
pytest.param(TEST_BOOTSTRAP_CONTENT_1, '=Test5', 1),
]
)
def test_set_current_project(tmpdir, contents, project_to_set, expected_result):
dev_root = str(tmpdir.join('dev').realpath()).replace('\\', '/')
os.makedirs(dev_root, exist_ok=True)
bootstrap_file = f'{dev_root}/bootstrap.cfg'
if os.path.isfile(bootstrap_file):
os.unlink(bootstrap_file)
with open(bootstrap_file, 'a') as s:
s.write(contents)
result = current_project.set_current_project(dev_root, project_to_set)
assert expected_result == result
if result == 0:
project_that_is_set = current_project.get_current_project(dev_root)
print(project_that_is_set)
print(project_to_set)
assert project_to_set.strip() == project_that_is_set