Merge branch 'main' into cpack_installer

This commit is contained in:
scottr
2021-06-04 06:45:43 -07:00
165 changed files with 8818 additions and 9865 deletions
-1
View File
@@ -12,5 +12,4 @@
add_subdirectory(detect_file_changes)
add_subdirectory(commit_validation)
add_subdirectory(o3de)
add_subdirectory(project_manager)
add_subdirectory(ctest)
+2 -1
View File
@@ -14,6 +14,7 @@ set -o errexit # exit on the first failure encountered
BASEDIR=$(dirname "$0")
source $BASEDIR/env_linux.sh
source $BASEDIR/clean_linux.sh
mkdir -p ${OUTPUT_DIRECTORY}
SOURCE_DIRECTORY=${PWD}
@@ -46,4 +47,4 @@ fi
echo [ci_build] cmake --build . --target ${CMAKE_TARGET} --config ${CONFIGURATION} -j $(grep -c processor /proc/cpuinfo) -- ${CMAKE_NATIVE_BUILD_ARGS}
cmake --build . --target ${CMAKE_TARGET} --config ${CONFIGURATION} -j $(grep -c processor /proc/cpuinfo) -- ${CMAKE_NATIVE_BUILD_ARGS}
popd
popd
+36 -1
View File
@@ -12,6 +12,16 @@
set -o errexit # exit on the first failure encountered
# Jenkins defines environment variables for parameters and passes "false" to variables
# that are not set. Here we clear them if they are false so we can also just define them
# from command line
if [[ "${CLEAN_ASSETS}" == "false" ]]; then
CLEAN_ASSETS=
fi
if [[ "${CLEAN_OUTPUT_DIRECTORY}" == "false" ]]; then
CLEAN_OUTPUT_DIRECTORY=
fi
if [[ -n "$CLEAN_ASSETS" ]]; then
echo "[ci_build] CLEAN_ASSETS option set"
for project in $(echo $CMAKE_LY_PROJECTS | sed "s/;/ /g")
@@ -23,10 +33,35 @@ if [[ -n "$CLEAN_ASSETS" ]]; then
done
fi
# If the node label changes, we issue a clean output since node changes can change SDK/CMake/toolchains/etc
LAST_CONFIGURE_NODE_LABEL_FILE=ci_last_node_label.txt
if [[ -n "$NODE_LABEL" ]]; then
if [[ -d $OUTPUT_DIRECTORY ]]; then
pushd $OUTPUT_DIRECTORY
if [[ -e ${LAST_CONFIGURE_NODE_LABEL_FILE} ]]; then
LAST_NODE_LABEL=$(<${LAST_CONFIGURE_NODE_LABEL_FILE})
else
LAST_NODE_LABEL=
fi
# Detect if the node label has changed
if [[ "${LAST_NODE_LABEL}" != "${NODE_LABEL}" ]]; then
echo [ci_build] Last run was done with node label \"${LAST_NODE_LABEL}\", new node label is \"${NODE_LABEL}\", forcing CLEAN_OUTPUT_DIRECTORY
CLEAN_OUTPUT_DIRECTORY=1
fi
popd
fi
fi
if [[ -n "$CLEAN_OUTPUT_DIRECTORY" ]]; then
echo "[ci_build] CLEAN_OUTPUT_DIRECTORY option set"
if [[ -d $OUTPUT_DIRECTORY ]]; then
echo "[ci_build] Deleting \"${OUTPUT_DIRECTORY}\""
rm -rf ${OUTPUT_DIRECTORY}
fi
fi
fi
mkdir -p ${OUTPUT_DIRECTORY}
# Save the node label
pushd $OUTPUT_DIRECTORY
echo "${NODE_LABEL}" > ${LAST_CONFIGURE_NODE_LABEL_FILE}
popd
+1
View File
@@ -14,6 +14,7 @@ set -o errexit # exit on the first failure encountered
BASEDIR=$(dirname "$0")
source $BASEDIR/env_mac.sh
source $BASEDIR/clean_mac.sh
mkdir -p ${OUTPUT_DIRECTORY}
SOURCE_DIRECTORY=${PWD}
+36 -1
View File
@@ -12,6 +12,16 @@
set -o errexit # exit on the first failure encountered
# Jenkins defines environment variables for parameters and passes "false" to variables
# that are not set. Here we clear them if they are false so we can also just define them
# from command line
if [[ "${CLEAN_ASSETS}" == "false" ]]; then
CLEAN_ASSETS=
fi
if [[ "${CLEAN_OUTPUT_DIRECTORY}" == "false" ]]; then
CLEAN_OUTPUT_DIRECTORY=
fi
if [[ -n "$CLEAN_ASSETS" ]]; then
echo "[ci_build] CLEAN_ASSETS option set"
for project in $(echo $CMAKE_LY_PROJECTS | sed "s/;/ /g")
@@ -23,10 +33,35 @@ if [[ -n "$CLEAN_ASSETS" ]]; then
done
fi
# If the node label changes, we issue a clean output since node changes can change SDK/CMake/toolchains/etc
LAST_CONFIGURE_NODE_LABEL_FILE=ci_last_node_label.txt
if [[ -n "$NODE_LABEL" ]]; then
if [[ -d $OUTPUT_DIRECTORY ]]; then
pushd $OUTPUT_DIRECTORY
if [[ -e ${LAST_CONFIGURE_NODE_LABEL_FILE} ]]; then
LAST_NODE_LABEL=$(<${LAST_CONFIGURE_NODE_LABEL_FILE})
else
LAST_NODE_LABEL=
fi
# Detect if the node label has changed
if [[ "${LAST_NODE_LABEL}" != "${NODE_LABEL}" ]]; then
echo [ci_build] Last run was done with node label \"${LAST_NODE_LABEL}\", new node label is \"${NODE_LABEL}\", forcing CLEAN_OUTPUT_DIRECTORY
CLEAN_OUTPUT_DIRECTORY=1
fi
popd
fi
fi
if [[ -n "$CLEAN_OUTPUT_DIRECTORY" ]]; then
echo "[ci_build] CLEAN_OUTPUT_DIRECTORY option set"
if [[ -d $OUTPUT_DIRECTORY ]]; then
echo "[ci_build] Deleting \"${OUTPUT_DIRECTORY}\""
rm -rf ${OUTPUT_DIRECTORY}
fi
fi
fi
mkdir -p ${OUTPUT_DIRECTORY}
# Save the node label
pushd $OUTPUT_DIRECTORY
echo "${NODE_LABEL}" > ${LAST_CONFIGURE_NODE_LABEL_FILE}
popd
@@ -13,6 +13,7 @@ REM
SETLOCAL EnableDelayedExpansion
CALL %~dp0env_windows.cmd
CALL %~dp0clean_windows.cmd
IF NOT EXIST "%OUTPUT_DIRECTORY%" (
MKDIR %OUTPUT_DIRECTORY%.
@@ -70,4 +71,4 @@ EXIT /b 0
:error
POPD
EXIT /b 1
EXIT /b 1
@@ -12,6 +12,16 @@ REM
SETLOCAL EnableDelayedExpansion
REM Jenkins defines environment variables for parameters and passes "false" to variables
REM that are not set. Here we clear them if they are false so we can also just define them
REM from command line
IF "%CLEAN_ASSETS%"=="false" (
set CLEAN_ASSETS=
)
IF "%CLEAN_OUTPUT_DIRECTORY%"=="false" (
set CLEAN_OUTPUT_DIRECTORY=
)
IF DEFINED CLEAN_ASSETS (
ECHO [ci_build] CLEAN_ASSETS option set
FOR %%P in (%CMAKE_LY_PROJECTS%) do (
@@ -19,7 +29,26 @@ IF DEFINED CLEAN_ASSETS (
ECHO [ci_build] Deleting "%%P\Cache"
DEL /s /q /f %%P\Cache 1>nul
)
)
)
)
REM If the node label changes, we issue a clean output since node changes can change SDK/CMake/toolchains/etc
SET LAST_CONFIGURE_NODE_LABEL_FILE=ci_last_node_label.txt
IF DEFINED NODE_LABEL (
IF EXIST %OUTPUT_DIRECTORY% (
PUSHD %OUTPUT_DIRECTORY%
IF EXIST !LAST_CONFIGURE_NODE_LABEL_FILE! (
FOR /F "delims=" %%x in (%LAST_CONFIGURE_NODE_LABEL_FILE%) DO SET LAST_NODE_LABEL=%%x
) ELSE (
SET LAST_NODE_LABEL=
)
REM Detect if the node label has changed
IF !LAST_NODE_LABEL! NEQ !NODE_LABEL! (
ECHO [ci_build] Last run was done with node label "!LAST_NODE_LABEL!", new node label is "!NODE_LABEL!", forcing CLEAN_OUTPUT_DIRECTORY
SET CLEAN_OUTPUT_DIRECTORY=1
)
POPD
)
)
IF DEFINED CLEAN_OUTPUT_DIRECTORY (
@@ -28,4 +57,12 @@ IF DEFINED CLEAN_OUTPUT_DIRECTORY (
ECHO [ci_build] Deleting "%OUTPUT_DIRECTORY%"
DEL /s /q /f %OUTPUT_DIRECTORY% 1>nul
)
)
)
IF NOT EXIST "%OUTPUT_DIRECTORY%" (
MKDIR %OUTPUT_DIRECTORY%.
)
REM Save the node label
PUSHD %OUTPUT_DIRECTORY%
ECHO !NODE_LABEL!> !LAST_CONFIGURE_NODE_LABEL_FILE!
POPD
+5 -2
View File
@@ -32,7 +32,7 @@ def add_args(parser, subparsers) -> None:
# 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, \
enable_gem, disable_gem, sha256
enable_gem, disable_gem, project_properties, sha256
# Remove the temporarily added path
sys.path = sys.path[1:]
@@ -55,7 +55,10 @@ def add_args(parser, subparsers) -> None:
# remove a gem from a project
disable_gem.add_args(subparsers)
# modify project properties
project_properties.add_args(subparsers)
# sha256
sha256.add_args(subparsers)
+75 -42
View File
@@ -97,6 +97,12 @@ def get_o3de_logs_folder() -> pathlib.Path:
return logs_folder
def get_o3de_third_party_folder() -> pathlib.Path:
third_party_folder = get_o3de_folder() / '3rdParty'
third_party_folder.mkdir(parents=True, exist_ok=True)
return third_party_folder
# o3de manifest file methods
def get_o3de_manifest() -> pathlib.Path:
manifest_path = get_o3de_folder() / 'o3de_manifest.json'
@@ -113,6 +119,7 @@ def get_o3de_manifest() -> pathlib.Path:
default_gems_folder = get_o3de_gems_folder()
default_templates_folder = get_o3de_templates_folder()
default_restricted_folder = get_o3de_restricted_folder()
default_third_party_folder = get_o3de_third_party_folder()
default_projects_restricted_folder = default_projects_folder / 'Restricted'
default_projects_restricted_folder.mkdir(parents=True, exist_ok=True)
@@ -129,6 +136,7 @@ def get_o3de_manifest() -> pathlib.Path:
json_data.update({'default_gems_folder': default_gems_folder.as_posix()})
json_data.update({'default_templates_folder': default_templates_folder.as_posix()})
json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()})
json_data.update({'default_third_party_folder': default_third_party_folder.as_posix()})
json_data.update({'projects': []})
json_data.update({'external_subdirectories': []})
@@ -302,64 +310,95 @@ def get_project_external_subdirectories(project_path: pathlib.Path) -> list:
project_object['external_subdirectories'])) if 'external_subdirectories' in project_object else []
def get_project_templates(project_path: pathlib.Path) -> list:
project_object = get_project_json_data(project_path=project_path)
return list(map(lambda rel_path: (pathlib.Path(project_path) / rel_path).as_posix(),
project_object['templates']))
def get_project_restricted(project_path: pathlib.Path) -> list:
project_object = get_project_json_data(project_path=project_path)
return list(map(lambda rel_path: (pathlib.Path(project_path) / rel_path).as_posix(),
project_object['restricted'])) if 'restricted' in project_object else []
# Combined manifest queries
def get_all_projects() -> list:
projects_data = set(get_projects())
projects_data.update(get_engine_projects())
return list(projects_data)
projects_data = get_projects()
projects_data.extend(get_engine_projects())
# Remove duplicates from the list
return list(dict.fromkeys(projects_data))
def get_all_gems(project_path: pathlib.Path = None) -> list:
gems_data = set(get_gems())
gems_data.update(get_engine_gems())
gems_data = get_gems()
gems_data.extend(get_engine_gems())
if project_path:
gems_data.update(get_project_gems(project_path))
return list(gems_data)
gems_data.extend(get_project_gems(project_path))
return list(dict.fromkeys(gems_data))
def get_all_external_subdirectories(project_path: pathlib.Path = None) -> list:
external_subdirectories_data = set(get_external_subdirectories())
external_subdirectories_data.update(get_engine_external_subdirectories())
external_subdirectories_data = get_external_subdirectories()
external_subdirectories_data.extend(get_engine_external_subdirectories())
if project_path:
external_subdirectories_data.update(get_project_external_subdirectories(project_path))
return list(templates_data)
external_subdirectories_data.extend(get_project_external_subdirectories(project_path))
return list(dict.fromkeys(external_subdirectories_data))
def get_all_templates() -> list:
templates_data = set(get_templates())
templates_data.update(get_engine_templates())
return list(templates_data)
def get_all_templates(project_path: pathlib.Path = None) -> list:
templates_data = get_templates()
templates_data.extend(get_engine_templates())
if project_path:
templates_data.extend(get_project_templates(project_path))
return list(dict.fromkeys(templates_data))
def get_all_restricted() -> list:
restricted_data = set(get_restricted())
restricted_data.update(get_engine_restricted())
return list(gems_data)
restricted_data = get_restricted()
restricted_data.extend(get_engine_restricted())
if project_path:
restricted_data.extend(get_project_restricted(project_path))
return list(dict.fromkeys(restricted_data))
# Template functions
def get_project_templates(): # temporary until we have a better way to do this... maybe template_type element
def get_templates_for_project_creation():
project_templates = []
for template in get_all_templates():
if 'Project' in template:
project_templates.append(template)
for template_path in get_all_templates():
template_path = pathlib.Path(template_path)
template_json_path = pathlib.Path(template_path) / 'template.json'
if not validation.valid_o3de_template_json(template_json_path):
continue
project_json_path = template_path / 'Template' / 'project.json'
if validation.valid_o3de_project_json(project_json_path):
project_templates.append(template_path)
return project_templates
def get_gem_templates(): # temporary until we have a better way to do this... maybe template_type element
def get_templates_for_gem_creation():
gem_templates = []
for template in get_all_templates():
if 'Gem' in template:
gem_templates.append(template)
for template_path in get_all_templates():
template_path = pathlib.Path(template_path)
template_json_path = pathlib.Path(template_path) / 'template.json'
if not validation.valid_o3de_template_json(template_json_path):
continue
gem_json_path = template_path / 'Template' / 'gem.json'
if validation.valid_o3de_gem_json(gem_json_path):
gem_templates.append(template_path)
return gem_templates
def get_generic_templates(): # temporary until we have a better way to do this... maybe template_type element
generic_templates = []
for template in get_all_templates():
if 'Project' not in template and 'Gem' not in template:
generic_templates.append(template)
return generic_templates
def get_templates_for_generic_creation(): # temporary until we have a better way to do this... maybe template_type element
def filter_project_and_gem_templates_out(template_path,
templates_for_project_creation = get_templates_for_project_creation(),
templates_for_gem_creation = get_templates_for_gem_creation()):
template_path = pathlib.Path(template_path)
return template_path not in templates_for_project_creation and template_path not in templates_for_gem_creation
return list(filter(filter_project_and_gem_templates_out, get_all_templates()))
def get_all_restricted() -> list:
@@ -515,7 +554,7 @@ def get_template_json_data(template_name: str = None,
return None
def get_restricted_data(restricted_name: str = None,
def get_restricted_json_data(restricted_name: str = None,
restricted_path: str or pathlib.Path = None) -> dict or None:
if not restricted_name and not restricted_path:
logger.error('Must specify either a Restricted name or Restricted Path.')
@@ -573,9 +612,7 @@ def get_registered(engine_name: str = None,
return engine_path
elif isinstance(project_name, str):
enging_projects = get_engine_projects()
projects = json_data['projects'].copy()
projects.extend(engine_object['projects'])
projects = get_all_projects()
for project_path in projects:
project_path = pathlib.Path(project_path).resolve()
project_json = project_path / 'project.json'
@@ -605,9 +642,7 @@ def get_registered(engine_name: str = None,
return gem_path
elif isinstance(template_name, str):
engine_templates = get_engine_templates()
templates = json_data['templates'].copy()
templates.extend(engine_templates)
templates = get_all_templates()
for template_path in templates:
template_path = pathlib.Path(template_path).resolve()
template_json = template_path / 'template.json'
@@ -622,9 +657,7 @@ def get_registered(engine_name: str = None,
return template_path
elif isinstance(restricted_name, str):
engine_restricted = get_engine_restricted()
restricted = json_data['restricted'].copy()
restricted.extend(engine_restricted)
restricted = get_all_restricted()
for restricted_path in restricted:
restricted_path = pathlib.Path(restricted_path).resolve()
restricted_json = restricted_path / 'restricted.json'
+251 -245
View File
@@ -13,6 +13,7 @@ import argparse
import json
import hashlib
import logging
import pathlib
import sys
import urllib.parse
@@ -21,219 +22,251 @@ from o3de import manifest, validation
logger = logging.getLogger()
logging.basicConfig()
def print_this_engine(verbose: int) -> None:
def get_project_path(project_path: pathlib.Path, project_name: str) -> pathlib.Path:
if not project_name and not project_path:
logger.error(f'Must either specify a Project path or Project Name.')
return None
if not project_path:
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("~/.o3de/o3de_manifest.json").expanduser())}, engine.json')
return None
if not project_path.is_dir():
logger.error(f'Project path {project_path} is not a folder.')
return None
return project_path
def print_this_engine(verbose: int) -> int:
engine_data = manifest.get_this_engine()
print(json.dumps(engine_data, indent=4))
result = True
if verbose > 0:
print_engines_data(engine_data)
result = print_manifest_json_data(engine_data, 'engine.json', 'This Engine',
manifest.get_engine_json_data, 'engine_path')
return 0 if result else 1
def print_engines(verbose: int) -> None:
engines_data = manifest.get_engines()
print(json.dumps(engines_data, indent=4))
if verbose > 0:
print_engines_data(engines_data)
return print_manifest_json_data(engines_data, 'engine.json', 'Engines',
manifest.get_engine_json_data, 'engine_path')
return 0
def print_projects(verbose: int) -> None:
def print_projects(verbose: int) -> int:
projects_data = manifest.get_projects()
print(json.dumps(projects_data, indent=4))
if verbose > 0:
print_projects_data(projects_data)
return print_manifest_json_data(projects_data, 'project.json', 'Projects',
manifest.get_project_json_data, 'project_path')
return 0
def print_gems(verbose: int) -> None:
def print_gems(verbose: int) -> int:
gems_data = manifest.get_gems()
print(json.dumps(gems_data, indent=4))
if verbose > 0:
print_gems_data(gems_data)
return print_manifest_json_data(gems_data, 'gem.json', 'Gems',
manifest.get_gem_json_data, 'gem_path')
return 0
def print_templates(verbose: int) -> None:
def print_templates(verbose: int) -> int:
templates_data = manifest.get_templates()
print(json.dumps(templates_data, indent=4))
if verbose > 0:
print_templates_data(templates_data)
return print_manifest_json_data(templates_data, 'template.json', 'Templates',
manifest.get_template_json_data, 'template_path')
return 0
def print_restricted(verbose: int) -> None:
def print_restricted(verbose: int) -> int:
restricted_data = manifest.get_restricted()
print(json.dumps(restricted_data, indent=4))
if verbose > 0:
print_restricted_data(restricted_data)
def print_engine_projects(verbose: int) -> None:
if verbose > 0:
return print_manifest_json_data(restricted_data, 'restricted.json', 'Restricted',
manifest.get_restricted_json_data, 'restricted_path')
return 0
# 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))
if verbose > 0:
print_projects_data(engine_projects_data)
return print_manifest_json_data(engine_projects_data, 'project.json', 'Projects',
manifest.get_project_json_data, 'project_path')
return 0
def print_engine_gems(verbose: int) -> None:
def print_engine_gems(verbose: int) -> int:
engine_gems_data = manifest.get_engine_gems()
print(json.dumps(engine_gems_data, indent=4))
if verbose > 0:
print_gems_data(engine_gems_data)
return print_manifest_json_data(engine_gems_data, 'gem.json', 'Gems',
manifest.get_gem_json_data, 'gem_path')
return 0
def print_engine_templates(verbose: int) -> None:
def print_engine_templates(verbose: int) -> int:
engine_templates_data = manifest.get_engine_templates()
print(json.dumps(engine_templates_data, indent=4))
if verbose > 0:
print_templates_data(engine_templates_data)
return print_manifest_json_data(engine_templates_data, 'template.json', 'Templates',
manifest.get_template_json_data, 'template_path')
return 0
def print_engine_restricted(verbose: int) -> None:
def print_engine_restricted(verbose: int) -> int:
engine_restricted_data = manifest.get_engine_restricted()
print(json.dumps(engine_restricted_data, indent=4))
if verbose > 0:
print_restricted_data(engine_restricted_data)
return print_manifest_json_data(engine_restricted_data, 'restricted.json', 'Restricted',
manifest.get_restricted_json_data, 'restricted_path')
return 0
def print_engine_external_subdirectories(verbose: int) -> None:
def print_engine_external_subdirectories() -> int:
external_subdirs_data = manifest.get_engine_external_subdirectories()
print(json.dumps(external_subdirs_data, indent=4))
return 0
def print_all_projects(verbose: int) -> None:
# Project output methods
def print_project_gems(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
project_gems_data = manifest.get_project_gems(project_path)
print(json.dumps(project_gems_data, indent=4))
if verbose > 0:
return print_manifest_json_data(project_gems_data, 'gem.json', 'Gems',
manifest.get_gem_json_data, 'gem_path')
return 0
def print_project_external_subdirectories(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))
return 0
def print_project_templates(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
project_templates_data = manifest.get_project_templates(project_path)
print(json.dumps(project_templates_data, indent=4))
if verbose > 0:
return print_manifest_json_data(project_templates_data, 'template.json', 'Templates',
manifest.get_template_json_data, 'template_path')
return 0
def print_project_restricted(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
project_restricted_data = manifest.get_project_restricted(project_path)
print(json.dumps(project_restricted_data, indent=4))
if verbose > 0:
return print_manifest_json_data(project_restricted_data, 'restricted.json', 'Restricted',
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))
if verbose > 0:
print_projects_data(all_projects_data)
return print_manifest_json_data(all_projects_data, 'project.json', 'Projects',
manifest.get_project_json_data, 'project_path')
return 0
def print_all_gems(verbose: int) -> None:
def print_all_gems(verbose: int) -> int:
all_gems_data = manifest.get_all_gems()
print(json.dumps(all_gems_data, indent=4))
if verbose > 0:
print_gems_data(all_gems_data)
return print_manifest_json_data(all_gems_data, 'gem.json', 'Gems',
manifest.get_gem_json_data, 'gem_path')
return 0
def print_all_templates(verbose: int) -> None:
def print_all_external_subdirectories() -> int:
all_external_subdirectories_data = manifest.get_all_external_subdirectories()
print(json.dumps(all_external_subdirectories_data, 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))
if verbose > 0:
print_templates_data(all_templates_data)
return print_manifest_json_data(all_templates_data, 'template.json', 'Templates',
manifest.get_template_json_data, 'template_path')
return 0
def print_all_restricted(verbose: int) -> None:
def print_all_restricted(verbose: int) -> int:
all_restricted_data = manifest.get_all_restricted()
print(json.dumps(all_restricted_data, indent=4))
if verbose > 0:
print_restricted_data(all_restricted_data)
return print_manifest_json_data(all_restricted_data, 'restricted.json', 'Restricted',
manifest.get_restricted_json_data, 'restricted_path')
return 0
def print_engines_data(engines_data: dict) -> None:
def print_manifest_json_data(uri_json_data: dict, json_filename: str,
print_prefix: str, get_json_func: callable, get_json_data_kw: str) -> int:
print('\n')
print("Engines================================================")
for engine_object in engines_data:
print(f"{print_prefix}================================================")
for manifest_uri in uri_json_data:
# if it's not local it should be in the cache
engine_uri = engine_object['path']
parsed_uri = urllib.parse.urlparse(engine_uri)
if parsed_uri.scheme == 'http' or \
parsed_uri.scheme == 'https' or \
parsed_uri.scheme == 'ftp' or \
parsed_uri.scheme == 'ftps':
repo_sha256 = hashlib.sha256(engine_uri.encode())
parsed_uri = urllib.parse.urlparse(manifest_uri)
if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']:
repo_sha256 = hashlib.sha256(manifest_uri.encode())
cache_folder = manifest.get_o3de_cache_folder()
engine = cache_folder / str(repo_sha256.hexdigest() + '.json')
print(f'{engine_uri}/engine.json cached as:')
manifest_json_path = cache_folder / str(repo_sha256.hexdigest() + '.json')
else:
engine_json = pathlib.Path(engine_uri).resolve() / 'engine.json'
manifest_json_path = pathlib.Path(manifest_uri).resolve() / json_filename
with engine_json.open('r') as f:
try:
engine_json_data = json.load(f)
except json.JSONDecodeError as e:
logger.warn(f'{engine_json} failed to load: {str(e)}')
else:
print(engine_json)
print(json.dumps(engine_json_data, indent=4))
print('\n')
json_data = get_json_func(**{get_json_data_kwargs: manifest_json_path})
if json_data:
print(manifest_json_path)
print(json.dumps(json_data, indent=4) + '\n')
return 0
def print_projects_data(projects_data: dict) -> None:
print('\n')
print("Projects================================================")
for project_uri in projects_data:
# if it's not local it should be in the cache
parsed_uri = urllib.parse.urlparse(project_uri)
if parsed_uri.scheme == 'http' or \
parsed_uri.scheme == 'https' or \
parsed_uri.scheme == 'ftp' or \
parsed_uri.scheme == 'ftps':
repo_sha256 = hashlib.sha256(project_uri.encode())
cache_folder = manifest.get_o3de_cache_folder()
project_json = cache_folder / str(repo_sha256.hexdigest() + '.json')
else:
project_json = pathlib.Path(project_uri).resolve() / 'project.json'
with project_json.open('r') as f:
try:
project_json_data = json.load(f)
except json.JSONDecodeError as e:
logger.warn(f'{project_json} failed to load: {str(e)}')
else:
print(project_json)
print(json.dumps(project_json_data, indent=4))
print('\n')
def print_gems_data(gems_data: dict) -> None:
print('\n')
print("Gems================================================")
for gem_uri in gems_data:
# if it's not local it should be in the cache
parsed_uri = urllib.parse.urlparse(gem_uri)
if parsed_uri.scheme == 'http' or \
parsed_uri.scheme == 'https' or \
parsed_uri.scheme == 'ftp' or \
parsed_uri.scheme == 'ftps':
repo_sha256 = hashlib.sha256(gem_uri.encode())
cache_folder = manifest.get_o3de_cache_folder()
gem_json = cache_folder / str(repo_sha256.hexdigest() + '.json')
else:
gem_json = pathlib.Path(gem_uri).resolve() / 'gem.json'
with gem_json.open('r') as f:
try:
gem_json_data = json.load(f)
except json.JSONDecodeError as e:
logger.warn(f'{gem_json} failed to load: {str(e)}')
else:
print(gem_json)
print(json.dumps(gem_json_data, indent=4))
print('\n')
def print_templates_data(templates_data: dict) -> None:
print('\n')
print("Templates================================================")
for template_uri in templates_data:
# if it's not local it should be in the cache
parsed_uri = urllib.parse.urlparse(template_uri)
if parsed_uri.scheme == 'http' or \
parsed_uri.scheme == 'https' or \
parsed_uri.scheme == 'ftp' or \
parsed_uri.scheme == 'ftps':
repo_sha256 = hashlib.sha256(template_uri.encode())
cache_folder = manifest.get_o3de_cache_folder()
template_json = cache_folder / str(repo_sha256.hexdigest() + '.json')
else:
template_json = pathlib.Path(template_uri).resolve() / 'template.json'
with template_json.open('r') as f:
try:
template_json_data = json.load(f)
except json.JSONDecodeError as e:
logger.warn(f'{template_json} failed to load: {str(e)}')
else:
print(template_json)
print(json.dumps(template_json_data, indent=4))
print('\n')
def print_repos_data(repos_data: dict) -> None:
def print_repos_data(repos_data: dict) -> int:
print('\n')
print("Repos================================================")
cache_folder = manifest.get_o3de_cache_folder()
@@ -251,29 +284,16 @@ def print_repos_data(repos_data: dict) -> None:
print(cache_file)
print(json.dumps(repo_json_data, indent=4))
print('\n')
def print_restricted_data(restricted_data: dict) -> None:
print('\n')
print("Restricted================================================")
for restricted_path in restricted_data:
restricted_json = pathlib.Path(restricted_path).resolve() / 'restricted.json'
with restricted_json.open('r') as f:
try:
restricted_json_data = json.load(f)
except json.JSONDecodeError as e:
logger.warn(f'{restricted_json} failed to load: {str(e)}')
else:
print(restricted_json)
print(json.dumps(restricted_json_data, indent=4))
print('\n')
return 0
def register_show_repos(verbose: int) -> None:
repos_data = get_repos()
repos_data = manifest.get_repos()
print(json.dumps(repos_data, indent=4))
if verbose > 0:
print_repos_data(repos_data)
return print_repos_data(repos_data) == 0
return 0
def register_show(verbose: int) -> None:
@@ -281,13 +301,15 @@ def register_show(verbose: int) -> None:
print(f"{manifest.get_o3de_manifest()}:")
print(json.dumps(json_data, indent=4))
result = True
if verbose > 0:
print_engines_data(manifest.get_engines())
print_projects_data(manifest.get_all_projects())
print_gems_data(manifest.get_gems())
print_templates_data(manifest.get_all_templates())
print_restricted_data(manifest.get_all_restricted())
print_repos_data(manifest.get_repos())
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
def _run_register_show(args: argparse) -> int:
@@ -295,75 +317,53 @@ def _run_register_show(args: argparse) -> int:
manifest.override_home_folder = args.override_home_folder
if args.this_engine:
print_this_engine(args.verbose)
return 0
return print_this_engine(args.verbose)
elif args.engines:
print_engines(args.verbose)
return 0
return print_engines(args.verbose)
elif args.projects:
print_projects(args.verbose)
return 0
return print_projects(args.verbose)
elif args.gems:
print_gems(args.verbose)
return 0
return print_gems(args.verbose)
elif args.templates:
print_templates(args.verbose)
return 0
return print_templates(args.verbose)
elif args.repos:
register_show_repos(args.verbose)
return 0
return register_show_repos(args.verbose)
elif args.restricted:
print_restricted(args.verbose)
return 0
return print_restricted(args.verbose)
elif args.engine_projects:
print_engine_projects(args.verbose)
return 0
return print_engine_projects(args.verbose)
elif args.engine_gems:
print_engine_gems(args.verbose)
return 0
elif args.engine_templates:
print_engine_templates(args.verbose)
return 0
elif args.engine_restricted:
print_engine_restricted(args.verbose)
return 0
return print_engine_gems(args.verbose)
elif args.engine_external_subdirectories:
print_engine_external_subdirectories(args.verbose)
return 0
return print_engine_external_subdirectories()
elif args.engine_templates:
return print_engine_templates(args.verbose)
elif args.engine_restricted:
return print_engine_restricted(args.verbose)
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)
elif args.project_templates:
return print_project_templates(args.verbose, args.project_path, args.project_name)
elif args.project_restricted:
return print_project_restricted(args.verbose, args.project_path, args.project_name)
elif args.all_projects:
print_all_projects(args.verbose)
return 0
return print_all_projects(args.verbose)
elif args.all_gems:
print_all_gems(args.verbose)
return 0
return print_all_gems(args.verbose)
elif args.all_external_subdirectories:
return print_all_external_subdirectories()
elif args.all_templates:
print_all_templates(args.verbose)
return 0
return print_all_templates(args.verbose)
elif args.all_restricted:
print_all_restricted(args.verbose)
return 0
return print_all_restricted(args.verbose)
elif args.downloadables:
print_downloadables(args.verbose)
return 0
if args.downloadable_engines:
print_downloadable_engines(args.verbose)
return 0
elif args.downloadable_projects:
print_downloadable_projects(args.verbose)
return 0
elif args.downloadable_gems:
print_downloadable_gems(args.verbose)
return 0
elif args.downloadable_templates:
print_downloadable_templates(args.verbose)
return 0
else:
register_show(args.verbose)
return 0
return register_show(args.verbose)
def add_parser_args(parser):
@@ -376,78 +376,84 @@ def add_parser_args(parser):
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('-te', '--this-engine', action='store_true', required=False,
default=False,
help='Just the local engines.')
help='Output the current engine path.')
group.add_argument('-e', '--engines', action='store_true', required=False,
default=False,
help='Just the local engines.')
help='Output the engines registered in the global ~/.o3de/o3de_manifest.json.')
group.add_argument('-p', '--projects', action='store_true', required=False,
default=False,
help='Just the local projects.')
help='Output the projects registered in the global ~/.o3de/o3de_manifest.json.')
group.add_argument('-g', '--gems', action='store_true', required=False,
default=False,
help='Just the local gems.')
help='Output the gems registered in the global ~/.o3de/o3de_manifest.json.')
group.add_argument('-t', '--templates', action='store_true', required=False,
default=False,
help='Just the local templates.')
help='Output the templates registered in the global ~/.o3de/o3de_manifest.json.')
group.add_argument('-r', '--repos', action='store_true', required=False,
default=False,
help='Just the local repos. Ignores repos.')
help='Output the repos registered in the global ~/.o3de/o3de_manifest.json. Ignores repos.')
group.add_argument('-rs', '--restricted', action='store_true', required=False,
default=False,
help='The local restricted folders.')
help='Output the restricted directories registered in the global ~/.o3de/o3de_manifest.json.')
group.add_argument('-ep', '--engine-projects', action='store_true', required=False,
default=False,
help='Just the local projects. Ignores repos.')
help='Output the projects registered in the current engine engine.json. Ignores repos.')
group.add_argument('-eg', '--engine-gems', action='store_true', required=False,
default=False,
help='Just the local gems. Ignores repos')
help='Output the gems registered in the current engine engine.json. Ignores repos')
group.add_argument('-et', '--engine-templates', action='store_true', required=False,
default=False,
help='Just the local templates. Ignores repos.')
help='Output the templates registered in the current engine engine.json. Ignores repos.')
group.add_argument('-ers', '--engine-restricted', action='store_true', required=False,
default=False,
help='The restricted folders.')
group.add_argument('-x', '--engine-external-subdirectories', action='store_true', required=False,
help='Output the restricted directories registered in the current engine engine.json.')
group.add_argument('-ees', '--engine-external-subdirectories', action='store_true', required=False,
default=False,
help='The external subdirectories.')
help='Output the external subdirectories registered in the current engine engine.json.')
group.add_argument('-pg', '--project-gems', action='store_true',
default=False,
help='Returns the gems registered with the project.json.')
group.add_argument('-pt', '--project-templates', action='store_true',
default=False,
help='Returns the templates registered with the project.json.')
group.add_argument('-prs', '--project-restricted', action='store_true',
default=False,
help='Returns the restricted directories registered with the project.json.')
group.add_argument('-pes', '--project-external-subdirectories', action='store_true',
default=False,
help='Returns the external subdirectories register with the project.json.')
group.add_argument('-ap', '--all-projects', action='store_true', required=False,
default=False,
help='Just the local projects. Ignores repos.')
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='Just the local gems. Ignores repos')
help='Output all gems registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos')
group.add_argument('-at', '--all-templates', action='store_true', required=False,
default=False,
help='Just the local templates. Ignores repos.')
group.add_argument('-ars', '--all-restricted', action='store_true', required=False,
help='Output all templates registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.')
group.add_argument('-ares', '--all-restricted', action='store_true', required=False,
default=False,
help='The restricted folders.')
group.add_argument('-d', '--downloadables', action='store_true', required=False,
help='Output all restricted directory registered in the ~/.o3de/o3de_manifest.json and the current engine.json.')
group.add_argument('-aes', '--all-external-subdirectories', action='store_true',
default=False,
help='Combine all repos into a single list of resources.')
group.add_argument('-de', '--downloadable-engines', action='store_true', required=False,
default=False,
help='Combine all repos engines into a single list of resources.')
group.add_argument('-dp', '--downloadable-projects', action='store_true', required=False,
default=False,
help='Combine all repos projects into a single list of resources.')
group.add_argument('-dg', '--downloadable-gems', action='store_true', required=False,
default=False,
help='Combine all repos gems into a single list of resources.')
group.add_argument('-dt', '--downloadable-templates', action='store_true', required=False,
default=False,
help='Combine all repos templates into a single list of resources.')
help='Output all external subdirectories registered in the ~/.o3de/o3de_manifest.json and the current engine.json.')
parser.add_argument('-v', '--verbose', action='count', required=False,
default=0,
help='How verbose do you want the output to be.')
default=0,
help='How verbose do you want the output to be.')
project_group = parser.add_mutually_exclusive_group(required=False)
project_group.add_argument('-pp', '--project-path', type=pathlib.Path,
help='The path to a project.')
project_group.add_argument('-pn', '--project-name', type=str,
help='The name of a project.')
parser.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.')
help='By default the home folder is the user folder, override it to this folder.')
parser.set_defaults(func=_run_register_show)
+104
View File
@@ -0,0 +1,104 @@
#
# 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 argparse
import json
import os
import pathlib
import sys
import logging
from o3de import manifest
logger = logging.getLogger()
logging.basicConfig()
def get_project_props(name: str = None, path: pathlib.Path = None) -> dict:
proj_json = manifest.get_project_json_data(project_name=name, project_path=path)
if not proj_json:
param = name if name else path
logger.error(f'Could not retrieve project.json file for {param}')
return None
return proj_json
def edit_project_props(proj_path, proj_name, new_origin, new_display,
new_summary, new_icon, new_tag, remove_tag) -> int:
proj_json = get_project_props(proj_name, proj_path)
if not proj_json:
return 1
if new_origin:
proj_json['origin'] = new_origin
if new_display:
proj_json['display_name'] = new_display
if new_summary:
proj_json['summary'] = new_summary
if new_icon:
proj_json['icon_path'] = new_icon
if new_tag:
proj_json.setdefault('user_tags', []).append(new_tag)
if remove_tag:
if 'user_tags' in proj_json:
if remove_tag in proj_json['user_tags']:
proj_json['user_tags'].remove(remove_tag)
else:
logger.warn(f'{remove_tag} not found in user_tags for removal.')
else:
logger.warn(f'user_tags property not found for removal of tag {remove_tag}.')
manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path) / 'project.json')
return 0
def _edit_project_props(args: argparse) -> int:
return edit_project_props(args.project_path,
args.project_name,
args.project_origin,
args.project_display,
args.project_summary,
args.project_icon,
args.project_tag,
args.remove_tag)
def add_parser_args(parser):
group = 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.')
group = parser.add_argument_group('properties', 'arguments for modifying individual project properties.')
group.add_argument('-po', '--project-origin', type=str, required=False,
help='Sets description or url for project origin (such as project host, repository, owner...etc).')
group.add_argument('-pd', '--project-display', type=str, required=False,
help='Sets the project display name.')
group.add_argument('-ps', '--project-summary', type=str, required=False,
help='Sets the summary description of the project.')
group.add_argument('-pi', '--project-icon', type=str, required=False,
help='Sets the path to the projects icon resource.')
group.add_argument('-pt', '--project-tag', type=str, required=False,
help='Adds a tag to user_tags property. These tags are intended for documentation and filtering.')
group.add_argument('-rt', '--remove-tag', type=str, required=False,
help='Removes a tag from the user_tags property.')
parser.set_defaults(func=_edit_project_props)
def add_args(subparsers) -> None:
enable_project_props_subparser = subparsers.add_parser('edit-project-properties')
add_parser_args(enable_project_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()
+29 -6
View File
@@ -486,7 +486,7 @@ def register_default_engines_folder(json_data: dict,
remove: bool = False) -> int:
return register_default_o3de_object_folder(json_data,
manifest.get_o3de_engines_folder() if remove else default_engines_folder,
'default_engines_folder', remove)
'default_engines_folder')
def register_default_projects_folder(json_data: dict,
@@ -494,7 +494,7 @@ def register_default_projects_folder(json_data: dict,
remove: bool = False) -> int:
return register_default_o3de_object_folder(json_data,
manifest.get_o3de_projects_folder() if remove else default_projects_folder,
'default_projects_folder', remove)
'default_projects_folder')
def register_default_gems_folder(json_data: dict,
@@ -502,7 +502,7 @@ def register_default_gems_folder(json_data: dict,
remove: bool = False) -> int:
return register_default_o3de_object_folder(json_data,
manifest.get_o3de_gems_folder() if remove else default_gems_folder,
'default_gems_folder', remove)
'default_gems_folder')
def register_default_templates_folder(json_data: dict,
@@ -510,16 +510,22 @@ def register_default_templates_folder(json_data: dict,
remove: bool = False) -> int:
return register_default_o3de_object_folder(json_data,
manifest.get_o3de_templates_folder() if remove else default_templates_folder,
'default_templates_folder', remove)
'default_templates_folder')
def register_default_restricted_folder(json_data: dict,
default_restricted_folder: str or pathlib.Path,
reset_to_default: bool = False) -> int:
remove: bool = False) -> int:
return register_default_o3de_object_folder(json_data,
manifest.get_o3de_restricted_folder() if remove else default_restricted_folder,
'default_restricted_folder', remove)
'default_restricted_folder')
def register_default_third_party_folder(json_data: dict,
default_third_party_folder: pathlib.Path,
remove: bool = False) -> int:
return register_default_o3de_object_folder(json_data,
manifest.get_o3de_third_party_folder() if remove else default_third_party_folder,
'default_third_party_folder')
def register(engine_path: str or pathlib.Path = None,
project_path: str or pathlib.Path = None,
@@ -533,6 +539,7 @@ def register(engine_path: str or pathlib.Path = None,
default_gems_folder: str or pathlib.Path = None,
default_templates_folder: str or pathlib.Path = None,
default_restricted_folder: str or pathlib.Path = None,
default_third_party_folder: pathlib.Path = None,
external_subdir_engine_path: pathlib.Path = None,
external_subdir_project_path: pathlib.Path = None,
remove: bool = False,
@@ -553,6 +560,7 @@ def register(engine_path: str or pathlib.Path = None,
:param default_gems_folder: default gems folder
:param default_templates_folder: default templates folder
:param default_restricted_folder: default restricted code folder
:param default_third_party_folder: default 3rd party cache folder
:param external_subdir_engine_path: Path to the engine to use when registering an external subdirectory.
The registration occurs in the engine.json file in this case
:param external_subdir_engine_path: Path to the project to use when registering an external subdirectory.
@@ -620,6 +628,9 @@ def register(engine_path: str or pathlib.Path = None,
elif isinstance(default_restricted_folder, str) or isinstance(default_restricted_folder, pathlib.PurePath):
result = register_default_restricted_folder(json_data, default_restricted_folder, remove)
elif default_third_party_folder:
result = register_default_third_party_folder(json_data, default_third_party_folder, remove)
# engine is done LAST
# Now that everything that could have an engine context is done, if the engine is supplied that means this is
# registering the engine itself
@@ -712,6 +723,15 @@ def remove_invalid_o3de_objects() -> None:
f" Set default {default_restricted_folder}")
register(default_restricted_folder=default_restricted_folder.as_posix())
default_third_party_folder = pathlib.Path(json_data['default_third_party_folder']).resolve()
if not default_third_party_folder.is_dir():
default_third_party_folder = manifest.get_o3de_folder() / '3rdParty'
default_third_party_folder.mkdir(parents=True, exist_ok=True)
logger.warn(
f"Default 3rd Party folder {default_third_party_folder} is invalid."
f" Set default {default_third_party_folder}")
register(default_third_party_folder=default_third_party_folder.as_posix())
def _run_register(args: argparse) -> int:
if args.override_home_folder:
@@ -751,6 +771,7 @@ def _run_register(args: argparse) -> int:
default_gems_folder=args.default_gems_folder,
default_templates_folder=args.default_templates_folder,
default_restricted_folder=args.default_restricted_folder,
default_third_party_folder=args.default_third_party_folder,
external_subdir_engine_path=args.external_subdirectory_engine_path,
external_subdir_project_path=args.external_subdirectory_project_path,
remove=args.remove,
@@ -804,6 +825,8 @@ def add_parser_args(parser):
help='The default templates folder to register/remove.')
group.add_argument('-drf', '--default-restricted-folder', type=str, required=False,
help='The default restricted folder to register/remove.')
group.add_argument('-dtpf', '--default-third-party-folder', type=pathlib.Path, required=False,
help='The default 3rd Party folder to register/remove.')
group.add_argument('-u', '--update', action='store_true', required=False,
default=False,
help='Refresh the repo cache.')
+7
View File
@@ -34,3 +34,10 @@ ly_add_pytest(
TEST_SUITE smoke
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
ly_add_pytest(
NAME o3de_manifest
PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_manifest.py
TEST_SUITE smoke
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
)
+111
View File
@@ -0,0 +1,111 @@
#
# 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 argparse
import json
import logging
import pytest
import pathlib
from unittest.mock import patch
from o3de import manifest
@pytest.mark.parametrize("valid_project_json_paths, valid_gem_json_paths", [
pytest.param([pathlib.Path('D:/o3de/Templates/DefaultProject/Template/project.json')],
[pathlib.Path('D:/o3de/Templates/DefaultGem/Template/gem.json')])
])
class TestGetTemplatesForCreation:
@staticmethod
def get_templates() -> list:
return []
@staticmethod
def get_project_templates() -> list:
return []
@staticmethod
def get_engine_templates() -> list:
return [pathlib.Path('D:/o3de/Templates/DefaultProject'), pathlib.Path('D:/o3de/Templates/DefaultGem')]
@pytest.mark.parametrize("expected_template_paths", [
pytest.param([])
]
)
def test_get_templates_for_generic_creation(self, valid_project_json_paths, valid_gem_json_paths,
expected_template_paths):
def validate_project_json(template_path) -> bool:
return pathlib.Path(template_path) in valid_project_json_paths
def validate_gem_json(template_path) -> bool:
return pathlib.Path(template_path) in valid_gem_json_paths
with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \
patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates)\
as get_project_templates_patch, \
patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates)\
as get_engine_templates_patch, \
patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json,\
patch('o3de.validation.valid_o3de_project_json', side_effect=validate_project_json) as validate_project_json,\
patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json:
templates = manifest.get_templates_for_generic_creation()
assert templates == expected_template_paths
@pytest.mark.parametrize("expected_template_paths", [
pytest.param([pathlib.Path('D:/o3de/Templates/DefaultProject')])
]
)
def test_get_templates_for_gem_creation(self, valid_project_json_paths, valid_gem_json_paths,
expected_template_paths):
def validate_project_json(template_path) -> bool:
return pathlib.Path(template_path) in valid_project_json_paths
def validate_gem_json(template_path) -> bool:
return pathlib.Path(template_path) in valid_gem_json_paths
with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \
patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates) \
as get_project_templates_patch, \
patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates) \
as get_engine_templates_patch, \
patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json, \
patch('o3de.validation.valid_o3de_project_json',
side_effect=validate_project_json) as validate_project_json, \
patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json:
templates = manifest.get_templates_for_project_creation()
assert templates == expected_template_paths
@pytest.mark.parametrize("expected_template_paths", [
pytest.param([pathlib.Path('D:/o3de/Templates/DefaultGem')])
]
)
def test_get_templates_for_project_creation(self, valid_project_json_paths, valid_gem_json_paths,
expected_template_paths):
def validate_project_json(template_path) -> bool:
return pathlib.Path(template_path) in valid_project_json_paths
def validate_gem_json(template_path) -> bool:
return pathlib.Path(template_path) in valid_gem_json_paths
with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \
patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates) \
as get_project_templates_patch, \
patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates) \
as get_engine_templates_patch, \
patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json, \
patch('o3de.validation.valid_o3de_project_json',
side_effect=validate_project_json) as validate_project_json, \
patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json:
templates = manifest.get_templates_for_gem_creation()
assert templates == expected_template_paths
+1 -1
View File
@@ -11,7 +11,7 @@
import pytest
from . import utils
from o3de import utils
@pytest.mark.parametrize(
"value, expected_result", [
-22
View File
@@ -1,22 +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.
#
ly_download_associated_package(pyside2)
ly_add_pytest(
NAME test_pyside
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_pyside.py
)
ly_add_pytest(
NAME test_projects
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_projects.py
)
-10
View File
@@ -1,10 +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.
#
-804
View File
@@ -1,804 +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.
#
# PySide project and gem selector GUI
import os
import pathlib
import sys
import argparse
import json
import logging
import subprocess
from logging.handlers import RotatingFileHandler
from typing import List
from pyside import add_pyside_environment, is_pyside_ready, uninstall_env
engine_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
sys.path.append(engine_path)
executable_path = ''
logger = logging.getLogger()
logger.setLevel(logging.INFO)
from o3de import disable_gem, enable_gem, cmake, engine_template, manifest, register
o3de_folder = manifest.get_o3de_folder()
o3de_logs_folder = manifest.get_o3de_logs_folder()
project_manager_log_file_path = o3de_logs_folder / "project_manager.log"
log_file_handler = RotatingFileHandler(filename=project_manager_log_file_path, maxBytes=1024 * 1024, backupCount=1)
formatter = logging.Formatter('%(asctime)s | %(levelname)s : %(message)s')
log_file_handler.setFormatter(formatter)
logger.addHandler(log_file_handler)
logger.info("Starting Project Manager")
def initialize_pyside_from_parser():
# Parse arguments up top. We need to know the path to our binaries and QT libs in particular to load up
# PySide
parser = argparse.ArgumentParser()
parser.add_argument('--executable-path', required=True, help='Path to Executable to launch with project')
parser.add_argument('--binaries-path', default=None, help='Path to QT Binaries necessary for PySide. If not'
' provided executable_path folder is assumed')
parser.add_argument('--parent-pid', default=0, help='Process ID of launching process')
args = parser.parse_args()
logger.info(f"parent_pid is {args.parent_pid}")
global executable_path
executable_path = args.executable_path
binaries_path = args.binaries_path or os.path.dirname(executable_path)
# Initialize PySide before imports below. This adds both PySide python modules to the python system interpreter
# path and adds the necessary paths to binaries for the DLLs to be found and load their dependencies
add_pyside_environment(binaries_path)
if not is_pyside_ready():
initialize_pyside_from_parser()
try:
from PySide2.QtWidgets import QApplication, QDialogButtonBox, QPushButton, QComboBox, QMessageBox, QFileDialog
from PySide2.QtWidgets import QListView, QLabel
from PySide2.QtUiTools import QUiLoader
from PySide2.QtCore import QFile, QObject, Qt, Signal, Slot
from PySide2.QtGui import QIcon, QStandardItemModel, QStandardItem
except ImportError as e:
logger.error(f"Failed to import PySide2 with error {e}")
exit(-1)
logger.error(f"PySide2 imports successful")
class DialogLoggerSignaller(QObject):
send_to_dialog = Signal(str)
def __init__(self, dialog_logger):
super(DialogLoggerSignaller, self).__init__()
self.dialog_logger = dialog_logger
# Independent class to handle log forwarding. Logger and qt signals both use emit method.
# This class's job is to receive the logger record and then emit the formatted message through
# DialogLoggerSignaller which is what the ProjectDialog handler listens for
class DialogLogger(logging.Handler):
def __init__(self, log_dialog, log_level=logging.INFO, forward_log_level=logging.WARNING,
message_box_log_level=logging.ERROR):
super(DialogLogger, self).__init__()
self.log_dialog = log_dialog
self.log_level = log_level
self.forward_log_level = forward_log_level
self.message_box_log_level = message_box_log_level
self.log_records = []
self.formatter = logging.Formatter('%(levelname)s : %(message)s')
self.setFormatter(self.formatter)
self.signaller = DialogLoggerSignaller(self)
def emit(self, record):
self.log_records.append(record)
if record.levelno >= self.message_box_log_level:
QMessageBox.warning(None, record.levelname, record.message)
elif record.levelno >= self.forward_log_level:
self.signaller.send_to_dialog.emit(self.format(record))
class ProjectManagerDialog(QObject):
"""
Main project manager dialog is responsible for displaying the project selection list and output pane
"""
def __init__(self, parent=None):
super(ProjectManagerDialog, self).__init__(parent)
self.ui_path = (pathlib.Path(__file__).parent / 'ui').resolve()
self.home_folder = manifest.get_home_folder()
self.log_display = None
self.dialog_logger = DialogLogger(self)
logger.addHandler(self.dialog_logger)
logger.setLevel(logging.INFO)
self.dialog_logger.signaller.send_to_dialog.connect(self.handle_log_message)
self.mru_file_path = o3de_folder / 'mru.json'
self.create_from_template_ui_file_path = self.ui_path / 'create_from_template.ui'
self.create_gem_ui_file_path = self.ui_path / 'create_gem.ui'
self.create_project_ui_file_path = self.ui_path / 'create_project.ui'
self.manage_project_gem_targets_ui_file_path = self.ui_path / 'manage_gem_targets.ui'
self.project_manager_icon_file_path = self.ui_path / 'project_manager.ico'
self.project_manager_ui_file_path = self.ui_path / 'project_manager.ui'
self.project_manager_ui_file = QFile(self.project_manager_ui_file_path.as_posix())
self.project_manager_ui_file.open(QFile.ReadOnly)
loader = QUiLoader()
self.dialog = loader.load(self.project_manager_ui_file)
self.dialog.setWindowIcon(QIcon(self.project_manager_icon_file_path.as_posix()))
self.dialog.setFixedSize(self.dialog.size())
self.project_list_box = self.dialog.findChild(QComboBox, 'projectListBox')
self.refresh_project_list()
mru = self.get_mru_list()
if len(mru):
last_mru = pathlib.Path(mru[0]).resolve()
for this_slot in range(self.project_list_box.count()):
item_text = self.project_list_box.itemText(this_slot)
if last_mru.as_posix() in item_text:
self.project_list_box.setCurrentIndex(this_slot)
break
self.create_project_button = self.dialog.findChild(QPushButton, 'createProjectButton')
self.create_project_button.clicked.connect(self.create_project_handler)
self.create_gem_button = self.dialog.findChild(QPushButton, 'createGemButton')
self.create_gem_button.clicked.connect(self.create_gem_handler)
self.create_template_button = self.dialog.findChild(QPushButton, 'createTemplateButton')
self.create_template_button.clicked.connect(self.create_template_handler)
self.create_from_template_button = self.dialog.findChild(QPushButton, 'createFromTemplateButton')
self.create_from_template_button.clicked.connect(self.create_from_template_handler)
self.add_project_button = self.dialog.findChild(QPushButton, 'addProjectButton')
self.add_project_button.clicked.connect(self.add_project_handler)
self.add_gem_button = self.dialog.findChild(QPushButton, 'addGemButton')
self.add_gem_button.clicked.connect(self.add_gem_handler)
self.add_template_button = self.dialog.findChild(QPushButton, 'addTemplateButton')
self.add_template_button.clicked.connect(self.add_template_handler)
self.add_restricted_button = self.dialog.findChild(QPushButton, 'addRestrictedButton')
self.add_restricted_button.clicked.connect(self.add_restricted_handler)
self.remove_project_button = self.dialog.findChild(QPushButton, 'removeProjectButton')
self.remove_project_button.clicked.connect(self.remove_project_handler)
self.remove_gem_button = self.dialog.findChild(QPushButton, 'removeGemButton')
self.remove_gem_button.clicked.connect(self.remove_gem_handler)
self.remove_template_button = self.dialog.findChild(QPushButton, 'removeTemplateButton')
self.remove_template_button.clicked.connect(self.remove_template_handler)
self.remove_restricted_button = self.dialog.findChild(QPushButton, 'removeRestrictedButton')
self.remove_restricted_button.clicked.connect(self.remove_restricted_handler)
self.manage_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageRuntimeGemTargetsButton')
self.manage_project_gem_targets_button.clicked.connect(self.manage_project_gem_targets_handler)
self.log_display = self.dialog.findChild(QLabel, 'logDisplay')
self.ok_cancel_button = self.dialog.findChild(QDialogButtonBox, 'okCancel')
self.ok_cancel_button.accepted.connect(self.accepted_handler)
self.dialog.show()
def refresh_project_list(self) -> None:
projects = manifest.get_all_projects()
self.project_list_box.clear()
for this_slot in range(len(projects)):
display_name = f'{os.path.basename(os.path.normpath(projects[this_slot]))} ({projects[this_slot]})'
self.project_list_box.addItem(display_name)
self.project_list_box.setItemData(self.project_list_box.count() - 1, projects[this_slot],
Qt.ToolTipRole)
def accepted_handler(self) -> None:
"""
Override for handling "Ok" on main project dialog to first check whether the user has selected a project and
prompt them to if not. If a project is selected will attempt to open it.
:return: None
"""
if not self.project_list_box.currentText():
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText("Please select a project")
msg_box.exec()
return
self.launch_with_project_path(self.get_selected_project_path())
def get_launch_project(self) -> str:
return os.path.normpath(self.get_selected_project_path())
def get_executable_launch_params(self) -> list:
"""
Retrieve the necessary launch parameters to make the subprocess launch call with - this is the path
to the executable such as the Editor and the path to the selected project
:return: list of params
"""
launch_params = [executable_path,
f'-regset="/Amazon/AzCore/Bootstrap/project_path={self.get_launch_project()}"']
return launch_params
def launch_with_project_path(self, project_path: str) -> None:
"""
Launch the desired application given the selected project
:param project_path: Path to currently selected project
:return: None
"""
logger.info(f'Attempting to open {project_path}')
self.update_mru_list(project_path)
launch_params = self.get_executable_launch_params()
logger.info(f'Launching with params {launch_params}')
subprocess.run(launch_params, env=uninstall_env())
def get_selected_project_path(self) -> str:
if self.project_list_box.currentIndex() == -1:
logger.warning("No project selected")
return ""
return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole)
def get_selected_project_name(self) -> str:
project_data = manifest.get_project_json_data(project_path=self.get_selected_project_path())
return project_data['project_name']
def create_project_handler(self):
"""
Opens the Create Project pane. Retrieves a list of available templates for display
:return: None
"""
loader = QUiLoader()
self.create_project_file = QFile(self.create_project_ui_file_path.as_posix())
if not self.create_project_file:
logger.error(f'Failed to create project UI file at {self.create_project_file}')
return
self.create_project_dialog = loader.load(self.create_project_file)
if not self.create_project_dialog:
logger.error(f'Failed to load create project dialog file at {self.create_project_file}')
return
self.create_project_ok_button = self.create_project_dialog.findChild(QDialogButtonBox, 'okCancel')
self.create_project_ok_button.accepted.connect(self.create_project_accepted_handler)
self.create_project_template_list = self.create_project_dialog.findChild(QListView, 'projectTemplates')
self.refresh_create_project_template_list()
self.create_project_dialog.exec()
def create_project_accepted_handler(self) -> None:
"""
Searches the available gems list for selected gems and attempts to add each one to the current project.
Updates UI after completion.
:return: None
"""
selected_item = self.create_project_template_list.selectionModel().currentIndex()
project_template_path = self.create_project_template_list.model().data(selected_item)
if not project_template_path:
return
folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name",
manifest.get_o3de_projects_folder().as_posix())
folder_dialog.setFileMode(QFileDialog.AnyFile)
folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
project_count = 0
project_name = "MyNewProject"
while os.path.exists(os.path.join(engine_path, project_name)):
project_name = f"MyNewProject{project_count}"
project_count += 1
folder_dialog.selectFile(project_name)
project_folder = None
if folder_dialog.exec():
project_folder = folder_dialog.selectedFiles()
if project_folder:
if engine_template.create_project(project_path=project_folder[0],
template_path=project_template_path) == 0:
# Success
register.register(project_path=project_folder[0])
self.refresh_project_list()
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Project {project_folder[0]} created.")
msg_box.exec()
return
def create_gem_handler(self):
"""
Opens the Create Gem pane. Retrieves a list of available templates for display
:return: None
"""
loader = QUiLoader()
self.create_gem_file = QFile(self.create_gem_ui_file_path.as_posix())
if not self.create_gem_file:
logger.error(f'Failed to create gem UI file at {self.create_gem_file}')
return
self.create_gem_dialog = loader.load(self.create_gem_file)
if not self.create_gem_dialog:
logger.error(f'Failed to load create gem dialog file at {self.create_gem_file}')
return
self.create_gem_ok_button = self.create_gem_dialog.findChild(QDialogButtonBox, 'okCancel')
self.create_gem_ok_button.accepted.connect(self.create_gem_accepted_handler)
self.create_gem_template_list = self.create_gem_dialog.findChild(QListView, 'gemTemplates')
self.refresh_create_gem_template_list()
self.create_gem_dialog.exec()
def create_gem_accepted_handler(self) -> None:
"""
Searches the available gems list for selected gems and attempts to add each one to the current gem.
Updates UI after completion.
:return: None
"""
selected_item = self.create_gem_template_list.selectionModel().currentIndex()
gem_template_path = self.create_gem_template_list.model().data(selected_item)
if not gem_template_path:
return
folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name",
manifest.get_o3de_gems_folder().as_posix())
folder_dialog.setFileMode(QFileDialog.AnyFile)
folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
gem_count = 0
gem_name = "MyNewGem"
while os.path.exists(os.path.join(engine_path, gem_name)):
gem_name = f"MyNewGem{gem_count}"
gem_count += 1
folder_dialog.selectFile(gem_name)
gem_folder = None
if folder_dialog.exec():
gem_folder = folder_dialog.selectedFiles()
if gem_folder:
if engine_template.create_gem(gem_path=gem_folder[0],
template_path=gem_template_path) == 0:
# Success
register.register(gem_path=gem_folder[0])
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Gem {gem_folder[0]} created.")
msg_box.exec()
return
def create_template_handler(self):
"""
Opens a foldr select dialog and lets the user select the source folder they want to make a template
out of, then opens a second folder select dialog to get where they want to put the template and it name
:return: None
"""
source_folder = QFileDialog.getExistingDirectory(self.dialog,
"Select a Folder to make a template out of.",
manifest.get_o3de_folder().as_posix())
if not source_folder:
return
destination_template_folder_dialog = QFileDialog(self.dialog,
"Select where the template is to be created and named.",
manifest.get_o3de_templates_folder().as_posix())
destination_template_folder_dialog.setFileMode(QFileDialog.AnyFile)
destination_template_folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
destination_folder = None
if destination_template_folder_dialog.exec():
destination_folder = destination_template_folder_dialog.selectedFiles()
if not destination_folder:
return
if engine_template.create_template(source_path=source_folder,
template_path=destination_folder[0]) == 0:
# Success
register.register(template_path=destination_folder[0])
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Template {destination_folder[0]} created.")
msg_box.exec()
return
def create_from_template_handler(self):
"""
Opens the Create from_template pane. Retrieves a list of available from_templates for display
:return: None
"""
loader = QUiLoader()
self.create_from_template_file = QFile(self.create_from_template_ui_file_path.as_posix())
if not self.create_from_template_file:
logger.error(f'Failed to create from_template UI file at {self.create_from_template_file}')
return
self.create_from_template_dialog = loader.load(self.create_from_template_file)
if not self.create_from_template_dialog:
logger.error(f'Failed to load create from_template dialog file at {self.create_from_template_file}')
return
self.create_from_template_ok_button = self.create_from_template_dialog.findChild(QDialogButtonBox, 'okCancel')
self.create_from_template_ok_button.accepted.connect(self.create_from_template_accepted_handler)
self.create_from_template_list = self.create_from_template_dialog.findChild(QListView, 'genericTemplates')
self.refresh_create_from_template_list()
self.create_from_template_dialog.exec()
def create_from_template_accepted_handler(self) -> None:
"""
Searches the available gems list for selected gems and attempts to add each one to the current gem.
Updates UI after completion.
:return: None
"""
create_gem_item = self.get_selected_gem_template()
if not create_gem_item:
return
folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name",
manifest.get_o3de_gems_folder().as_posix())
folder_dialog.setFileMode(QFileDialog.AnyFile)
folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
gem_count = 0
gem_name = "MyNewGem"
while os.path.exists(os.path.join(engine_path, gem_name)):
gem_name = f"MyNewGem{gem_count}"
gem_count += 1
folder_dialog.selectFile(gem_name)
gem_folder = None
if folder_dialog.exec():
gem_folder = folder_dialog.selectedFiles()
if gem_folder:
if engine_template.create_gem(gem_folder[0], create_gem_item[1]) == 0:
# Success
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"gem {os.path.basename(os.path.normpath(gem_folder[0]))} created."
" Build your\nnew gem before hitting OK to launch.")
msg_box.exec()
return
def add_project_handler(self):
"""
Open a file search dialog looking for a folder which contains a valid project. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder",
manifest.get_o3de_projects_folder().as_posix())
if project_folder:
if register.register(project_path=project_folder) == 0:
# Success
self.refresh_project_list()
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Added Project {project_folder}.")
msg_box.exec()
return
def add_gem_handler(self):
"""
Open a file search dialog looking for a folder which contains a gem. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder",
manifest.get_o3de_gems_folder().as_posix())
if gem_folder:
if register.register(gem_path=gem_folder) == 0:
# Success
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Added Gem {gem_folder}.")
msg_box.exec()
return
def add_template_handler(self):
"""
Open a file search dialog looking for a folder which contains a valid template. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder",
manifest.get_o3de_templates_folder().as_posix())
if template_folder:
if register.register(template_path=template_folder) == 0:
# Success
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Added Template {template_folder}.")
msg_box.exec()
return
def add_restricted_handler(self):
"""
Open a file search dialog looking for a folder which contains a valid template. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder",
manifest.get_o3de_restricted_folder().as_posix())
if restricted_folder:
if register.register(restricted_path=restricted_folder) == 0:
# Success
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Added Restricted {restricted_folder}.")
msg_box.exec()
return
def remove_project_handler(self):
"""
Open a file search dialog looking for a folder which contains a valid project. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder",
manifest.get_o3de_projects_folder().as_posix())
if project_folder:
if register.register(project_path=project_folder, remove=True) == 0:
# Success
self.refresh_project_list()
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Removed Project {project_folder}.")
msg_box.exec()
return
def remove_gem_handler(self):
"""
Open a file search dialog looking for a folder which contains a gem. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder",
manifest.get_o3de_gems_folder().as_posix())
if gem_folder:
if register.register(gem_path=gem_folder, remove=True) == 0:
# Success
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Removed Gem {gem_folder}.")
msg_box.exec()
return
def remove_template_handler(self):
"""
Open a file search dialog looking for a folder which contains a valid template. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder",
manifest.get_o3de_templates_folder().as_posix())
if template_folder:
if register.register(template_path=template_folder, remove=True) == 0:
# Success
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Removed Template {template_folder}.")
msg_box.exec()
return
def remove_restricted_handler(self):
"""
Open a file search dialog looking for a folder which contains a valid template. If valid
will update the mru list with the new entry, if invalid will warn the user.
:return: None
"""
restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder",
manifest.get_o3de_restricted_folder().as_posix())
if restricted_folder:
if register.register(restricted_path=restricted_folder, remove=True) == 0:
# Success
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Removed Restricted {restricted_folder}.")
msg_box.exec()
return
def manage_project_gem_targets_handler(self):
"""
Opens the Gem management pane. Waits for the load thread to complete if still running and displays all
active gems for the current project as well as all available gems which aren't currently active.
:return: None
"""
if not self.get_selected_project_path():
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText("Please select a project")
msg_box.exec()
return
loader = QUiLoader()
self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix())
if not self.manage_project_gem_targets_file:
logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}')
return
self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file)
if not self.manage_project_gem_targets_dialog:
logger.error(f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}')
return
self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Gems for Project:"
f" {self.get_selected_project_name()}")
self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton')
self.add_gem_button.clicked.connect(self.add_project_gem_targets_handler)
self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView,
'availableGemTargetsList')
self.refresh_project_gem_targets_available_list()
self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton,
'removeGemTargetsButton')
self.remove_project_gem_targets_button.clicked.connect(self.remove_project_gem_targets_handler)
self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView,
'enabledGemTargetsList')
self.refresh_project_gem_targets_enabled_list()
self.manage_project_gem_targets_dialog.exec()
def manage_project_gem_targets_get_selected_available_gems(self) -> list:
selected_items = self.available_gem_targets_list.selectionModel().selectedRows()
return [(self.available_gem_targets_list.model().data(item)) for item in selected_items]
def manage_project_gem_targets_get_selected_enabled_gems(self) -> list:
selected_items = self.enabled_gem_targets_list.selectionModel().selectedRows()
return [(self.enabled_gem_targets_list.model().data(item)) for item in selected_items]
def add_project_gem_targets_handler(self) -> None:
gem_paths = manifest.get_all_gems()
for gem_target in self.manage_project_gem_targets_get_selected_available_gems():
for gem_path in gem_paths:
enable_gem.enable_gem_in_project(gem_path=gem_path,
project_path=self.get_selected_project_path())
self.refresh_project_gem_targets_available_list()
self.refresh_project_gem_targets_enabled_list()
return
self.refresh_project_gem_targets_available_list()
self.refresh_project_gem_targets_enabled_list()
def remove_project_gem_targets_handler(self):
gem_paths = manifest.get_all_gems()
for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems():
for gem_path in gem_paths:
disable_gem.disable_gem_in_project(gem_path=gem_path,
project_path=self.get_selected_project_path())
self.refresh_project_gem_targets_available_list()
self.refresh_project_gem_targets_enabled_list()
return
self.refresh_project_gem_targets_available_list()
self.refresh_project_gem_targets_enabled_list()
def refresh_project_gem_targets_enabled_list(self) -> None:
enabled_project_gem_targets_model = QStandardItemModel()
enabled_project_gems = cmake.get_project_gems(project_path=self.get_selected_project_path())
for gem_target in sorted(enabled_project_gems):
model_item = QStandardItem(gem_target)
enabled_project_gem_targets_model.appendRow(model_item)
self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model)
def refresh_project_gem_targets_available_list(self) -> None:
available_project_gem_targets_model = QStandardItemModel()
enabled_project_gem_targets = cmake.get_project_gems(project_path=self.get_selected_project_path())
all_gem_targets = manifest.get_all_gems()
for gem_target in sorted(all_gem_targets):
if gem_target not in enabled_project_gem_targets:
model_item = QStandardItem(gem_target)
available_project_gem_targets_model.appendRow(model_item)
self.available_gem_targets_list.setModel(available_project_gem_targets_model)
def refresh_create_project_template_list(self) -> None:
self.create_project_template_model = QStandardItemModel()
for project_template_path in manifest.get_project_templates():
model_item = QStandardItem(project_template_path)
self.create_project_template_model.appendRow(model_item)
self.create_project_template_list.setModel(self.create_project_template_model)
def refresh_create_gem_template_list(self) -> None:
self.create_gem_template_model = QStandardItemModel()
for gem_template_path in manifest.get_gem_templates():
model_item = QStandardItem(gem_template_path)
self.create_gem_template_model.appendRow(model_item)
self.create_gem_template_list.setModel(self.create_gem_template_model)
def refresh_create_from_template_list(self) -> None:
self.create_from_template_model = QStandardItemModel()
for generic_template_path in manifest.get_generic_templates():
model_item = QStandardItem(generic_template_path)
self.create_from_template_model.appendRow(model_item)
self.create_from_template_list.setModel(self.create_from_template_model)
def update_mru_list(self, used_project: str) -> None:
"""
Promote a supplied project name to the "most recent" in a given MRU list.
:param used_project: path to project to promote
:param file_path: path to mru list file
:return: None
"""
used_project = os.path.normpath(used_project)
if not os.path.exists(os.path.dirname(self.mru_file_path)):
os.makedirs(os.path.dirname(self.mru_file_path), exist_ok=True)
mru_data = {}
try:
with open(self.mru_file_path, 'r') as mru_file:
mru_data = json.loads(mru_file.read())
except FileNotFoundError:
pass
except json.JSONDecodeError:
pass
recent_list = mru_data.get('Projects', [])
recent_list = [item for item in recent_list if item.get('Path') != used_project and
self.is_project_folder(item.get('Path'))]
new_list = [{'Path': used_project}]
new_list.extend(recent_list)
mru_data['Projects'] = new_list
try:
with open(self.mru_file_path, 'w') as mru_file:
mru_file.write(json.dumps(mru_data, indent=1))
except PermissionError as e:
logger.warning(f"Failed to write {self.mru_file_path} with error {e}")
def get_mru_list(self) -> List[str]:
"""
Retrieve the current MRU list. Does not perform validation that the projects still appear valid
:return: list of full path strings to project folders
"""
if not os.path.exists(os.path.dirname(self.mru_file_path)):
return []
try:
with open(self.mru_file_path, 'r') as mru_file:
mru_data = json.loads(mru_file.read())
except FileNotFoundError:
return []
except json.JSONDecodeError:
logger.error(f'MRU list at {self.mru_file_path} is not valid JSON')
return []
recent_list = mru_data.get('Projects', [])
return [item.get('Path') for item in recent_list if item.get('Path') is not None]
@Slot(str)
def handle_log_message(self, message: str) -> None:
"""
Signal handler for messages from the logger. Displays the most recent warning/error
:param message: formatted log message from DialogLoggerSignaller
:return:
"""
if not self.log_display:
return
self.log_display.setText(message)
self.log_display.setToolTip(message)
if __name__ == "__main__":
dialog_app = QApplication(sys.argv)
my_dialog = ProjectManagerDialog()
dialog_app.exec_()
sys.exit(0)
-61
View File
@@ -1,61 +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 logging
from pathlib import Path
logger = logging.getLogger()
pyside_initialized = False
old_env = os.environ.copy()
# Helper to extend OS PATH for pyside to locate our QT binaries based on our build folder
def add_pyside_environment(bin_path):
if is_pyside_ready():
# No need to reinitialize currently
logger.info("Pyside environment already initialized")
return
global old_env
old_env = os.environ.copy()
binaries_path = Path(os.path.normpath(bin_path))
platforms_path = binaries_path.joinpath("platforms")
logger.info(f'Adding binaries path {binaries_path}')
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(platforms_path)
path = os.environ['PATH']
new_path = os.pathsep.join([str(binaries_path), str(platforms_path), path])
os.environ['PATH'] = new_path
global pyside_initialized
pyside_initialized = True
def is_pyside_ready():
return pyside_initialized
def is_configuration_valid(workspace):
return os.path.basename(workspace.paths.build_directory()) != "debug"
def uninstall_env():
if not is_pyside_ready():
logger.warning("Pyside not initialized")
return os.environ
global old_env
if old_env.get("QT_QPA_PLATFORM_PLUGIN_PATH"):
old_env.pop("QT_QPA_PLATFORM_PLUGIN_PATH")
os.environ = old_env
return old_env
-10
View File
@@ -1,10 +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.
#
@@ -1,255 +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 pytest
'''
import os
import sys
import tempfile
import logging
import pathlib
from unittest.mock import MagicMock
logger = logging.getLogger()
# Code lives one folder above
project_manager_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(project_manager_path)
from pyside import add_pyside_environment, is_configuration_valid
from ly_test_tools import WINDOWS
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
executable_path = ''
from cmake.Tools import registration
from cmake.Tools import engine_template
class ProjectHelper:
def __init__(self):
self._temp_directory = pathlib.Path(tempfile.TemporaryDirectory().name).resolve()
self._temp_directory.mkdir(parents=True, exist_ok=True)
self.home_path = self._temp_directory
registration.override_home_folder = self.home_path
self.engine_path = registration.get_this_engine_path()
if registration.register(engine_path=self.engine_path):
assert True, f"Failed to register the engine."
if registration.register_shipped_engine_o3de_objects():
assert True, f"Failed to register shipped engine objects."
self.projects_folder = registration.get_o3de_projects_folder()
if not self.projects_folder.is_dir():
assert True
self.application = None
self.dialog = None
def create_empty_projects(self):
self.project_1_dir = self.projects_folder / "Project1"
if engine_template.create_project(project_manager_path=self.project_1_dir):
assert True, f"Failed to create Project1."
self.project_2_dir = self.projects_folder / "Project2"
if engine_template.create_project(project_manager_path=self.project_2_dir):
assert True, f"Failed to create Project2."
self.project_3_dir = self.projects_folder / "Project3"
if engine_template.create_project(project_manager_path=self.project_3_dir):
assert True, f"Failed to create Project3."
self.invalid_project_dir = self.projects_folder / "InvalidProject"
self.invalid_project_dir.mkdir(parents=True, exist_ok=True)
def setup_dialog_test(self, workspace):
add_pyside_environment(workspace.paths.build_directory())
if not is_configuration_valid(workspace):
# This is essentially skipif debug. Our debug tests use our profile version of python, but that means we'd
# need to use the profile version of PySide which works with the profile QT libs which aren't in the debug
# folder we've built.
return None
from PySide2.QtWidgets import QApplication, QMessageBox
if QApplication.instance():
self.application = QApplication.instance()
else:
self.application = QApplication(sys.argv)
assert self.application
from projects import ProjectManagerDialog
try:
self.dialog = ProjectManagerDialog(settings_folder=self.home_path)
return self.dialog
except Exception as e:
logger.error(f'Failed to create ProjectManagerDialog with error {e}')
return None
def create_project_from_template(self, project_name) -> bool:
"""
Uses the dialog to create a temporary project based on the first template found
:param project_name: Name of project to create. Will be created under temp_project_root
:return: True for Success, False for failure
"""
from PySide2.QtWidgets import QWidget, QFileDialog
from projects import ProjectManagerDialog
QWidget.exec = MagicMock()
self.dialog.create_project_handler()
QWidget.exec.assert_called_once()
assert len(self.dialog.project_templates), 'Failed to find any project templates'
ProjectManagerDialog.get_selected_project_template = MagicMock(return_value=self.dialog.project_templates[0])
QFileDialog.exec = MagicMock()
create_project_path = self.projects_folder / project_name
QFileDialog.selectedFiles = MagicMock(return_value=[create_project_path])
self.dialog.create_project_accepted_handler()
if create_project_path.is_dir():
assert True, f"Expected project creation folder not found at {create_project_path}"
if QWidget.exec.call_count == 2:
assert True, "Message box confirming project creation failed to show"
@pytest.fixture
def project_helper():
return ProjectHelper()
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
def test_logger_handler(workspace, project_helper):
my_dialog = project_helper.setup_dialog_test(workspace)
if not my_dialog:
return
from PySide2.QtWidgets import QMessageBox
QMessageBox.warning = MagicMock()
logger.error(f'Testing logger')
QMessageBox.warning.assert_called_once()
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
def test_mru_list(workspace, project_helper):
my_dialog = project_helper.setup_dialog_test(workspace)
if not my_dialog:
return
project_helper.create_empty_projects()
from PySide2.QtWidgets import QMessageBox
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 0, f'MRU list unexpectedly had entries: {mru_list}'
QMessageBox.warning = MagicMock()
my_dialog.add_project(project_helper.invalid_project_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 0, f'MRU list unexpectedly added an invalid project : {mru_list}'
QMessageBox.warning.assert_called_once()
my_dialog.add_project(project_helper.project_1_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 1, f'MRU list failed to add project at {project_helper.project_1_dir}'
my_dialog.add_project(project_helper.project_1_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}'
my_dialog.update_mru_list(project_helper.project_1_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}'
my_dialog.add_project(project_helper.project_2_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 2, f'MRU list failed to add project at {project_helper.project_2_dir}'
assert mru_list[0] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't first item"
assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item"
my_dialog.update_mru_list(project_helper.project_1_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 2, f'MRU list added wrong items {mru_list}'
assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item"
assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item"
my_dialog.add_project(project_helper.invalid_project_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 2, f'MRU list added invalid item {mru_list}'
assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item"
assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item"
my_dialog.add_project(project_helper.project_3_dir)
mru_list = my_dialog.get_mru_list()
assert len(mru_list) == 3, f'MRU list failed to add {project_helper.project_3_dir} : {mru_list}'
assert mru_list[0] == project_helper.project_3_dir, f"{project_helper.project_3_dir} wasn't first item"
assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item"
assert mru_list[2] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't third item"
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
def test_create_project(workspace, project_helper):
my_dialog = project_helper.setup_dialog_test(workspace)
if not my_dialog:
return
project_helper.create_project_from_template("TestCreateProject")
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
def test_add_remove_gems(workspace, project_helper):
my_dialog = project_helper.setup_dialog_test(workspace)
if not my_dialog:
return
my_project_name = "TestAddRemoveGems"
project_helper.create_project_from_template(project_manager_path=my_project_name)
my_project_path = project_helper.projects_folder / my_project_name
from PySide2.QtWidgets import QWidget, QFileDialog
from projects import ProjectManagerDialog
assert my_dialog.get_selected_project_path() == my_project_path, "TestAddRemoveGems project not selected"
QWidget.exec = MagicMock()
my_dialog.manage_gems_handler()
assert my_dialog.manage_gem_targets_dialog, "No gem management dialog created"
QWidget.exec.assert_called_once()
if not len(my_dialog.all_gems_list):
assert True, 'Failed to find any gems'
my_test_gem_path = my_dialog.all_gems_list[0]
gem_data = registration.get_gem_data(my_test_gem_path)
my_test_gem_selection = (my_test_gem_name, my_test_gem_path)
ProjectManagerDialog.get_selected_add_gems = MagicMock(return_value=[my_test_gem_selection])
assert my_test_gem_name, "No Name set in test gem"
assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} already in project gem list'
my_dialog.add_gems_handler()
assert my_test_gem_name in my_dialog.project_gem_list, f'Gem {my_test_gem_name} failed to add to gem list'
ProjectManagerDialog.get_selected_project_gems = MagicMock(return_value=[my_test_gem_name])
my_dialog.remove_gems_handler()
assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} still in project gem list'
'''
def test_project_place_holder():
pass
@@ -1,54 +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 pytest
import sys
import os
pyside_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(pyside_path)
from pyside import add_pyside_environment, is_pyside_ready, is_configuration_valid
from ly_test_tools import WINDOWS
import logging
logger = logging.getLogger()
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
def test_add_pyside_environment(workspace):
import_failed = False
try:
import PySide2
except ImportError:
import_failed = True
if not import_failed:
cur_path = sys.path
logger.warning(f"Expected to fail initial import but passed. Sys path was {cur_path}")
assert is_pyside_ready() is False, "Expected pyside not to be initialized yet"
add_pyside_environment(workspace.paths.build_directory())
assert is_pyside_ready() is True, "Expected pyside to be initialized yet"
try:
import PySide2
if not is_configuration_valid(workspace):
return
from PySide2.QtWidgets import QApplication
except ImportError as e:
assert False, f"Failed to import PySide2 with error {e}"
try:
from PySide2.QtWidgets import QApplication
except ImportError as e:
assert False, f"Failed to import QApplication from PySide2.QtWidgets with error {e}"
@@ -1,94 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>createFromTemplateDialog</class>
<widget class="QDialog" name="createFromTemplateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>467</width>
<height>288</height>
</rect>
</property>
<property name="windowTitle">
<string>Create From Template</string>
</property>
<widget class="QDialogButtonBox" name="okCancel">
<property name="geometry">
<rect>
<x>50</x>
<y>250</y>
<width>400</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QListView" name="genericTemplates">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>449</width>
<height>221</height>
</rect>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>300</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Available Templates</string>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>okCancel</sender>
<signal>accepted()</signal>
<receiver>createFromTemplateDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>okCancel</sender>
<signal>rejected()</signal>
<receiver>createFromTemplateDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
-94
View File
@@ -1,94 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>createGemDialog</class>
<widget class="QDialog" name="createGemDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>467</width>
<height>288</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Gem</string>
</property>
<widget class="QDialogButtonBox" name="okCancel">
<property name="geometry">
<rect>
<x>50</x>
<y>250</y>
<width>400</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QListView" name="gemTemplates">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>449</width>
<height>221</height>
</rect>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>300</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Available Templates</string>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>okCancel</sender>
<signal>accepted()</signal>
<receiver>createGemDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>okCancel</sender>
<signal>rejected()</signal>
<receiver>createGemDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
@@ -1,94 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>createProjectDialog</class>
<widget class="QDialog" name="createProjectDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>467</width>
<height>288</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Project</string>
</property>
<widget class="QDialogButtonBox" name="okCancel">
<property name="geometry">
<rect>
<x>50</x>
<y>250</y>
<width>400</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QListView" name="projectTemplates">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>449</width>
<height>221</height>
</rect>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>0</y>
<width>300</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Available Templates</string>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>okCancel</sender>
<signal>accepted()</signal>
<receiver>createProjectDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>okCancel</sender>
<signal>rejected()</signal>
<receiver>createProjectDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>manageGemTargetsDialog</class>
<widget class="QDialog" name="manageGemTargetsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>702</width>
<height>297</height>
</rect>
</property>
<property name="windowTitle">
<string>Manage Gem Targets</string>
</property>
<widget class="QDialogButtonBox" name="close">
<property name="geometry">
<rect>
<x>310</x>
<y>260</y>
<width>71</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
<widget class="QListView" name="availableGemTargetsList">
<property name="geometry">
<rect>
<x>440</x>
<y>50</y>
<width>250</width>
<height>221</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
<widget class="QListView" name="enabledGemTargetsList">
<property name="geometry">
<rect>
<x>10</x>
<y>50</y>
<width>250</width>
<height>221</height>
</rect>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>440</x>
<y>30</y>
<width>251</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Available Gem Targets</string>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>251</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Enabled Gem Targets</string>
</property>
</widget>
<widget class="QPushButton" name="removeGemTargetsButton">
<property name="geometry">
<rect>
<x>264</x>
<y>130</y>
<width>171</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Remove Gem Targets &gt;&gt;</string>
</property>
</widget>
<widget class="QPushButton" name="addGemTargetsButton">
<property name="geometry">
<rect>
<x>264</x>
<y>100</y>
<width>171</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>&lt;&lt; Add Gem Targets</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>5</y>
<width>400</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Adding new Gem Targets may require rebuilding</string>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>close</sender>
<signal>accepted()</signal>
<receiver>manageGemTargetsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>close</sender>
<signal>rejected()</signal>
<receiver>manageGemTargetsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:113be7ded1969e0d535722382b6d3730eed052a10430cf23804ae7f20414b999
size 108278
@@ -1,407 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>712</width>
<height>395</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>O3DE</string>
</property>
<property name="whatsThis">
<string>Select and manage your projects for O3DE</string>
</property>
<widget class="QDialogButtonBox" name="okCancel">
<property name="geometry">
<rect>
<x>540</x>
<y>360</y>
<width>161</width>
<height>31</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QComboBox" name="projectListBox">
<property name="geometry">
<rect>
<x>10</x>
<y>30</y>
<width>691</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Current project to launch or manage gems for.</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>47</width>
<height>13</height>
</rect>
</property>
<property name="text">
<string>Project</string>
</property>
</widget>
<widget class="QLabel" name="logDisplay">
<property name="geometry">
<rect>
<x>270</x>
<y>220</y>
<width>431</width>
<height>141</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QGroupBox" name="createGroupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>70</y>
<width>241</width>
<height>151</height>
</rect>
</property>
<property name="title">
<string>Create</string>
</property>
<widget class="QPushButton" name="createFromTemplateButton">
<property name="geometry">
<rect>
<x>10</x>
<y>110</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Create a new O3DE object from a pre configured template.</string>
</property>
<property name="text">
<string>Create From Template</string>
</property>
</widget>
<widget class="QPushButton" name="createProjectButton">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Create a new O3DE object from a pre configured template.</string>
</property>
<property name="text">
<string>Create Project</string>
</property>
</widget>
<widget class="QPushButton" name="createGemButton">
<property name="geometry">
<rect>
<x>11</x>
<y>50</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Create a new O3DE object from a pre configured template.</string>
</property>
<property name="text">
<string>Create Gem</string>
</property>
</widget>
<widget class="QPushButton" name="createTemplateButton">
<property name="geometry">
<rect>
<x>11</x>
<y>80</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Create a new O3DE object from a pre configured template.</string>
</property>
<property name="text">
<string>Create Template</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="addRemoveGroupBox">
<property name="geometry">
<rect>
<x>260</x>
<y>70</y>
<width>451</width>
<height>141</height>
</rect>
</property>
<property name="title">
<string>Registration</string>
</property>
<widget class="QPushButton" name="addTemplateButton">
<property name="geometry">
<rect>
<x>10</x>
<y>80</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Add Template</string>
</property>
</widget>
<widget class="QPushButton" name="addProjectButton">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Add Project</string>
</property>
</widget>
<widget class="QPushButton" name="addGemButton">
<property name="geometry">
<rect>
<x>10</x>
<y>50</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Add Gem</string>
</property>
</widget>
<widget class="QPushButton" name="addRestrictedButton">
<property name="geometry">
<rect>
<x>10</x>
<y>110</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Add Restricted</string>
</property>
</widget>
<widget class="QPushButton" name="removeRestrictedButton">
<property name="geometry">
<rect>
<x>230</x>
<y>110</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Remove Restricted</string>
</property>
</widget>
<widget class="QPushButton" name="removeProjectButton">
<property name="geometry">
<rect>
<x>230</x>
<y>20</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Remove Project</string>
</property>
</widget>
<widget class="QPushButton" name="removeGemButton">
<property name="geometry">
<rect>
<x>230</x>
<y>50</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Remove Gem</string>
</property>
</widget>
<widget class="QPushButton" name="removeTemplateButton">
<property name="geometry">
<rect>
<x>230</x>
<y>80</y>
<width>211</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Browse for an existing O3DE project.</string>
</property>
<property name="text">
<string>Remove Template</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="manageProjectGroupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>230</y>
<width>241</width>
<height>121</height>
</rect>
</property>
<property name="title">
<string>Manage Project</string>
</property>
<widget class="QPushButton" name="manageServerGemTargetsButton">
<property name="geometry">
<rect>
<x>10</x>
<y>80</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Add or remove gems from your selected project. Gems add and remove additional assets and features to projects.</string>
</property>
<property name="text">
<string>Manage Server Gem Targets</string>
</property>
</widget>
<widget class="QPushButton" name="manageToolGemTargetsButton">
<property name="geometry">
<rect>
<x>10</x>
<y>50</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Add or remove gems from your selected project. Gems add and remove additional assets and features to projects.</string>
</property>
<property name="text">
<string>Manage Tool Gem Targets</string>
</property>
</widget>
<widget class="QPushButton" name="manageRuntimeGemTargetsButton">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>221</width>
<height>31</height>
</rect>
</property>
<property name="whatsThis">
<string>Add or remove gems from your selected project. Gems add and remove additional assets and features to projects.</string>
</property>
<property name="text">
<string>Manage Runtime Gem Targets</string>
</property>
</widget>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>okCancel</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>okCancel</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>