You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/cmake/Tools/engine_template.py

2439 lines
137 KiB
Python

#
# 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.
#
"""
This file contains all the code that has to do with creating and instantiate engine templates
"""
import argparse
import logging
import os
import shutil
import sys
import json
import uuid
import re
from cmake.Tools import utils
import cmake.Tools.registration as registration
logger = logging.getLogger()
logging.basicConfig()
binary_file_ext = {
'.pak',
'.bin',
'.png',
'.tga',
'.bmp',
'.dat',
'.trp',
'.fbx',
'.mb',
'.tif',
'.tiff',
'.bmp',
'.dds',
'.dds0',
'.dds1',
'.dds2',
'.dds3',
'.dds4',
'.dds5',
'.dds6',
'.dds7',
'.dds8',
'.dds9',
'.ctc',
'.bnk',
'.wav',
'.dat',
'.akd',
'.cgf',
'.wem',
'.assetinfo',
'.animgraph',
'.motionset'
}
expect_license_info_ext = {
'.cpp',
'.h',
'.hpp',
'.inl',
'.py',
'.cmake'
}
restricted_platforms = {
'Provo',
'Salem',
'Jasper',
'Paris'
}
template_file_name = 'template.json'
def _transform(s_data: str,
replacements: list,
keep_license_text: bool = False) -> str:
"""
Internal function called to transform source data into templated data
:param s_data: the source data to be transformed
:param replacements: list of transformation pairs A->B
:param keep_license_text: whether or not you want to keep license text
:return: the potentially transformed data
"""
# copy the s_data into t_data, then apply all transformations only on t_data
t_data = s_data
for replacement in replacements:
t_data = t_data.replace(replacement[0], replacement[1])
# if someone hand edits the template to have ${Random_Uuid} then replace it with a randomly generated uuid
while '${Random_Uuid}' in t_data:
t_data = t_data.replace('${Random_Uuid}', str(uuid.uuid4()), 1)
##################################################################
# For some reason the re.sub call here gets into some kind of infinite
# loop and never returns on some files consistently.
# Until I figure out why we can use the string replacement method
# if not keep_license_text:
# t_data = re.sub(r"^(//|'''|#)\s*{BEGIN_LICENSE}((.|\n)*){END_LICENSE}\n", "", t_data, flags=re.DOTALL)
if not keep_license_text:
while '{BEGIN_LICENSE}' in t_data:
start = t_data.find('{BEGIN_LICENSE}')
if start != -1:
line_start = t_data.rfind('\n', 0, start)
if line_start == -1:
line_start = 0
end = t_data.find('{END_LICENSE}')
end = t_data.find('\n', end)
if end != -1:
t_data = t_data[:line_start] + t_data[end + 1:]
###################################################################
return t_data
def _transform_copy(source_file: str,
destination_file: str,
replacements: list,
keep_license_text: bool = False) -> None:
"""
Internal function called to transform and copy a source file into templated destination file
:param source_file: the source file to be transformed
:param destination_file: the destination file, this is the transformed file
:param replacements: list of transformation pairs A->B
:param keep_license_text: whether or not you want to keep license text
"""
# if its a known binary type just copy it, else try to transform it.
# if its an unknown binary type it will throw and we catch copy.
# if we had no known binary type it would still work, but much slower
name, ext = os.path.splitext(source_file)
if ext in binary_file_ext:
shutil.copy(source_file, destination_file)
else:
try:
# open the file and transform its data
with open(source_file, 'r') as s:
s_data = s.read()
d_data = _transform(s_data, replacements, keep_license_text)
# if the dst file we are about to write exists already for some reason delete it
if os.path.isfile(destination_file):
os.unlink(destination_file)
with open(destination_file, 'w') as d:
d.write(d_data)
except Exception as e:
# usually happens if there is an unknown binary type
shutil.copy(source_file, destination_file)
pass
def _execute_template_json(json_data: dict,
destination_path: str,
template_path: str,
replacements: list,
keep_license_text: bool = False) -> None:
# create dirs first
# for each createDirectory entry, transform the folder name
for create_directory in json_data['createDirectories']:
# construct the new folder name
new_dir = f"{destination_path}/{create_directory['dir']}"
# transform the folder name
new_dir = _transform(new_dir, replacements, keep_license_text)
# create the folder
os.makedirs(new_dir, exist_ok=True)
# for each copyFiles entry, _transformCopy the templated source file into a concrete instance file or
# regular copy if not templated
for copy_file in json_data['copyFiles']:
# construct the input file name
in_file = f"{template_path}/Template/{copy_file['file']}"
# the file can be marked as optional, if it is and it does not exist skip
if copy_file['isOptional'] and copy_file['isOptional'] == 'true':
if not os.path.isfile(in_file):
continue
# construct the output file name
out_file = f"{destination_path}/{copy_file['file']}"
# transform the output file name
out_file = _transform(out_file, replacements, keep_license_text)
# if for some reason the output folder for this file was not created above do it now
os.makedirs(os.path.dirname(out_file), exist_ok=True)
# if templated _transformCopy the file, if not just copy it
if copy_file['isTemplated']:
_transform_copy(in_file, out_file, replacements, keep_license_text)
else:
shutil.copy(in_file, out_file)
def _execute_restricted_template_json(json_data: dict,
restricted_platform: str,
destination_name,
template_name,
destination_path: str,
destination_restricted_path: str,
template_restricted_path: str,
destination_restricted_platform_relative_path: str,
template_restricted_platform_relative_path: str,
replacements: list,
keep_restricted_in_instance: bool = False,
keep_license_text: bool = False) -> None:
# if we are not keeping restricted in instance make restricted.json if not present
if not keep_restricted_in_instance:
restricted_json = f"{destination_restricted_path}/restricted.json".replace('//', '/')
os.makedirs(os.path.dirname(restricted_json), exist_ok=True)
if not os.path.isfile(restricted_json):
with open(restricted_json, 'w') as s:
restricted_json_data = {}
restricted_json_data.update({"restricted_name": destination_name})
s.write(json.dumps(restricted_json_data, indent=4))
# create dirs first
# for each createDirectory entry, transform the folder name
for create_directory in json_data['createDirectories']:
# construct the new folder name
new_dir = f"{destination_restricted_path}/{restricted_platform}/{destination_restricted_platform_relative_path}/{destination_name}/{create_directory['dir']}".replace(
'//', '/')
if keep_restricted_in_instance:
new_dir = f"{destination_path}/{create_directory['origin']}".replace('//', '/')
# transform the folder name
new_dir = _transform(new_dir, replacements, keep_license_text)
# create the folder
os.makedirs(new_dir, exist_ok=True)
# for each copyFiles entry, _transformCopy the templated source file into a concrete instance file or
# regular copy if not templated
for copy_file in json_data['copyFiles']:
# construct the input file name
in_file = f"{template_restricted_path}/{restricted_platform}/{template_restricted_platform_relative_path}/{template_name}/Template/{copy_file['file']}".replace(
'//', '/')
# the file can be marked as optional, if it is and it does not exist skip
if copy_file['isOptional'] and copy_file['isOptional'] == 'true':
if not os.path.isfile(in_file):
continue
# construct the output file name
out_file = f"{destination_restricted_path}/{restricted_platform}/{destination_restricted_platform_relative_path}/{destination_name}/{copy_file['file']}".replace(
'//', '/')
if keep_restricted_in_instance:
out_file = f"{destination_path}/{copy_file['origin']}".replace('//', '/')
# transform the output file name
out_file = _transform(out_file, replacements, keep_license_text)
# if for some reason the output folder for this file was not created above do it now
os.makedirs(os.path.dirname(out_file), exist_ok=True)
# if templated _transformCopy the file, if not just copy it
if copy_file['isTemplated']:
_transform_copy(in_file, out_file, replacements, keep_license_text)
else:
shutil.copy(in_file, out_file)
def _instantiate_template(template_json_data: dict,
destination_name: str,
template_name: str,
destination_path: str,
template_path: str,
destination_restricted_path: str,
template_restricted_path: str,
destination_restricted_platform_relative_path: str,
template_restricted_platform_relative_path: str,
replacements: list,
keep_restricted_in_instance: bool = False,
keep_license_text: bool = False) -> int:
"""
Internal function to create a concrete instance from a template
:param template_json_data: the template json data
:param destination_name: the name of folder you want to instantiate the template in
:param template_name: the name of the template
:param destination_path: the path you want to instantiate the template in
:param template_path: the path of the template
:param destination_restricted_path: the path of the restricted destination
:param template_restricted_path: the path of the restricted template
:param destination_restricted_platform_relative_path: any path after the Platform of the restricted destination
:param template_restricted_platform_relative_path: any path after the Platform of the restricted template
:param replacements: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs
Ex. ${Name},TestGem,${Player},TestGemPlayer
This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer'
:param keep_restricted_in_instance: whether or not you want to keep the templates restricted files in your instance
or separate them out into the restricted folder
:param keep_license_text: whether or not you want to keep the templates license text in your instance.
template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE},
this controls if you want to keep the license text from the template in the new instance. It is false by default
because most customers will not want license text in their instances, but we may want to keep them.
:return: 0 for success or non 0 failure code
"""
# execute the template json
_execute_template_json(template_json_data,
destination_path,
template_path,
replacements,
keep_license_text)
# execute restricted platform jsons if any
if template_restricted_path:
for restricted_platform in os.listdir(template_restricted_path):
if os.path.isfile(restricted_platform):
continue
template_restricted_platform = f'{template_restricted_path}/{restricted_platform}'
template_restricted_platform_path_rel = f'{template_restricted_platform}/{template_restricted_platform_relative_path}/{template_name}'
platform_json = f'{template_restricted_platform_path_rel}/{template_file_name}'.replace('//', '/')
if os.path.isfile(platform_json):
if not registration.valid_o3de_template_json(platform_json):
logger.error(f'Template json {platform_json} is invalid.')
return 1
# load the template json and execute it
with open(platform_json, 'r') as s:
try:
json_data = json.load(s)
except Exception as e:
logger.error(f'Failed to load {platform_json}: ' + str(e))
return 1
else:
_execute_restricted_template_json(json_data,
restricted_platform,
destination_name,
template_name,
destination_path,
destination_restricted_path,
template_restricted_path,
destination_restricted_platform_relative_path,
template_restricted_platform_relative_path,
replacements,
keep_restricted_in_instance,
keep_license_text)
return 0
def create_template(source_path: str,
template_path: str,
source_restricted_path: str = None,
source_restricted_name: str = None,
template_restricted_path: str = None,
template_restricted_name: str = None,
source_restricted_platform_relative_path: str = None,
template_restricted_platform_relative_path: str = None,
keep_restricted_in_template: bool = False,
keep_license_text: bool = False,
replace: list = None) -> int:
"""
Create a template from a source directory using replacement
:param source_path: The path to the source that you want to make into a template
:param template_path: the path of the template to create, can be absolute or relative to default templates path
:param source_restricted_path: path to the source restricted folder
:param source_restricted_name: name of the source restricted folder
:param template_restricted_path: path to the templates restricted folder
:param template_restricted_name: name of the templates restricted folder
:param source_restricted_platform_relative_path: any path after the platform in the source restricted
:param template_restricted_platform_relative_path: any path after the platform in the template restricted
:param replace: optional list of strings uses to make templated parameters out of concrete names. X->Y pairs
Ex. TestGem,${Name},TestGemPlayer,${Player}
This will cause all references to 'TestGem' be replaced by ${Name}, and all 'TestGemPlayer' replaced by ${Player}
Note these replacements are executed in order, so if you have larger matches, do them first, i.e.
TestGemPlayer,${Player},TestGem,${Name}
TestGemPlayer will get matched first and become ${Player} and will not become ${Name}Player
:param keep_restricted_in_template: whether or not you want to keep the templates restricted in your template.
:param keep_license_text: whether or not you want to keep the templates license text in your instance.
Templated files can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE},
this controls if you want to keep the license text from the template in the new instance. It is false by default
because most people will not want license text in their instances.
:return: 0 for success or non 0 failure code
"""
# if no src path error
if not source_path:
logger.error('Src path cannot be empty.')
return 1
source_path = source_path.replace('\\', '/')
if not os.path.isdir(source_path):
logger.error(f'Src path {source_path} is not a folder.')
return 1
# source_name is now the last component of the source_path
source_name = os.path.basename(source_path)
# if no template path, error
if not template_path:
logger.info(f'Template path empty. Using source name {source_name}')
template_path = source_name
template_path = template_path.replace('\\', '/')
if not os.path.isabs(template_path):
default_templates_folder = registration.get_registered(default_folder='templates')
template_path = f'{default_templates_folder}/{template_path}'
logger.info(f'Template path not a full path. Using default templates folder {template_path}')
if os.path.isdir(template_path):
logger.error(f'Template path {template_path} is already exists.')
return 1
# template name is now the last component of the template_path
template_name = os.path.basename(template_path)
# template name cannot be the same as a restricted platform name
if template_name in restricted_platforms:
logger.error(f'Template path cannot be a restricted name. {template_name}')
return 1
if source_restricted_name and not source_restricted_path:
source_restricted_path = registration.get_registered(restricted_name=source_restricted_name)
# source_restricted_path
if source_restricted_path:
source_restricted_path = source_restricted_path.replace('\\', '/')
if not os.path.isabs(source_restricted_path):
engine_json = f'{registration.get_this_engine_path()}/engine.json'
if not registration.valid_o3de_engine_json(engine_json):
logger.error(f"Engine json {engine_json} is not valid.")
return 1
with open(engine_json) as s:
try:
engine_json_data = json.load(s)
except Exception as e:
logger.error(f"Failed to read engine json {engine_json}: {str(e)}")
return 1
try:
engine_restricted = engine_json_data['restricted']
except Exception as e:
logger.error(f"Engine json {engine_json} restricted not found.")
return 1
engine_restricted_folder = registration.get_registered(restricted_name=engine_restricted)
new_source_restricted_path = f'{engine_restricted_folder}/{source_restricted_path}'
logger.info(f'Source restricted path {source_restricted_path} not a full path. We must assume this engines'
f' restricted folder {new_source_restricted_path}')
if not os.path.isdir(source_restricted_path):
logger.error(f'Source restricted path {source_restricted_path} is not a folder.')
return 1
if template_restricted_name and not template_restricted_path:
template_restricted_path = registration.get_registered(restricted_name=template_restricted_name)
if not template_restricted_name:
template_restricted_name = template_name
# template_restricted_path
if template_restricted_path:
template_restricted_path = template_restricted_path.replace('\\', '/')
if not os.path.isabs(template_restricted_path):
default_templates_restricted_folder = registration.get_registered(restricted_name='templates')
new_template_restricted_path = f'{default_templates_restricted_folder}/{template_restricted_path}'
logger.info(f'Template restricted path {template_restricted_path} not a full path. We must assume the'
f' default templates restricted folder {new_template_restricted_path}')
template_restricted_path = new_template_restricted_path
if os.path.isdir(template_restricted_path):
# see if this is already a restricted path, if it is get the "restricted_name" from the restricted json
# so we can set "restricted" to it for this template
restricted_json = f'{template_restricted_path}/restricted.json'
if os.path.isfile(restricted_json):
if not registration.valid_o3de_restricted_json(restricted_json):
logger.error(f'{restricted_json} is not valid.')
return 1
with open(restricted_json, 'r') as s:
try:
restricted_json_data = json.load(s)
except Exception as e:
logger.error(f'Failed to load {restricted_json}: ' + str(e))
return 1
try:
template_restricted_name = restricted_json_data['restricted_name']
except Exception as e:
logger.error(f'Failed to read restricted_name from {restricted_json}')
return 1
else:
os.makedirs(template_restricted_path)
# source restricted relative
if source_restricted_platform_relative_path:
source_restricted_platform_relative_path = source_restricted_platform_relative_path.replace('\\', '/')
else:
source_restricted_platform_relative_path = ''
# template restricted relative
if template_restricted_platform_relative_path:
template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/')
else:
template_restricted_platform_relative_path = ''
logger.info(f'Processing Src: {source_path}')
# set all user defined replacements first
replacements = list()
while replace:
replace_this = replace.pop(0)
with_this = replace.pop(0)
replacements.append((replace_this, with_this))
os.makedirs(template_path, exist_ok=True)
# set the src templates name as the ${Name}
replacements.append((source_name.lower(), '${NameLower}'))
replacements.append((source_name.upper(), '${NameUpper}'))
replacements.append((source_name, '${Name}'))
def _transform_into_template(s_data: object) -> (bool, object):
"""
Internal function to transform any data into templated data
:param s_data: the input data, this could be file data or file name data
:return: bool: whether or not the returned data MAY need to be transformed to instantiate it
t_data: potentially transformed data 0 for success or non 0 failure code
"""
# copy the src data to the transformed data, then operate only on transformed data
t_data = s_data
# run all the replacements
for replacement in replacements:
t_data = t_data.replace(replacement[0], replacement[1])
if not keep_license_text:
t_data = re.sub(r"(//|'''|#)\s*{BEGIN_LICENSE}((.|\n)*){END_LICENSE}\n", "", t_data, flags=re.DOTALL)
# See if this file has the ModuleClassId
try:
pattern = r'.*AZ_RTTI\(\$\{Name\}Module, \"(?P<ModuleClassId>\{.*-.*-.*-.*-.*\})\", AZ::Module'
module_class_id = re.search(pattern, t_data).group('ModuleClassId')
replacements.append((module_class_id, '${ModuleClassId}'))
t_data = t_data.replace(module_class_id, '${ModuleClassId}')
except Exception as e:
pass
# See if this file has the SysCompClassId
try:
pattern = r'.*AZ_COMPONENT\(\$\{Name\}SystemComponent, \"(?P<SysCompClassId>\{.*-.*-.*-.*-.*\})\"'
sys_comp_class_id = re.search(pattern, t_data).group('SysCompClassId')
replacements.append((sys_comp_class_id, '${SysCompClassId}'))
t_data = t_data.replace(sys_comp_class_id, '${SysCompClassId}')
except Exception as e:
pass
# See if this file has the EditorSysCompClassId
try:
pattern = r'.*AZ_COMPONENT\(\$\{Name\}EditorSystemComponent, \"(?P<EditorSysCompClassId>\{.*-.*-.*-.*-.*\})\"'
editor_sys_comp_class_id = re.search(pattern, t_data).group('EditorSysCompClassId')
replacements.append((editor_sys_comp_class_id, '${EditorSysCompClassId}'))
t_data = t_data.replace(editor_sys_comp_class_id, '${EditorSysCompClassId}')
except Exception as e:
pass
# we want to send back the transformed data and whether or not this file
# may require transformation when instantiated. So if the input data is not the
# same as the output, then we transformed it which means there may be a transformation
# needed to instance it. Or if we chose not to remove the license text then potentially it
# may need to be transformed when instanced
if s_data != t_data or '{BEGIN_LICENSE}' in t_data:
return True, t_data
else:
return False, t_data
def _transform_into_template_restricted_filename(s_data: object,
platform: str) -> (bool, object):
"""
Internal function to transform a restricted platform file name into restricted template file name
:param s_data: the input data, this could be file data or file name data
:return: bool: whether or not the returned data MAY need to be transformed to instantiate it
t_data: potentially transformed data 0 for success or non 0 failure code
"""
# copy the src data to the transformed data, then operate only on transformed data
t_data = s_data
# run all the replacements
for replacement in replacements:
t_data = t_data.replace(replacement[0], replacement[1])
# the name of the Platform should follow the '/Platform/{platform}'
t_data = t_data.replace(f"Platform/{platform}", '')
# we want to send back the transformed data and whether or not this file
# may require transformation when instantiated. So if the input data is not the
# same as the output, then we transformed it which means there may be a transformation
# needed to instance it.
if s_data != t_data:
return True, t_data
else:
return False, t_data
def _transform_restricted_into_copyfiles_and_createdirs(source_path: str,
restricted_platform: str,
root_abs: str,
path_abs: str = None) -> None:
"""
Internal function recursively called to transform any paths files into copyfiles and create dirs relative to
the root. This will transform and copy the files, and save the copyfiles and createdirs data, no not save it
:param root_abs: This is the path everything will end up relative to
:path_abs: This is the path being processed, it is always either root_abs (where it starts) or a subdir
of root_abs
"""
if not path_abs:
path_abs = root_abs
entries = os.listdir(path_abs)
for entry in entries:
# create the absolute entry by joining the path_abs and the entry
entry_abs = f'{path_abs}/{entry}'
# create the relative entry by removing the root_abs
entry_rel = entry_abs.replace(root_abs + '/', '')
# report what file we are processing so we have a good idea if it breaks on what file it broke on
logger.info(f'Processing file: {entry_abs}')
# this is a restricted file, so we need to transform it, unpalify it
# restricted/<platform>/<source_path_rel>/some/folders/<file> ->
# <source_path_rel>/some/folders/Platform/<platform>/<file>
#
# C:/repo/Lumberyard/restricted/Jasper/TestDP/CMakeLists.txt ->
# C:/repo/Lumberyard/TestDP/Platform/Jasper/CMakeLists.txt
#
_, origin_entry_rel = _transform_into_template(entry_rel)
components = origin_entry_rel.split('/')
num_components = len(components)
# see how far along the source path the restricted folder matches
# then hopefully there is a Platform folder, warn if there isn't
before = []
after = []
relative = ''
if os.path.isdir(entry_abs):
for x in range(0, num_components):
relative += f'{components[x]}/'
if os.path.isdir(f'{source_path}/{relative}'):
before.append(components[x])
else:
after.append(components[x])
else:
for x in range(0, num_components - 1):
relative += f'{components[x]}/'
if os.path.isdir(f'{source_path}/{relative}'):
before.append(components[x])
else:
after.append(components[x])
after.append(components[num_components - 1])
before.append("Platform")
warn_if_not_platform = f'{source_path}/{"/".join(before)}'
before.append(restricted_platform)
before.extend(after)
origin_entry_rel = '/'.join(before)
if not os.path.isdir(warn_if_not_platform):
logger.warning(
f'{entry_abs} -> {origin_entry_rel}: Other Platforms not found in {warn_if_not_platform}')
destination_entry_rel = origin_entry_rel
destination_entry_abs = f'{template_path}/Template/{origin_entry_rel}'
# clean up any collapsed folders
origin_entry_rel = origin_entry_rel.replace('//', '/')
destination_entry_abs = destination_entry_abs.replace('//', '/')
destination_entry_rel = destination_entry_rel.replace('//', '/')
# clean up any relative leading slashes
while origin_entry_rel.startswith('/'):
origin_entry_rel = origin_entry_rel[1:]
while destination_entry_rel.startswith('/'):
destination_entry_rel = destination_entry_rel[1:]
# make sure the dst folder may or may not exist yet, make sure it does exist before we transform
# data into it
os.makedirs(os.path.dirname(destination_entry_abs), exist_ok=True)
# if the entry is a file, we need to transform it and add the entries into the copyfiles
# if the entry is a folder then we need to add the entry to the createDirs and recurse into that folder
templated = False
if os.path.isfile(entry_abs):
# if this file is a known binary file, there is no transformation needed and just copy it
# if not a known binary file open it and try to transform the data. if it is an unknown binary
# type it will throw and we catch copy
# if we had no known binary type it would still work, but much slower
name, ext = os.path.splitext(entry)
if ext in binary_file_ext:
shutil.copy(entry_abs, destination_entry_abs)
else:
try:
# open the file and attempt to transform it
with open(entry_abs, 'r') as s:
source_data = s.read()
templated, source_data = _transform_into_template(source_data)
# if the file type is a file that we expect to fins license header and we don't find any
# warn that the we didn't find the license info, this makes it easy to make sure we didn't
# miss any files we want to have license info in.
if keep_license_text and ext in expect_license_info_ext:
if 'Copyright (c)' not in source_data or '{BEGIN_LICENSE}' not in source_data:
logger.warning(f'Un-templated License header in {entry_abs}')
# if the transformed file we are about to write already exists for some reason, delete it
if os.path.isfile(destination_entry_abs):
os.unlink(destination_entry_abs)
with open(destination_entry_abs, 'w') as s:
s.write(source_data)
except Exception as e:
# we were not able to template the file, this is usually due to a unknown binary format
# so we catch copy it
shutil.copy(entry_abs, destination_entry_abs)
pass
copy_files.append({
"file": destination_entry_rel,
"origin": origin_entry_rel,
"isTemplated": templated,
"isOptional": False
})
else:
create_dirs.append({
"dir": destination_entry_rel,
"origin": origin_entry_rel
})
_transform_restricted_into_copyfiles_and_createdirs(source_path, restricted_platform, root_abs,
entry_abs)
def _transform_dir_into_copyfiles_and_createdirs(root_abs: str,
path_abs: str = None) -> None:
"""
Internal function recursively called to transform any paths files into copyfiles and create dirs relative to
the root. This will transform and copy the files, and save the copyfiles and createdirs data, no not save it
:param root_abs: This is the path everything will end up relative to
:path_abs: This is the path being processed, it is always either root_abs (where it starts) or a subdir
of root_abs
"""
if not path_abs:
path_abs = root_abs
entries = os.listdir(path_abs)
for entry in entries:
# create the absolute entry by joining the path_abs and the entry
entry_abs = f'{path_abs}/{entry}'
# create the relative entry by removing the root_abs
entry_rel = entry_abs.replace(root_abs + '/', '')
# report what file we are processing so we have a good idea if it breaks on what file it broke on
logger.info(f'Processing file: {entry_abs}')
# see if the entry is a platform file, if it is then we save its copyfile data in a platform specific list
# then at the end we can save the restricted ones separately
found_platform = ''
platform = False
if not keep_restricted_in_template and '/Platform' in entry_abs:
platform = True
try:
# the name of the Platform should follow the '/Platform/'
pattern = r'/Platform/(?P<Platform>[^/:*?\"<>|\r\n]+/?)'
found_platform = re.search(pattern, entry_abs).group('Platform')
found_platform = found_platform.replace('/', '')
except Exception as e:
pass
# if the entry is a file and the found_platform isn't yet in the restricted_platform_entries
# then set found_platform to empty as if we didn't find it as we should have already found a folder
# with the platform name containing this file
if os.path.isfile(entry_abs) and found_platform not in restricted_platform_entries:
found_platform = ''
# if we found a platform that is not yet in the restricted_platform_entries and it is a restricted
# platform, then add empty copyfiles and createDirs for this found restricted platform
if found_platform not in restricted_platform_entries and found_platform in restricted_platforms:
restricted_platform_entries.update({found_platform: {'copyFiles': [], 'createDirs': []}})
# Now if we found a platform and still have a found_platform which is a restricted platform
# then transform the entry relative name into a dst relative entry name and dst abs entry.
# if not then create a normal relative and abs dst entry name
_, origin_entry_rel = _transform_into_template(entry_rel)
if platform and found_platform in restricted_platforms:
# if we don't have a template restricted path and we found restricted files... warn and skip
# the file/dir
if not template_restricted_path:
logger.warning("Restricted platform files found!!! {entry_rel}, {found_platform}")
continue
_, destination_entry_rel = _transform_into_template_restricted_filename(entry_rel, found_platform)
destination_entry_abs = f'{template_restricted_path}/{found_platform}/{template_restricted_platform_relative_path}/{template_name}/Template/{destination_entry_rel}'
else:
destination_entry_rel = origin_entry_rel
destination_entry_abs = f'{template_path}/Template/{destination_entry_rel}'
# clean up any collapsed folders
origin_entry_rel = origin_entry_rel.replace('//', '/')
destination_entry_abs = destination_entry_abs.replace('//', '/')
destination_entry_rel = destination_entry_rel.replace('//', '/')
# clean up any relative leading slashes
while origin_entry_rel.startswith('/'):
origin_entry_rel = origin_entry_rel[1:]
while destination_entry_rel.startswith('/'):
destination_entry_rel = destination_entry_rel[1:]
# make sure the dst folder may or may not exist yet, make sure it does exist before we transform
# data into it
os.makedirs(os.path.dirname(destination_entry_abs), exist_ok=True)
# if the entry is a file, we need to transform it and add the entries into the copyfiles
# if the entry is a folder then we need to add the entry to the createDirs and recurse into that folder
templated = False
if os.path.isfile(entry_abs):
# if this file is a known binary file, there is no transformation needed and just copy it
# if not a known binary file open it and try to transform the data. if it is an unknown binary
# type it will throw and we catch copy
# if we had no known binary type it would still work, but much slower
name, ext = os.path.splitext(entry)
if ext in binary_file_ext:
shutil.copy(entry_abs, destination_entry_abs)
else:
try:
# open the file and attempt to transform it
with open(entry_abs, 'r') as s:
source_data = s.read()
templated, source_data = _transform_into_template(source_data)
# if the file type is a file that we expect to fins license header and we don't find any
# warn that the we didn't find the license info, this makes it easy to make sure we didn't
# miss any files we want to have license info in.
if keep_license_text and ext in expect_license_info_ext:
if 'Copyright (c)' not in source_data or '{BEGIN_LICENSE}' not in source_data:
logger.warning(f'Un-templated License header in {entry_abs}')
# if the transformed file we are about to write already exists for some reason, delete it
if os.path.isfile(destination_entry_abs):
os.unlink(destination_entry_abs)
with open(destination_entry_abs, 'w') as s:
s.write(source_data)
except Exception as e:
# we were not able to template the file, this is usually due to a unknown binary format
# so we catch copy it
shutil.copy(entry_abs, destination_entry_abs)
pass
# if the file was for a restricted platform add the entry to the restricted platform, otherwise add it
# to the non restricted
if platform and found_platform in restricted_platforms:
restricted_platform_entries[found_platform]['copyFiles'].append({
"file": destination_entry_rel,
"origin": origin_entry_rel,
"isTemplated": templated,
"isOptional": False
})
else:
copy_files.append({
"file": destination_entry_rel,
"origin": origin_entry_rel,
"isTemplated": templated,
"isOptional": False
})
else:
# if the folder was for a restricted platform add the entry to the restricted platform, otherwise add it
# to the non restricted
if platform and found_platform in restricted_platforms:
restricted_platform_entries[found_platform]['createDirs'].append({
"dir": destination_entry_rel,
"origin": origin_entry_rel
})
else:
create_dirs.append({
"dir": destination_entry_rel,
"origin": origin_entry_rel
})
# recurse using the same root and this folder
_transform_dir_into_copyfiles_and_createdirs(root_abs, entry_abs)
# when we run the transformation to create copyfiles, createdirs, any we find will go in here
copy_files = []
create_dirs = []
# when we run the transformation any restricted platforms entries we find will go in here
restricted_platform_entries = {}
# Every project will have a unrestricted folder which is src_path_abs which MAY have restricted files in it, and
# each project MAY have a restricted folder which will only have restricted files in them. The process is the
# same for all of them and the result will be a separation of all restricted files from unrestricted files. We do
# this by running the transformation first over the src path abs and then on each restricted folder for this project
# we find. This will effectively combine all sources then separates all the restricted.
# run the transformation on the src, which may or may not have restricted files
_transform_dir_into_copyfiles_and_createdirs(source_path)
# every src may have a matching restricted folder per restricted platform
# run the transformation on each src restricted folder
if source_restricted_path:
for restricted_platform in os.listdir(source_restricted_path):
restricted_platform_src_path_abs = f'{source_restricted_path}/{restricted_platform}/{source_restricted_platform_relative_path}/{source_name}'.replace(
'//', '/')
if os.path.isdir(restricted_platform_src_path_abs):
_transform_restricted_into_copyfiles_and_createdirs(source_path, restricted_platform,
restricted_platform_src_path_abs)
# sort
copy_files.sort(key=lambda x: x['file'])
create_dirs.sort(key=lambda x: x['dir'])
# now we should have all our copyFiles and createDirs entries, so write out the Template json for the unrestricted
# platforms all together
json_data = {}
json_data.update({'template_name': template_name})
json_data.update({'origin': f'The primary repo for {template_name} goes here: i.e. http://www.mydomain.com'})
json_data.update(
{'license': f'What license {template_name} uses goes here: i.e. https://opensource.org/licenses/MIT'})
json_data.update({'display_name': template_name})
json_data.update({'summary': f"A short description of {template_name}."})
json_data.update({'canonical_tags': []})
json_data.update({'user_tags': [f"{template_name}"]})
json_data.update({'icon_path': "preview.png"})
if template_restricted_path:
json_data.update({'restricted': template_restricted_name})
if template_restricted_platform_relative_path != '':
json_data.update({'template_restricted_platform_relative_path': template_restricted_platform_relative_path})
json_data.update({'copyFiles': copy_files})
json_data.update({'createDirectories': create_dirs})
json_name = f'{template_path}/{template_file_name}'
# if the json file we are about to write already exists for some reason, delete it
if os.path.isfile(json_name):
os.unlink(json_name)
with open(json_name, 'w') as s:
s.write(json.dumps(json_data, indent=4))
# copy the default preview.png
this_script_parent = os.path.dirname(os.path.realpath(__file__))
preview_png_src = f'{this_script_parent}/preview.png'
preview_png_dst = f'{template_path}/Template/preview.png'
if not os.path.isfile(preview_png_dst):
shutil.copy(preview_png_src, preview_png_dst)
# if no restricted template path was given and restricted platform files were found
if not template_restricted_path and len(restricted_platform_entries):
logger.info(f'Restricted platform files found!!! and no template restricted path was found...')
if template_restricted_path:
# now write out each restricted platform template json separately
for restricted_platform in restricted_platform_entries:
restricted_template_path = f'{template_restricted_path}/{restricted_platform}/{template_restricted_platform_relative_path}/{template_name}'.replace(
'//', '/')
# sort
restricted_platform_entries[restricted_platform]['copyFiles'].sort(key=lambda x: x['file'])
restricted_platform_entries[restricted_platform]['createDirs'].sort(key=lambda x: x['dir'])
json_data = {}
json_data.update({'template_name': template_name})
json_data.update(
{'origin': f'The primary repo for {template_name} goes here: i.e. http://www.mydomain.com'})
json_data.update(
{'license': f'What license {template_name} uses goes here: i.e. https://opensource.org/licenses/MIT'})
json_data.update({'display_name': template_name})
json_data.update({'summary': f"A short description of {template_name}."})
json_data.update({'canonical_tags': []})
json_data.update({'user_tags': [f'{template_name}']})
json_data.update({'icon_path': "preview.png"})
json_data.update({'copyFiles': restricted_platform_entries[restricted_platform]['copyFiles']})
json_data.update({'createDirectories': restricted_platform_entries[restricted_platform]['createDirs']})
json_name = f'{restricted_template_path}/{template_file_name}'
os.makedirs(os.path.dirname(json_name), exist_ok=True)
# if the json file we are about to write already exists for some reason, delete it
if os.path.isfile(json_name):
os.unlink(json_name)
with open(json_name, 'w') as s:
s.write(json.dumps(json_data, indent=4))
preview_png_dst = f'{restricted_template_path}/Template/preview.png'
if not os.path.isfile(preview_png_dst):
shutil.copy(preview_png_src, preview_png_dst)
return 0
def create_from_template(destination_path: str,
template_path: str = None,
template_name: str = None,
destination_restricted_path: str = None,
destination_restricted_name: str = None,
template_restricted_path: str = None,
template_restricted_name: str = None,
destination_restricted_platform_relative_path: str = None,
template_restricted_platform_relative_path: str = None,
keep_restricted_in_instance: bool = False,
keep_license_text: bool = False,
replace: list = None) -> int:
"""
Generic template instantiation for non o3de object templates. This function makes NO assumptions!
Assumptions are made only for specializations like create_project or create_gem etc... So this function
will NOT try to divine intent.
:param destination_path: the folder you want to instantiate the template into
:param template_path: the path to the template you want to instance
:param template_name: the name of the template you want to instance, resolves template_path
:param destination_restricted_path: path to the projects restricted folder
:param destination_restricted_name: name of the projects restricted folder, resolves destination_restricted_path
:param template_restricted_path: path of the templates restricted folder
:param template_restricted_name: name of the templates restricted folder, resolves template_restricted_path
:param destination_restricted_platform_relative_path: any path after the platform in the destination restricted
:param template_restricted_platform_relative_path: any path after the platform in the template restricted
:param keep_restricted_in_instance: whether or not you want to keep the templates restricted files in your instance
or separate them out into the restricted folder
:param keep_license_text: whether or not you want to keep the templates license text in your instance.
template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE},
this controls if you want to keep the license text from the template in the new instance. It is false by default
because most customers will not want license text in their instances, but we may want to keep them.
:param replace: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs
Ex. ${Name},TestGem,${Player},TestGemPlayer
This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer'
:return: 0 for success or non 0 failure code
"""
if template_name and template_path:
logger.error(f'Template Name and Template Path provided, these are mutually exclusive.')
return 1
if destination_restricted_name and destination_restricted_path:
logger.error(f'Destination Restricted Name and Destination Restricted Path provided, these are mutually'
f' exclusive.')
return 1
if template_restricted_name and template_restricted_path:
logger.error(f'Template Restricted Name and Template Restricted Path provided, these are mutually exclusive.')
return 1
# need either the template name or path
if not template_path and not template_name:
logger.error(f'Template Name or Template Path must be specified.')
return 1
if template_name:
template_path = registration.get_registered(template_name=template_name)
if not os.path.isdir(template_path):
logger.error(f'Could not find the template {template_name}=>{template_path}')
return 1
# template folder name is now the last component of the template_path
template_folder_name = os.path.basename(template_path)
# the template.json should be in the template_path, make sure it's there a nd valid
template_json = f'{template_path}/template.json'
if not registration.valid_o3de_template_json(template_json):
logger.error(f'Template json {template_path} is invalid.')
return 1
# read in the template.json
with open(template_json) as s:
try:
template_json_data = json.load(s)
except Exception as e:
logger.error(f'Could read template json {template_json}: {str(e)}.')
return 1
# read template name from the json
try:
template_name = template_json_data['template_name']
except Exception as e:
logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.')
return 1
# if the user has not specified either a restricted name or restricted path
# see if the template itself specifies a restricted name
if not template_restricted_name and not template_restricted_path:
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".')
else:
template_restricted_name = template_json_restricted_name
# if no restricted name or path we continue on as if there is no template restricted files.
if template_restricted_name or template_restricted_path:
# If the user specified a --template-restricted-name we need to check that against the templates
# 'restricted' if it has one and see if they match. If they match then we don't have a problem.
# If they don't then we error out. If supplied but not present in the template we warn and use it.
# If not supplied we set what's in the template. If not supplied and not in the template we continue
# on as if there is no template restricted files.
if template_restricted_name:
# The user specified a --template-restricted-name
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".'
f' Using supplied {template_restricted_name}')
else:
if template_json_restricted_name != template_restricted_name:
logger.error(
f'The supplied --template-restricted-name {template_restricted_name} does not match the'
f' templates "restricted". Either the the --template-restricted-name is incorrect or the'
f' templates "restricted" is wrong. Note that since this template specifies "restricted" as'
f' {template_json_restricted_name}, --template-restricted-name need not be supplied.')
template_restricted_path = registration.get_registered(restricted_name=template_restricted_name)
else:
# The user has supplied the --template-restricted-path, see if that matches the template specifies.
# If it does then we do not have a problem. If it doesn't match then error out. If not specified
# in the template then warn and use the --template-restricted-path
template_restricted_path = template_restricted_path.replace('\\', '/')
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".'
f' Using supplied {template_restricted_path}')
else:
template_json_restricted_path = registration.get_registered(
restricted_name=template_json_restricted_name)
if template_json_restricted_path != template_restricted_path:
logger.error(
f'The supplied --template-restricted-path {template_restricted_path} does not match the'
f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.'
f' Either the the supplied --template-restricted-path is incorrect or the templates'
f' "restricted" is wrong. Note that since this template specifies "restricted" as'
f' {template_json_restricted_name} --template-restricted-path need not be supplied'
f' and {template_json_restricted_path} will be used.')
return 1
# check and make sure the restricted exists
if not os.path.isdir(template_restricted_path):
logger.error(f'Template restricted path {template_restricted_path} does not exist.')
return 1
# If the user specified a --template-restricted-platform-relative-path we need to check that against
# the templates 'restricted_platform_relative_path' and see if they match. If they match we don't have
# a problem. If they don't match then we error out. If supplied but not present in the template we warn
# and use --template-restricted-platform-relative-path. If not supplied we set what's in the template.
# If not supplied and not in the template set empty string.
if template_restricted_platform_relative_path:
# The user specified a --template-restricted-platform-relative-path
template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace(
'\\', '/')
try:
template_json_restricted_platform_relative_path = template_json_data[
'restricted_platform_relative_path']
except Exception as e:
# the template json doesn't have a 'restricted_platform_relative_path' element warn and use it
logger.info(f'The template does not specify a "restricted_platform_relative_path".'
f' Using {template_restricted_platform_relative_path}')
else:
# the template has a 'restricted_platform_relative_path', if it matches we are fine, if not
# something is wrong with either the --template-restricted-platform-relative or the template is.
if template_restricted_platform_relative_path != template_json_restricted_platform_relative_path:
logger.error(f'The supplied --template-restricted-platform-relative-path does not match the'
f' templates "restricted_platform_relative_path". Either'
f' --template-restricted-platform-relative-path is incorrect or the templates'
f' "restricted_platform_relative_path" is wrong. Note that since this template'
f' specifies "restricted_platform_relative_path" it need not be supplied and'
f' {template_json_restricted_platform_relative_path} will be used.')
return 1
else:
# The user has not supplied --template-restricted-platform-relative-path, try to read it from
# the template json.
try:
template_restricted_platform_relative_path = template_json_data[
'restricted_platform_relative_path']
except Exception as e:
# The template json doesn't have a 'restricted_platform_relative_path' element, set empty string.
template_restricted_platform_relative_path = ''
if not template_restricted_platform_relative_path:
template_restricted_platform_relative_path = ''
# if no destination_path, error
if not destination_path:
logger.error('Destination path cannot be empty.')
return 1
destination_path = destination_path.replace('\\', '/')
if os.path.isdir(destination_path):
logger.error(f'Destination path {destination_path} already exists.')
return 1
else:
os.makedirs(destination_path)
# destination name is now the last component of the destination_path
destination_name = os.path.basename(destination_path)
# destination name cannot be the same as a restricted platform name
if destination_name in restricted_platforms:
logger.error(f'Destination path cannot be a restricted name. {destination_name}')
return 1
# destination restricted name
if destination_restricted_name:
destination_restricted_path = registration.get_registered(restricted_name=destination_restricted_name)
# destination restricted path
elif destination_restricted_path:
destination_restricted_path = destination_restricted_path.replace('\\', '/')
if os.path.isabs(destination_restricted_path):
restricted_default_path = registration.get_registered(default='restricted')
new_destination_restricted_path = f'{restricted_default_path}/{destination_restricted_path}'
logger.info(f'{destination_restricted_path} is not a full path, making it relative'
f' to default restricted path = {new_destination_restricted_path}')
destination_restricted_path = new_destination_restricted_path
elif template_restricted_path:
restricted_default_path = registration.get_registered(default='restricted')
logger.info(f'--destination-restricted-path is not specified, using default restricted path / destination name'
f' = {restricted_default_path}')
destination_restricted_path = restricted_default_path
# destination restricted relative
if destination_restricted_platform_relative_path:
destination_restricted_platform_relative_path = destination_restricted_platform_relative_path.replace('\\', '/')
else:
destination_restricted_platform_relative_path = ''
# any user supplied replacements
replacements = list()
while replace:
replace_this = replace.pop(0)
with_this = replace.pop(0)
replacements.append((replace_this, with_this))
# dst name is Name
replacements.append(("${Name}", destination_name))
replacements.append(("${NameUpper}", destination_name.upper()))
replacements.append(("${NameLower}", destination_name.lower()))
if _instantiate_template(template_json_data,
destination_name,
template_name,
destination_path,
template_path,
destination_restricted_path,
template_restricted_path,
destination_restricted_platform_relative_path,
template_restricted_platform_relative_path,
replacements,
keep_restricted_in_instance,
keep_license_text):
logger.error(f'Instantiation of the template has failed.')
return 1
# We created the destination, now do anything extra that a destination requires
# If we are not keeping the restricted in the destination read the destination restricted this might be a new
# restricted folder, so make sure the restricted has this destinations name
# Note there is no linking of non o3de objects to o3de restricted. So this will make no attempt to figure out
# if this destination was actually an o3de object and try to alter the <type>.json
if not keep_restricted_in_instance:
if destination_restricted_path:
os.makedirs(destination_restricted_path, exist_ok=True)
# read the restricted_name from the destination restricted.json
restricted_json = f"{destination_restricted_path}/restricted.json".replace('//', '/')
if not os.path.isfile(restricted_json):
with open(restricted_json, 'w') as s:
restricted_json_data = {}
restricted_json_data.update({'restricted_name': destination_name})
s.write(json.dumps(restricted_json_data, indent=4))
logger.warning(f'Instantiation successful. NOTE: This is a generic instantiation of the template. If this'
f' was a template of an o3de object like a project, gem, template, etc. then you should have used'
f' specialization that knows how to link that object type via its project.json or gem.json, etc.'
f' Create from template is meant only to instance a template of a non o3de object.')
return 0
def create_project(project_path: str,
template_path: str = None,
template_name: str = None,
project_restricted_path: str = None,
project_restricted_name: str = None,
template_restricted_path: str = None,
template_restricted_name: str = None,
project_restricted_platform_relative_path: str = None,
template_restricted_platform_relative_path: str = None,
keep_restricted_in_project: bool = False,
keep_license_text: bool = False,
replace: list = None,
system_component_class_id: str = None,
editor_system_component_class_id: str = None,
module_id: str = None) -> int:
"""
Template instantiation specialization that makes all default assumptions for a Project template instantiation,
reducing the effort needed in instancing a project
:param project_path: the project path, can be absolute or relative to default projects path
:param template_path: the path to the template you want to instance, can be absolute or relative to default templates path
:param template_name: the name the registered template you want to instance, defaults to DefaultProject, resolves template_path
:param project_restricted_path: path to the projects restricted folder, can be absolute or relative to the restricted='projects'
:param project_restricted_name: name of the registered projects restricted path, resolves project_restricted_path
:param template_restricted_path: templates restricted path can be absolute or relative to restricted='templates'
:param template_restricted_name: name of the registered templates restricted path, resolves template_restricted_path
:param project_restricted_platform_relative_path: any path after the platform to append to the project_restricted_path
:param template_restricted_platform_relative_path: any path after the platform to append to the template_restricted_path
:param keep_restricted_in_project: whether or not you want to keep the templates restricted files in your project or
separate them out into the restricted folder
:param keep_license_text: whether or not you want to keep the templates license text in your instance.
template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE},
this controls if you want to keep the license text from the template in the new instance. It is false by default
because most customers will not want license text in their instances, but we may want to keep them.
:param replace: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs
Ex. ${Name},TestGem,${Player},TestGemPlayer
This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer'
:param system_component_class_id: optionally specify a uuid for the system component class, default is random uuid
:param editor_system_component_class_id: optionally specify a uuid for the editor system component class, default is
random uuid
:param module_id: optionally specify a uuid for the module class, default is random uuid
:return: 0 for success or non 0 failure code
"""
if template_name and template_path:
logger.error(f'Template Name and Template Path provided, these are mutually exclusive.')
return 1
if project_restricted_name and project_restricted_path:
logger.error(f'Project Restricted Name and Project Restricted Path provided, these are mutually exclusive.')
return 1
if template_restricted_name and template_restricted_path:
logger.error(f'Template Restricted Name and Template Restricted Path provided, these are mutually exclusive.')
return 1
if not template_path and not template_name:
template_name = 'DefaultProject'
if template_name and not template_path:
template_path = registration.get_registered(template_name=template_name)
if not os.path.isdir(template_path):
logger.error(f'Could not find the template {template_name}=>{template_path}')
return 1
# template folder name is now the last component of the template_path
template_folder_name = os.path.basename(template_path)
# the template.json should be in the template_path, make sure it's there and valid
template_json = f'{template_path}/template.json'
if not registration.valid_o3de_template_json(template_json):
logger.error(f'Template json {template_path} is not valid.')
return 1
# read in the template.json
with open(template_json) as s:
try:
template_json_data = json.load(s)
except Exception as e:
logger.error(f'Could read template json {template_json}: {str(e)}.')
return 1
# read template name from the json
try:
template_name = template_json_data['template_name']
except Exception as e:
logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.')
return 1
# if the user has not specified either a restricted name or restricted path
# see if the template itself specifies a restricted name
if not template_restricted_name and not template_restricted_path:
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".')
else:
template_restricted_name = template_json_restricted_name
# if no restricted name or path we continue on as if there is no template restricted files.
if template_restricted_name or template_restricted_path:
# If the user specified a --template-restricted-name we need to check that against the templates
# 'restricted' if it has one and see if they match. If they match then we don't have a problem.
# If they don't then we error out. If supplied but not present in the template we warn and use it.
# If not supplied we set what's in the template. If not supplied and not in the template we continue
# on as if there is no template restricted files.
if template_restricted_name and not template_restricted_path:
# The user specified a --template-restricted-name
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".'
f' Using supplied {template_restricted_name}')
else:
if template_json_restricted_name != template_restricted_name:
logger.error(
f'The supplied --template-restricted-name {template_restricted_name} does not match the'
f' templates "restricted". Either the the --template-restricted-name is incorrect or the'
f' templates "restricted" is wrong. Note that since this template specifies "restricted" as'
f' {template_json_restricted_name}, --template-restricted-name need not be supplied.')
template_restricted_path = registration.get_registered(restricted_name=template_restricted_name)
else:
# The user has supplied the --template-restricted-path, see if that matches the template specifies.
# If it does then we do not have a problem. If it doesn't match then error out. If not specified
# in the template then warn and use the --template-restricted-path
template_restricted_path = template_restricted_path.replace('\\', '/')
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".'
f' Using supplied {template_restricted_path}')
else:
template_json_restricted_path = registration.get_registered(
restricted_name=template_json_restricted_name)
if template_json_restricted_path != template_restricted_path:
logger.error(
f'The supplied --template-restricted-path {template_restricted_path} does not match the'
f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.'
f' Either the the supplied --template-restricted-path is incorrect or the templates'
f' "restricted" is wrong. Note that since this template specifies "restricted" as'
f' {template_json_restricted_name} --template-restricted-path need not be supplied'
f' and {template_json_restricted_path} will be used.')
return 1
# check and make sure the restricted exists
if not os.path.isdir(template_restricted_path):
logger.error(f'Template restricted path {template_restricted_path} does not exist.')
return 1
# If the user specified a --template-restricted-platform-relative-path we need to check that against
# the templates 'restricted_platform_relative_path' and see if they match. If they match we don't have
# a problem. If they don't match then we error out. If supplied but not present in the template we warn
# and use --template-restricted-platform-relative-path. If not supplied we set what's in the template.
# If not supplied and not in the template set empty string.
if template_restricted_platform_relative_path:
# The user specified a --template-restricted-platform-relative-path
template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/')
try:
template_json_restricted_platform_relative_path = template_json_data[
'restricted_platform_relative_path']
except Exception as e:
# the template json doesn't have a 'restricted_platform_relative_path' element warn and use it
logger.info(f'The template does not specify a "restricted_platform_relative_path".'
f' Using {template_restricted_platform_relative_path}')
else:
# the template has a 'restricted_platform_relative_path', if it matches we are fine, if not
# something is wrong with either the --template-restricted-platform-relative or the template is.
if template_restricted_platform_relative_path != template_json_restricted_platform_relative_path:
logger.error(f'The supplied --template-restricted-platform-relative-path does not match the'
f' templates "restricted_platform_relative_path". Either'
f' --template-restricted-platform-relative-path is incorrect or the templates'
f' "restricted_platform_relative_path" is wrong. Note that since this template'
f' specifies "restricted_platform_relative_path" it need not be supplied and'
f' {template_json_restricted_platform_relative_path} will be used.')
return 1
else:
# The user has not supplied --template-restricted-platform-relative-path, try to read it from
# the template json.
try:
template_restricted_platform_relative_path = template_json_data[
'restricted_platform_relative_path']
except Exception as e:
# The template json doesn't have a 'restricted_platform_relative_path' element, set empty string.
template_restricted_platform_relative_path = ''
if not template_restricted_platform_relative_path:
template_restricted_platform_relative_path = ''
# if no project path, error
if not project_path:
logger.error('Project path cannot be empty.')
return 1
project_path = project_path.replace('\\', '/')
if not os.path.isabs(project_path):
default_projects_folder = registration.get_registered(default_folder='projects')
new_project_path = f'{default_projects_folder}/{project_path}'
logger.info(f'Project Path {project_path} is not a full path, we must assume its relative'
f' to default projects path = {new_project_path}')
project_path = new_project_path
if os.path.isdir(project_path):
logger.error(f'Project path {project_path} already exists.')
return 1
else:
os.makedirs(project_path)
# project name is now the last component of the project_path
project_name = os.path.basename(project_path)
# project name cannot be the same as a restricted platform name
if project_name in restricted_platforms:
logger.error(f'Project path cannot be a restricted name. {project_name}')
return 1
# project restricted name
if project_restricted_name and not project_restricted_path:
project_restricted_path = registration.get_registered(restricted_name=project_restricted_name)
# project restricted path
elif project_restricted_path:
project_restricted_path = project_restricted_path.replace('\\', '/')
if not os.path.isabs(project_restricted_path):
default_projects_restricted_folder = registration.get_registered(restricted_name='projects')
new_project_restricted_path = f'{default_projects_restricted_folder}/{project_restricted_path}'
logger.info(f'Project restricted path {project_restricted_path} is not a full path, we must assume its'
f' relative to default projects restricted path = {new_project_restricted_path}')
project_restricted_path = new_project_restricted_path
elif template_restricted_path:
project_restricted_default_path = registration.get_registered(restricted_name='projects')
logger.info(f'--project-restricted-path is not specified, using default project restricted path / project name'
f' = {project_restricted_default_path}')
project_restricted_path = project_restricted_default_path
# project restricted relative path
if project_restricted_platform_relative_path:
project_restricted_platform_relative_path = project_restricted_platform_relative_path.replace('\\', '/')
else:
project_restricted_platform_relative_path = ''
# any user supplied replacements
replacements = list()
while replace:
replace_this = replace.pop(0)
with_this = replace.pop(0)
replacements.append((replace_this, with_this))
# project name
replacements.append(("${Name}", project_name))
replacements.append(("${NameUpper}", project_name.upper()))
replacements.append(("${NameLower}", project_name.lower()))
# module id is a uuid with { and -
if module_id:
replacements.append(("${ModuleClassId}", module_id))
else:
replacements.append(("${ModuleClassId}", '{' + str(uuid.uuid4()) + '}'))
# system component class id is a uuid with { and -
if system_component_class_id:
if '{' not in system_component_class_id or '-' not in system_component_class_id:
logger.error(
f'System component class id {system_component_class_id} is malformed. Should look like Ex.' +
'{b60c92eb-3139-454b-a917-a9d3c5819594}')
return 1
replacements.append(("${SysCompClassId}", system_component_class_id))
else:
replacements.append(("${SysCompClassId}", '{' + str(uuid.uuid4()) + '}'))
# editor system component class id is a uuid with { and -
if editor_system_component_class_id:
if '{' not in editor_system_component_class_id or '-' not in editor_system_component_class_id:
logger.error(
f'Editor System component class id {editor_system_component_class_id} is malformed. Should look like'
f' Ex.' + '{b60c92eb-3139-454b-a917-a9d3c5819594}')
return 1
replacements.append(("${EditorSysCompClassId}", editor_system_component_class_id))
else:
replacements.append(("${EditorSysCompClassId}", '{' + str(uuid.uuid4()) + '}'))
if _instantiate_template(template_json_data,
project_name,
template_name,
project_path,
template_path,
project_restricted_path,
template_restricted_path,
project_restricted_platform_relative_path,
template_restricted_platform_relative_path,
replacements,
keep_restricted_in_project,
keep_license_text):
logger.error(f'Instantiation of the template has failed.')
return 1
# We created the project, now do anything extra that a project requires
# If we are not keeping the restricted in the project read the name of the restricted folder from the
# restricted json and set that as this projects restricted
if not keep_restricted_in_project:
if project_restricted_path:
os.makedirs(project_restricted_path, exist_ok=True)
# read the restricted_name from the projects restricted.json
restricted_json = f"{project_restricted_path}/restricted.json".replace('//', '/')
if os.path.isfile(restricted_json):
if not registration.valid_o3de_restricted_json(restricted_json):
logger.error(f'Restricted json {restricted_json} is not valid.')
return 1
else:
with open(restricted_json, 'w') as s:
restricted_json_data = {}
restricted_json_data.update({'restricted_name': project_name})
s.write(json.dumps(restricted_json_data, indent=4))
with open(restricted_json, 'r') as s:
try:
restricted_json_data = json.load(s)
except Exception as e:
logger.error(f'Failed to load restricted json {restricted_json}.')
return 1
try:
restricted_name = restricted_json_data["restricted_name"]
except Exception as e:
logger.error(f'Failed to read "restricted_name" from restricted json {restricted_json}.')
return 1
# set the "restricted": "restricted_name" element of the project.json
project_json = f"{project_path}/project.json".replace('//', '/')
if not registration.valid_o3de_project_json(project_json):
logger.error(f'Project json {project_json} is not valid.')
return 1
with open(project_json, 'r') as s:
try:
project_json_data = json.load(s)
except Exception as e:
logger.error(f'Failed to load project json {project_json}.')
return 1
project_json_data.update({"restricted": restricted_name})
os.unlink(project_json)
with open(project_json, 'w') as s:
try:
s.write(json.dumps(project_json_data, indent=4))
except Exception as e:
logger.error(f'Failed to write project json {project_json}.')
return 1
for restricted_platform in restricted_platforms:
restricted_project = f'{project_restricted_path}/{restricted_platform}/{project_name}'
os.makedirs(restricted_project, exist_ok=True)
cmakelists_file_name = f'{restricted_project}/CMakeLists.txt'
if not os.path.isfile(cmakelists_file_name):
with open(cmakelists_file_name, 'w') as d:
if keep_license_text:
d.write('# {BEGIN_LICENSE}\n')
d.write('# All or portions of this file Copyright (c) Amazon.com, Inc. or its'
' affiliates or\n')
d.write('# its licensors.\n')
d.write('#\n')
d.write('# For complete copyright and license terms please see the LICENSE at the'
' root of this\n')
d.write('# distribution (the "License"). All use of this software is governed by'
' the License,\n')
d.write('# or, if provided, by the license below or the license accompanying this'
' file. Do not\n')
d.write('# remove or modify any license notices. This file is distributed on an'
' "AS IS" BASIS,\n')
d.write('# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n')
d.write('# {END_LICENSE}\n')
# copy the o3de_manifest.cmake into the project root
engine_path = registration.get_this_engine_path()
o3de_manifest_cmake = f'{engine_path}/cmake/o3de_manifest.cmake'
shutil.copy(o3de_manifest_cmake, project_path)
return 0
def create_gem(gem_path: str,
template_path: str = None,
template_name: str = None,
gem_restricted_path: str = None,
gem_restricted_name: str = None,
template_restricted_path: str = None,
template_restricted_name: str = None,
gem_restricted_platform_relative_path: str = None,
template_restricted_platform_relative_path: str = None,
keep_restricted_in_gem: bool = False,
keep_license_text: bool = False,
replace: list = None,
system_component_class_id: str = None,
editor_system_component_class_id: str = None,
module_id: str = None) -> int:
"""
Template instantiation specialization that makes all default assumptions for a Gem template instantiation,
reducing the effort needed in instancing a gem
:param gem_path: the gem path, can be absolute or relative to default gems path
:param template_path: the template path you want to instance, can be absolute or relative to default templates path
:param template_name: the name of the registered template you want to instance, defaults to DefaultGem, resolves template_path
:param gem_restricted_path: path to the gems restricted folder, can be absolute or relative to the restricted='gems'
:param gem_restricted_name: str = name of the registered gems restricted path, resolves gem_restricted_path
:param template_restricted_path: the templates restricted path, can be absolute or relative to the restricted='templates'
:param template_restricted_name: name of the registered templates restricted path, resolves template_restricted_path
:param gem_restricted_platform_relative_path: any path after the platform to append to the gem_restricted_path
:param template_restricted_platform_relative_path: any path after the platform to append to the template_restricted_path
:param keep_restricted_in_gem: whether or not you want to keep the templates restricted files in your instance or
separate them out into the restricted folder
:param keep_license_text: whether or not you want to keep the templates license text in your instance. template can
have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE}, this controls if you want to keep
the license text from the template in the new instance. It is false by default because most customers will not
want license text in their instances, but we may want to keep them.
:param replace: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs
Ex. ${Name},TestGem,${Player},TestGemPlayer
This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer'
:param system_component_class_id: optionally specify a uuid for the system component class, default is random uuid
:param editor_system_component_class_id: optionally specify a uuid for the editor system component class, default is
random uuid
:param module_id: optionally specify a uuid for the module class, default is random uuid
:return: 0 for success or non 0 failure code
"""
if template_name and template_path:
logger.error(f'Template Name and Template Path provided, these are mutually exclusive.')
return 1
if gem_restricted_name and gem_restricted_path:
logger.error(f'Gem Restricted Name and Gem Restricted Path provided, these are mutually exclusive.')
return 1
if template_restricted_name and template_restricted_path:
logger.error(f'Template Restricted Name and Template Restricted Path provided, these are mutually exclusive.')
return 1
if not template_name and not template_path:
template_name = 'DefaultGem'
if template_name and not template_path:
template_path = registration.get_registered(template_name=template_name)
if not os.path.isdir(template_path):
logger.error(f'Could not find the template {template_name}=>{template_path}')
return 1
# template name is now the last component of the template_path
template_folder_name = os.path.basename(template_path)
# the template.json should be in the template_path, make sure it's there and valid
template_json = f'{template_path}/template.json'
if not registration.valid_o3de_template_json(template_json):
logger.error(f'Template json {template_path} is not valid.')
return 1
# read in the template.json
with open(template_json) as s:
try:
template_json_data = json.load(s)
except Exception as e:
logger.error(f'Could read template json {template_json}: {str(e)}.')
return 1
# read template name from the json
try:
template_name = template_json_data['template_name']
except Exception as e:
logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.')
return 1
# if the user has not specified either a restricted name or restricted path
# see if the template itself specifies a restricted name
if not template_restricted_name and not template_restricted_path:
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".')
else:
template_restricted_name = template_json_restricted_name
# if no restricted name or path we continue on as if there is no template restricted files.
if template_restricted_name or template_restricted_path:
# if the user specified a --template-restricted-name we need to check that against the templates 'restricted'
# if it has one and see if they match. If they match then we don't have a problem. If they don't then we error
# out. If supplied but not present in the template we warn and use it. If not supplied we set what's in the
# template. If not supplied and not in the template we continue on as if there is no template restricted files.
if template_restricted_name and not template_restricted_path:
# The user specified a --template-restricted-name
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".'
f' Using supplied {template_restricted_name}')
else:
if template_json_restricted_name != template_restricted_name:
logger.error(
f'The supplied --template-restricted-name {template_restricted_name} does not match the'
f' templates "restricted". Either the the --template-restricted-name is incorrect or the'
f' templates "restricted" is wrong. Note that since this template specifies "restricted" as'
f' {template_json_restricted_name}, --template-restricted-name need not be supplied.')
template_restricted_path = registration.get_registered(restricted_name=template_restricted_name)
else:
# The user has supplied the --template-restricted-path, see if that matches the template specifies.
# If it does then we do not have a problem. If it doesn't match then error out. If not specified
# in the template then warn and use the --template-restricted-path
template_restricted_path = template_restricted_path.replace('\\', '/')
try:
template_json_restricted_name = template_json_data['restricted']
except Exception as e:
# the template json doesn't have a 'restricted' element warn and use it
logger.info(f'The template does not specify a "restricted".'
f' Using supplied {template_restricted_path}')
else:
template_json_restricted_path = registration.get_registered(
restricted_name=template_json_restricted_name)
if template_json_restricted_path != template_restricted_path:
logger.error(
f'The supplied --template-restricted-path {template_restricted_path} does not match the'
f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.'
f' Either the the supplied --template-restricted-path is incorrect or the templates'
f' "restricted" is wrong. Note that since this template specifies "restricted" as'
f' {template_json_restricted_name} --template-restricted-path need not be supplied'
f' and {template_json_restricted_path} will be used.')
return 1
# check and make sure the restricted path exists
if not os.path.isdir(template_restricted_path):
logger.error(f'Template restricted path {template_restricted_path} does not exist.')
return 1
# If the user specified a --template-restricted-platform-relative-path we need to check that against
# the templates 'restricted_platform_relative_path' and see if they match. If they match we don't have
# a problem. If they don't match then we error out. If supplied but not present in the template we warn
# and use --template-restricted-platform-relative-path. If not supplied we set what's in the template.
# If not supplied and not in the template set empty string.
if template_restricted_platform_relative_path:
# The user specified a --template-restricted-platform-relative-path
template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/')
try:
template_json_restricted_platform_relative_path = template_json_data[
'restricted_platform_relative_path']
except Exception as e:
# the template json doesn't have a 'restricted_platform_relative_path' element warn and use it
logger.info(f'The template does not specify a "restricted_platform_relative_path".'
f' Using {template_restricted_platform_relative_path}')
else:
# the template has a 'restricted_platform_relative_path', if it matches we are fine, if not something is
# wrong with either the --template-restricted-platform-relative or the template is
if template_restricted_platform_relative_path != template_json_restricted_platform_relative_path:
logger.error(f'The supplied --template-restricted-platform-relative-path does not match the'
f' templates "restricted_platform_relative_path". Either'
f' --template-restricted-platform-relative-path is incorrect or the templates'
f' "restricted_platform_relative_path" is wrong. Note that since this template'
f' specifies "restricted_platform_relative_path" it need not be supplied and'
f' {template_json_restricted_platform_relative_path} will be used.')
return 1
else:
# The user has not supplied --template-restricted-platform-relative-path, try to read it from
# the template json.
try:
template_restricted_platform_relative_path = template_json_data[
'restricted_platform_relative_path']
except Exception as e:
# The template json doesn't have a 'restricted_platform_relative_path' element, set empty string.
template_restricted_platform_relative_path = ''
if not template_restricted_platform_relative_path:
template_restricted_platform_relative_path = ''
# if no gem_path, error
if not gem_path:
logger.error('Gem path cannot be empty.')
return 1
gem_path = gem_path.replace('\\', '/')
if not os.path.isabs(gem_path):
default_gems_folder = registration.get_registered(default_folder='gems')
new_gem_path = f'{default_gems_folder}/{gem_path}'
logger.info(f'Gem Path {gem_path} is not a full path, we must assume its relative'
f' to default gems path = {new_gem_path}')
gem_path = new_gem_path
if os.path.isdir(gem_path):
logger.error(f'Gem path {gem_path} already exists.')
return 1
else:
os.makedirs(gem_path)
# gem name is now the last component of the gem_path
gem_name = os.path.basename(gem_path)
# gem name cannot be the same as a restricted platform name
if gem_name in restricted_platforms:
logger.error(f'Gem path cannot be a restricted name. {gem_name}')
return 1
# gem restricted name
if gem_restricted_name and not gem_restricted_path:
gem_restricted_path = registration.get_registered(restricted_name=gem_restricted_name)
# gem restricted path
elif gem_restricted_path:
gem_restricted_path = gem_restricted_path.replace('\\', '/')
if not os.path.isabs(gem_restricted_path):
default_gems_restricted_folder = registration.get_registered(restricted_name='gems')
new_gem_restricted_path = f'{default_gems_restricted_folder}/{gem_restricted_path}'
logger.info(f'Gem restricted path {gem_restricted_path} is not a full path, we must assume its'
f' relative to default gems restricted path = {new_gem_restricted_path}')
gem_restricted_path = new_gem_restricted_path
elif template_restricted_path:
gem_restricted_default_path = registration.get_registered(restricted_name='gems')
logger.info(f'--gem-restricted-path is not specified, using default gem restricted path / gem name'
f' = {gem_restricted_default_path}')
gem_restricted_path = gem_restricted_default_path
# gem restricted relative
if gem_restricted_platform_relative_path:
gem_restricted_platform_relative_path = gem_restricted_platform_relative_path.replace('\\', '/')
else:
gem_restricted_platform_relative_path = ''
# any user supplied replacements
replacements = list()
while replace:
replace_this = replace.pop(0)
with_this = replace.pop(0)
replacements.append((replace_this, with_this))
# gem name
replacements.append(("${Name}", gem_name))
replacements.append(("${NameUpper}", gem_name.upper()))
replacements.append(("${NameLower}", gem_name.lower()))
# module id is a uuid with { and -
if module_id:
replacements.append(("${ModuleClassId}", module_id))
else:
replacements.append(("${ModuleClassId}", '{' + str(uuid.uuid4()) + '}'))
# system component class id is a uuid with { and -
if system_component_class_id:
if '{' not in system_component_class_id or '-' not in system_component_class_id:
logger.error(
f'System component class id {system_component_class_id} is malformed. Should look like Ex.' +
'{b60c92eb-3139-454b-a917-a9d3c5819594}')
return 1
replacements.append(("${SysCompClassId}", system_component_class_id))
else:
replacements.append(("${SysCompClassId}", '{' + str(uuid.uuid4()) + '}'))
# editor system component class id is a uuid with { and -
if editor_system_component_class_id:
if '{' not in editor_system_component_class_id or '-' not in editor_system_component_class_id:
logger.error(
f'Editor System component class id {editor_system_component_class_id} is malformed. Should look like'
f' Ex.' + '{b60c92eb-3139-454b-a917-a9d3c5819594}')
return 1
replacements.append(("${EditorSysCompClassId}", editor_system_component_class_id))
else:
replacements.append(("${EditorSysCompClassId}", '{' + str(uuid.uuid4()) + '}'))
if _instantiate_template(template_json_data,
gem_name,
template_name,
gem_path,
template_path,
gem_restricted_path,
template_restricted_path,
gem_restricted_platform_relative_path,
template_restricted_platform_relative_path,
replacements,
keep_restricted_in_gem,
keep_license_text):
logger.error(f'Instantiation of the template has failed.')
return 1
# We created the gem, now do anything extra that a gem requires
# If we are not keeping the restricted in the gem read the name of the restricted folder from the
# restricted json and set that as this gems restricted
if not keep_restricted_in_gem:
if gem_restricted_path:
os.makedirs(gem_restricted_path, exist_ok=True)
# read the restricted_name from the gems restricted.json
restricted_json = f"{gem_restricted_path}/restricted.json".replace('//', '/')
if os.path.isfile(restricted_json):
if not registration.valid_o3de_restricted_json(restricted_json):
logger.error(f'Restricted json {restricted_json} is not valid.')
return 1
else:
with open(restricted_json, 'w') as s:
restricted_json_data = {}
restricted_json_data.update({'restricted_name': gem_name})
s.write(json.dumps(restricted_json_data, indent=4))
with open(restricted_json, 'r') as s:
try:
restricted_json_data = json.load(s)
except Exception as e:
logger.error(f'Failed to load restricted json {restricted_json}.')
return 1
try:
restricted_name = restricted_json_data["restricted_name"]
except Exception as e:
logger.error(f'Failed to read "restricted_name" from restricted json {restricted_json}.')
return 1
# set the "restricted": "restricted_name" element of the gem.json
gem_json = f"{gem_path}/gem.json".replace('//', '/')
if not registration.valid_o3de_gem_json(gem_json):
logger.error(f'Gem json {gem_json} is not valid.')
return 1
with open(gem_json, 'r') as s:
try:
gem_json_data = json.load(s)
except Exception as e:
logger.error(f'Failed to load gem json {gem_json}.')
return 1
gem_json_data.update({"restricted": restricted_name})
os.unlink(gem_json)
with open(gem_json, 'w') as s:
try:
s.write(json.dumps(gem_json_data, indent=4))
except Exception as e:
logger.error(f'Failed to write project json {gem_json}.')
return 1
for restricted_platform in restricted_platforms:
restricted_gem = f'{gem_restricted_path}/{restricted_platform}/{gem_name}'
os.makedirs(restricted_gem, exist_ok=True)
cmakelists_file_name = f'{restricted_gem}/CMakeLists.txt'
if not os.path.isfile(cmakelists_file_name):
with open(cmakelists_file_name, 'w') as d:
if keep_license_text:
d.write('# {BEGIN_LICENSE}\n')
d.write(
'# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or\n')
d.write('# its licensors.\n')
d.write('#\n')
d.write(
'# For complete copyright and license terms please see the LICENSE at the root of this\n')
d.write(
'# distribution (the "License"). All use of this software is governed by the License,\n')
d.write(
'# or, if provided, by the license below or the license accompanying this file. Do not\n')
d.write(
'# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,\n')
d.write('# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n')
d.write('# {END_LICENSE}\n')
return 0
def _run_create_template(args: argparse) -> int:
return create_template(args.source_path,
args.template_path,
args.source_restricted_path,
args.source_restricted_name,
args.template_restricted_path,
args.template_restricted_name,
args.source_restricted_platform_relative_path,
args.template_restricted_platform_relative_path,
args.keep_restricted_in_template,
args.keep_license_text,
args.replace)
def _run_create_from_template(args: argparse) -> int:
return create_from_template(args.destination_path,
args.template_path,
args.template_name,
args.destination_restricted_path,
args.destination_restricted_name,
args.template_restricted_path,
args.template_restricted_name,
args.destination_restricted_platform_relative_path,
args.template_restricted_platform_relative_path,
args.keep_restricted_in_instance,
args.keep_license_text,
args.replace)
def _run_create_project(args: argparse) -> int:
return create_project(args.project_path,
args.template_path,
args.template_name,
args.project_restricted_path,
args.project_restricted_name,
args.template_restricted_path,
args.template_restricted_name,
args.project_restricted_platform_relative_path,
args.template_restricted_platform_relative_path,
args.keep_restricted_in_project,
args.keep_license_text,
args.replace,
args.system_component_class_id,
args.editor_system_component_class_id,
args.module_id)
def _run_create_gem(args: argparse) -> int:
return create_gem(args.gem_path,
args.template_path,
args.template_name,
args.gem_restricted_path,
args.gem_restricted_name,
args.template_restricted_path,
args.template_restricted_name,
args.gem_restricted_platform_relative_path,
args.template_restricted_platform_relative_path,
args.keep_restricted_in_gem,
args.keep_license_text,
args.replace,
args.system_component_class_id,
args.editor_system_component_class_id,
args.module_id)
def add_args(parser, subparsers) -> None:
"""
add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be
invoked locally or aggregated by a central python file.
Ex. Directly run from this file alone with: python engine_template.py create_gem --gem-path TestGem
OR
o3de.py can aggregate commands by importing engine_template,
call add_args and execute: python o3de.py create_gem --gem-path TestGem
:param parser: the caller instantiates a parser and passes it in here
:param subparsers: the caller instantiates subparsers and passes it in here
"""
# turn a directory into a template
create_template_subparser = subparsers.add_parser('create-template')
create_template_subparser.add_argument('-sp', '--source-path', type=str, required=True,
help='The path to the source that you want to make into a template')
create_template_subparser.add_argument('-tp', '--template-path', type=str, required=False,
help='The path to the template to create, can be absolute or relative'
' to default templates path')
group = create_template_subparser.add_mutually_exclusive_group(required=True)
group.add_argument('-srp', '--source-restricted-path', type=str, required=False,
default=None,
help='The path to the source restricted folder.')
group.add_argument('-srn', '--source-restricted-name', type=str, required=False,
default=None,
help='The name of the source restricted folder. If supplied this will resolve'
' the --source-restricted-path.')
group = create_template_subparser.add_mutually_exclusive_group(required=True)
group.add_argument('-trp', '--template-restricted-path', type=str, required=False,
default=None,
help='The path to the templates restricted folder.')
group.add_argument('-trn', '--template-restricted-name', type=str, required=False,
default=None,
help='The name of the templates restricted folder. If supplied this will resolve'
' the --template-restricted-path.')
create_template_subparser.add_argument('-srprp', '--source-restricted-platform-relative-path', type=str,
required=False,
default=None,
help='Any path to append to the --source-restricted-path/<platform>'
' to where the restricted source is. EX.'
' --source-restricted-path C:/restricted'
' --source-restricted-platform-relative-path some/folder'
' => C:/restricted/<platform>/some/folder/<source_name>')
create_template_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str,
required=False,
default=None,
help='Any path to append to the --template-restricted-path/<platform>'
' to where the restricted template source is.'
' --template-restricted-path C:/restricted'
' --template-restricted-platform-relative-path some/folder'
' => C:/restricted/<platform>/some/folder/<template_name>')
create_template_subparser.add_argument('-kr', '--keep-restricted-in-template', action='store_true',
default=False,
help='Should the template keep the restricted platforms in the template, or'
' create the restricted files in the restricted folder, default is'
' False so it will create a restricted folder by default')
create_template_subparser.add_argument('-kl', '--keep-license-text', action='store_true',
default=False,
help='Should license in the template files text be kept in the'
' instantiation, default is False, so will not keep license text'
' by default. License text is defined as all lines of text starting'
' on a line with {BEGIN_LICENSE} and ending line {END_LICENSE}.')
create_template_subparser.add_argument('-r', '--replace', type=str, required=False,
nargs='*',
help='String that specifies A->B replacement pairs.'
' Ex. --replace CoolThing ${the_thing} 1723905 ${id}'
' Note: <TemplateName> is the last component of template_path'
' Note: <TemplateName> is automatically ${Name}'
' Note: <templatename> is automatically ${NameLower}'
' Note: <TEMPLATENAME> is automatically ${NameUpper}')
create_template_subparser.set_defaults(func=_run_create_template)
# create from template
create_from_template_subparser = subparsers.add_parser('create-from-template')
create_from_template_subparser.add_argument('-dp', '--destination-path', type=str, required=True,
help='The path to where you want the template instantiated,'
' can be absolute or dev root relative.'
'Ex. C:/o3de/Test'
'Test = <destination_name>')
group = create_from_template_subparser.add_mutually_exclusive_group(required=True)
group.add_argument('-tp', '--template-path', type=str, required=False,
help='The path to the template you want to instantiate, can be absolute'
' or dev root/Templates relative.'
'Ex. C:/o3de/Template/TestTemplate'
'TestTemplate = <template_name>')
group.add_argument('-tn', '--template-name', type=str, required=False,
help='The name to the registered template you want to instantiate. If supplied this will'
' resolve the --template-path.')
group = create_from_template_subparser.add_mutually_exclusive_group(required=True)
group.add_argument('-drp', '--destination-restricted-path', type=str, required=False,
default=None,
help='The destination restricted path is where the restricted files'
' will be written to.')
group.add_argument('-drn', '--destination-restricted-name', type=str, required=False,
default=None,
help='The name the registered restricted path where the restricted files'
' will be written to. If supplied this will resolve the --destination-restricted-path.')
group = create_from_template_subparser.add_mutually_exclusive_group(required=False)
group.add_argument('-trp', '--template-restricted-path', type=str, required=False,
default=None,
help='The template restricted path to read from if any')
group.add_argument('-trn', '--template-restricted-name', type=str, required=False,
default=None,
help='The name of the registered restricted path to read from if any. If supplied this will'
' resolve the --template-restricted-path.')
create_from_template_subparser.add_argument('-drprp', '--destination-restricted-platform-relative-path', type=str,
required=False,
default='',
help='Any path to append to the --destination-restricted-path/<platform>'
' to where the restricted destination is.'
' --destination-restricted-path C:/instance'
' --destination-restricted-platform-relative-path some/folder'
' => C:/instance/<platform>/some/folder/<destination_name>')
create_from_template_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str,
required=False,
default='Templates',
help='Any path to append to the --template-restricted-path/<platform>'
' to where the restricted template is.'
' --template-restricted-path C:/restricted'
' --template-restricted-platform-relative-path some/folder'
' => C:/restricted/<platform>/some/folder/<template_name>')
create_from_template_subparser.add_argument('-kr', '--keep-restricted-in-instance', action='store_true',
default=False,
help='Should the instance keep the restricted platforms in the instance,'
' or create the restricted files in the restricted folder, default'
' is False')
create_from_template_subparser.add_argument('-kl', '--keep-license-text', action='store_true',
default=False,
help='Should license in the template files text be kept in the instantiation,'
' default is False, so will not keep license text by default.'
' License text is defined as all lines of text starting on a line'
' with {BEGIN_LICENSE} and ending line {END_LICENSE}.')
create_from_template_subparser.add_argument('-r', '--replace', type=str, required=False,
nargs='*',
help='String that specifies A->B replacement pairs.'
' Ex. --replace CoolThing ${the_thing} ${id} 1723905'
' Note: <DestinationName> is the last component of destination_path'
' Note: ${Name} is automatically <DestinationName>'
' Note: ${NameLower} is automatically <destinationname>'
' Note: ${NameUpper} is automatically <DESTINATIONNAME>')
create_from_template_subparser.set_defaults(func=_run_create_from_template)
# creation of a project from a template (like create from template but makes project assumptions)
create_project_subparser = subparsers.add_parser('create-project')
create_project_subparser.add_argument('-pp', '--project-path', type=str, required=True,
help='The name of the project you wish to create from the template,'
' can be an absolute path or dev root relative.'
' Ex. C:/o3de/TestProject'
' TestProject = <project_name>')
group = create_project_subparser.add_mutually_exclusive_group(required=False)
group.add_argument('-tp', '--template-path', type=str, required=False,
default=None,
help='the path to the template you want to instance, can be absolute or'
' relative to default templates path')
group.add_argument('-tn', '--template-name', type=str, required=False,
default=None,
help='The name the registered template you want to instance, defaults'
' to DefaultProject. If supplied this will resolve the --template-path.')
group = create_project_subparser.add_mutually_exclusive_group(required=False)
group.add_argument('-prp', '--project-restricted-path', type=str, required=False,
default=None,
help='path to the projects restricted folder, can be absolute or relative'
' to the restricted="projects"')
group.add_argument('-prn', '--project-restricted-name', type=str, required=False,
default=None,
help='The name of the registered projects restricted path. If supplied this will resolve'
' the --project-restricted-path.')
group = create_project_subparser.add_mutually_exclusive_group(required=False)
group.add_argument('-trp', '--template-restricted-path', type=str, required=False,
default=None,
help='The templates restricted path can be absolute or relative to'
' restricted="templates"')
group.add_argument('-trn', '--template-restricted-name', type=str, required=False,
default=None,
help='The name of the registered templates restricted path. If supplied this will resolve'
' the --template-restricted-path.')
create_project_subparser.add_argument('-prprp', '--project-restricted-platform-relative-path', type=str,
required=False,
default=None,
help='Any path to append to the --project-restricted-path/<platform>'
' to where the restricted project is.'
' --project-restricted-path C:/restricted'
' --project-restricted-platform-relative-path some/folder'
' => C:/restricted/<platform>/some/folder/<project_name>')
create_project_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str,
required=False,
default=None,
help='Any path to append to the --template-restricted-path/<platform>'
' to where the restricted template is.'
' --template-restricted-path C:/restricted'
' --template-restricted-platform-relative-path some/folder'
' => C:/restricted/<platform>/some/folder/<template_name>')
create_project_subparser.add_argument('-kr', '--keep-restricted-in-project', action='store_true',
default=False,
help='Should the new project keep the restricted platforms in the project, or'
'create the restricted files in the restricted folder, default is False')
create_project_subparser.add_argument('-kl', '--keep-license-text', action='store_true',
default=False,
help='Should license in the template files text be kept in the instantiation,'
' default is False, so will not keep license text by default.'
' License text is defined as all lines of text starting on a line'
' with {BEGIN_LICENSE} and ending line {END_LICENSE}.')
create_project_subparser.add_argument('-r', '--replace', required=False,
nargs='*',
help='String that specifies ADDITIONAL A->B replacement pairs. ${Name} and'
' all other standard project replacements will be automatically'
' inferred from the project name. These replacements will superseded'
' all inferred replacements.'
' Ex. --replace ${DATE} 1/1/2020 ${id} 1723905'
' Note: <ProjectName> is the last component of project_path'
' Note: ${Name} is automatically <ProjectName>'
' Note: ${NameLower} is automatically <projectname>'
' Note: ${NameUpper} is automatically <PROJECTNAME>')
create_project_subparser.add_argument('--system-component-class-id', type=utils.validate_uuid4, required=False,
help='The uuid you want to associate with the system class component, default'
' is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_project_subparser.add_argument('--editor-system-component-class-id', type=utils.validate_uuid4,
required=False,
help='The uuid you want to associate with the editor system class component,'
' default is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_project_subparser.add_argument('--module-id', type=utils.validate_uuid4, required=False,
help='The uuid you want to associate with the module, default is a random'
' uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_project_subparser.set_defaults(func=_run_create_project)
# creation of a gem from a template (like create from template but makes gem assumptions)
create_gem_subparser = subparsers.add_parser('create-gem')
create_gem_subparser.add_argument('-gp', '--gem-path', type=str, required=True,
help='The gem path, can be absolute or relative to default gems path')
group = create_gem_subparser.add_mutually_exclusive_group(required=False)
group.add_argument('-tp', '--template-path', type=str, required=False,
default=None,
help='The template path you want to instance, can be absolute or relative'
' to default templates path')
group.add_argument('-tn', '--template-name', type=str, required=False,
default=None,
help='The name of the registered template you want to instance, defaults'
' to DefaultGem. If supplied this will resolve the --template-path.')
group = create_gem_subparser.add_mutually_exclusive_group(required=False)
group.add_argument('-grp', '--gem-restricted-path', type=str, required=False,
default=None,
help='The path to the gem restricted to write to folder if any, can be'
'absolute or dev root relative, default is dev root/restricted.')
group.add_argument('-grn', '--gem-restricted-name', type=str, required=False,
default=None,
help='The path to the gem restricted to write to folder if any, can be'
'absolute or dev root relative, default is dev root/restricted. If supplied'
' this will resolve the --gem-restricted-path.')
group = create_gem_subparser.add_mutually_exclusive_group(required=False)
group.add_argument('-trp', '--template-restricted-path', type=str, required=False,
default=None,
help='The templates restricted path, can be absolute or relative to'
' the restricted="templates"')
group.add_argument('-trn', '--template-restricted-name', type=str, required=False,
default=None,
help='The name of the registered templates restricted path. If supplied'
' this will resolve the --template-restricted-path.')
create_gem_subparser.add_argument('-grprp', '--gem-restricted-platform-relative-path', type=str,
required=False,
default=None,
help='Any path to append to the --gem-restricted-path/<platform>'
' to where the restricted template is.'
' --gem-restricted-path C:/restricted'
' --gem-restricted-platform-relative-path some/folder'
' => C:/restricted/<platform>/some/folder/<gem_name>')
create_gem_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str,
required=False,
default=None,
help='Any path to append to the --template-restricted-path/<platform>'
' to where the restricted template is.'
' --template-restricted-path C:/restricted'
' --template-restricted-platform-relative-path some/folder'
' => C:/restricted/<platform>/some/folder/<template_name>')
create_gem_subparser.add_argument('-r', '--replace', type=str, required=False,
nargs='*',
help='String that specifies ADDITIONAL A->B replacement pairs. ${Name} and'
' all other standard gem replacements will be automatically inferred'
' from the gem name. These replacements will superseded all inferred'
' replacement pairs.'
' Ex. --replace ${DATE} 1/1/2020 ${id} 1723905'
' Note: <GemName> is the last component of gem_path'
' Note: ${Name} is automatically <GemName>'
' Note: ${NameLower} is automatically <gemname>'
' Note: ${NameUpper} is automatically <GEMANME>')
create_gem_subparser.add_argument('-kr', '--keep-restricted-in-gem', action='store_true',
default=False,
help='Should the new gem keep the restricted platforms in the project, or'
'create the restricted files in the restricted folder, default is False')
create_gem_subparser.add_argument('-kl', '--keep-license-text', action='store_true',
default=False,
help='Should license in the template files text be kept in the instantiation,'
' default is False, so will not keep license text by default.'
' License text is defined as all lines of text starting on a line'
' with {BEGIN_LICENSE} and ending line {END_LICENSE}.')
create_gem_subparser.add_argument('--system-component-class-id', type=utils.validate_uuid4, required=False,
help='The uuid you want to associate with the system class component, default'
' is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_gem_subparser.add_argument('--editor-system-component-class-id', type=utils.validate_uuid4,
required=False,
help='The uuid you want to associate with the editor system class component,'
' default is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_gem_subparser.add_argument('--module-id', type=utils.validate_uuid4, required=False,
help='The uuid you want to associate with the gem module,'
' default is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}')
create_gem_subparser.set_defaults(func=_run_create_gem)
if __name__ == "__main__":
# parse the command line args
the_parser = argparse.ArgumentParser()
# add subparsers
the_subparsers = the_parser.add_subparsers(help='sub-command help')
# add args to the parser
add_args(the_parser, the_subparsers)
# parse args
the_args = the_parser.parse_args()
# run
ret = the_args.func(the_args)
# return
sys.exit(ret)