#! /usr/bin/env python
# encoding: utf-8
# Modifications copyright Amazon.com, Inc. or its affiliates.
# XCode 3/XCode 4 generator for Waf
# Nicolas Mercier 2011
# Christopher Bolte: Created a copy to easier adjustment to crytek specific changes
"""
Usage:
def options(opt):
opt.load('xcode')
$ waf configure xcode_ios
$ waf configure xcode_mac
$ waf configure xcode_appletv
"""
# System Imports
import copy
import os
import re
import subprocess
import sys
# waflib imports
from waflib import Build, Context, Logs, Options, TaskGen, Utils
from waflib.Configure import conf, conf_event, ConfigurationContext
from waflib.TaskGen import after_method, feature
# lmbrwaflib imports
from lmbrwaflib.build_configurations import PLATFORM_MAP
HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
MAP_EXT = {
'.h' : "sourcecode.c.h",
'.hh': "sourcecode.cpp.h",
'.inl': "sourcecode.cpp.h",
'.hpp': "sourcecode.cpp.h",
'.c': "sourcecode.c.c",
'.m': "sourcecode.c.objc",
'.mm': "sourcecode.cpp.objcpp",
'.cc': "sourcecode.cpp.cpp",
'.cpp': "sourcecode.cpp.cpp",
'.C': "sourcecode.cpp.cpp",
'.cxx': "sourcecode.cpp.cpp",
'.c++': "sourcecode.cpp.cpp",
'.l': "sourcecode.lex", # luthor
'.ll': "sourcecode.lex",
'.y': "sourcecode.yacc",
'.yy': "sourcecode.yacc",
'.plist': "text.plist.xml",
'.nib': "wrapper.nib",
'.xib': "text.xib",
}
LMBR_WAF_SCRIPT_REL_PATH = 'Tools/build/waf-1.7.13/lmbr_waf'
XCTEST_WRAPPER_TARGETS = ('ios', )
XCTEST_WRAPPER_REL_PATH = 'Code/Tools/Apple/XCTestWrapper'
XCTEST_WRAPPER_SOURCE = [
'LumberyardXCTestWrapperTests.mm',
]
XCTEST_WRAPPER_FILES = XCTEST_WRAPPER_SOURCE + [ 'Info.plist' ]
PLATFORM_SDK_NAME = {
'appletv' : 'appletvos',
'darwin_x64' : 'macosx',
'ios' : 'iphoneos',
}
FRAMEWORKS_REL_PATH = 'System/Library/Frameworks'
XCODE_WORKSPACE_SETTINGS = r'''
BuildSystemType
Original
PreviewsEnabled
'''
root_dir = ''
id = 562000999
uintmax = 2147483647
def newid():
global id
id = id + 1
return "%04X%04X%04X%012d" % (0, 10000, 0, id)
def XCodeEscapeSpacesForShell(spaceString):
return spaceString.replace(' ', '\\ ')
class XCodeNode:
def __init__(self):
self._id = newid()
def tostring(self, value):
if isinstance(value, dict):
result = "{\n"
for k,v in list(value.items()):
result = result + "\t\t\t%s = %s;\n" % (k, self.tostring(v))
result = result + "\t\t}"
return result
elif isinstance(value, str):
return "\"%s\"" % value
elif isinstance(value, list):
result = "(\n"
for i in value:
result = result + "\t\t\t%s,\n" % self.tostring(i)
result = result + "\t\t)"
return result
elif isinstance(value, XCodeNode):
return value._id
else:
return str(value)
def write_recursive(self, value, file):
if isinstance(value, dict):
for k,v in list(value.items()):
self.write_recursive(v, file)
elif isinstance(value, list):
for i in value:
self.write_recursive(i, file)
elif isinstance(value, XCodeNode):
value.write(file)
def write(self, file):
for attribute,value in list(self.__dict__.items()):
if attribute[0] != '_':
self.write_recursive(value, file)
w = file.write
w("\t%s = {\n" % self._id)
w("\t\tisa = %s;\n" % self.__class__.__name__)
for attribute,value in list(self.__dict__.items()):
if attribute[0] != '_':
w("\t\t%s = %s;\n" % (attribute, self.tostring(value)))
w("\t};\n\n")
# Configurations
class XCBuildConfiguration(XCodeNode):
def __init__(self, name, settings = {}):
XCodeNode.__init__(self)
self.baseConfigurationReference = ""
self.buildSettings = settings
self.name = name
class XCConfigurationList(XCodeNode):
def __init__(self, settings):
XCodeNode.__init__(self)
self.buildConfigurations = settings
self.defaultConfigurationIsVisible = 0
self.defaultConfigurationName = settings and settings[0].name or ""
# Group/Files
class PBXFileReference(XCodeNode):
def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT", explicit_filetype = ''):
XCodeNode.__init__(self)
self.fileEncoding = 4
if not filetype:
_, ext = os.path.splitext(name)
filetype = MAP_EXT.get(ext, 'text')
self.lastKnownFileType = filetype
self.name = name
self.path = path
self.sourceTree = sourcetree
if explicit_filetype:
self.explicitFileType = explicit_filetype
class PBXGroup(XCodeNode):
folders_cache = {}
def __init__(self, name, sourcetree = ""):
XCodeNode.__init__(self)
self.children = []
self.name = name
self.sourceTree = sourcetree
def sort_recursive(self):
self.children.sort(key=lambda child: [not isinstance(child, PBXGroup), child.name])
for child in self.children:
if isinstance(child, PBXGroup):
child.sort_recursive()
def add(self, root, sources):
folders = PBXGroup.folders_cache
def folder(node):
if not node:
return self
Logs.debug('xcode_group: %s', node)
Logs.debug('xcode_group: %s', node.name)
Logs.debug('xcode_group: %s', node.parent)
if node == root:
return self
try:
return folders[node]
except KeyError:
new_folder = PBXGroup(node.name)
parent = folder(node.parent)
folders[node] = new_folder
parent.children.append(new_folder)
return new_folder
for s in sources:
Logs.debug('xcode_group: ========')
Logs.debug('xcode_group: %s', s)
a_folder = folder(s.parent)
source = PBXFileReference(s.name, s.abspath())
a_folder.children.append(source)
def get_child_file_references_recursive(self, file_references):
for child in self.children:
if isinstance(child, PBXGroup):
child.get_child_file_references_recursive(file_references)
elif isinstance(child, PBXFileReference):
file_references.append(child)
# Targets
class PBXLegacyTarget(XCodeNode):
def __init__(self, platform_name, project_spec, target, ctx):
XCodeNode.__init__(self)
global root_dir
build_configuration_list = []
for config in ctx.get_target_configurations():
build_configuration_list.append(XCBuildConfiguration(config))
self.buildConfigurationList = XCConfigurationList(build_configuration_list)
action = 'build_' + platform_name + "_$CONFIGURATION"
lmbr_waf_script = os.path.join(ctx.engine_path, LMBR_WAF_SCRIPT_REL_PATH)
self.buildArgumentsString = "%s -cwd %s %s -p%s --targets=%s" % (XCodeEscapeSpacesForShell(lmbr_waf_script), XCodeEscapeSpacesForShell(root_dir), action, project_spec, target)
self.buildPhases = []
self.buildToolPath = sys.executable
self.buildWorkingDirectory = ""
self.dependencies = []
self.name = target or action
self.productName = target or action
self.passBuildSettingsInEnvironment = 0
class PBXShellScriptBuildPhase(XCodeNode):
def __init__(self):
XCodeNode.__init__(self)
self.buildActionMask = uintmax # default when adding a build phase manually from Xcode
self.files = []
self.inputPaths = []
self.outputPaths = []
self.runOnlyForDeploymentPostprocessing = 0
self.shellPath = "/bin/sh"
self.shellScript = ""
class PBXBuildPhaseBase(XCodeNode):
def __init__(self, files):
XCodeNode.__init__(self)
self.buildActionMask = uintmax # default when adding a build phase manually from Xcode
self.files = []
for file in files:
self.files.append(PBXBuildFile(file))
self.runOnlyForDeploymentPostprocessing = 0
class PBXSourcesBuildPhase(PBXBuildPhaseBase):
pass
class PBXFrameworksBuildPhase(PBXBuildPhaseBase):
pass
class PBXResourcesBuildPhase(PBXBuildPhaseBase):
pass
class PBXBuildFile(XCodeNode):
def __init__(self, file_reference):
XCodeNode.__init__(self)
self.fileRef = file_reference._id
class PBXTargetDependency(XCodeNode):
def __init__(self, pbx_native_target):
XCodeNode.__init__(self)
self.target = pbx_native_target
class PBXNativeTarget(XCodeNode):
def __init__(self, platform_name, ctx):
XCodeNode.__init__(self)
self._ctx = ctx
self._platform_name = platform_name
self.buildPhases = []
self.buildRules = []
self.dependencies = []
def init_as_exectuable(self, task_generator, settings, project_spec):
if not task_generator:
return
ctx = self._ctx
platform_name = self._platform_name
target = task_generator.name
node = task_generator.link_task.outputs[0]
target_settings = copy.copy(settings)
target_settings['PRODUCT_NAME'] = node.name
task_gen_for_plist = task_generator
# For the launchers they do not have the plist info files, but the game project does.
# Search for the game project and grab its plist info file.
launcher_name = ctx.get_launcher_name(platform_name)
if launcher_name and target.endswith(launcher_name):
task_gen_for_plist = ctx.get_tgen_by_name(target[:-len(launcher_name)])
# Not all native target are going to have a plist file (like command line executables).
plist_files = getattr(task_gen_for_plist, 'mac_plist', None)
if plist_files:
plist_nodes = task_gen_for_plist.to_nodes(plist_files)
if plist_nodes:
target_settings['INFOPLIST_FILE'] = plist_nodes[0].abspath()
# Since we have a plist file we are going to create an app bundle for this native target
node = node.change_ext('.app')
target_settings['ASSETCATALOG_COMPILER_APPICON_NAME'] = ctx.get_app_icon(target)
target_settings['ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME'] = ctx.get_launch_image(target)
target_settings['RUN_WAF_BUILD'] = 'YES'
output_folder = getattr(task_generator, 'output_folder', None)
if output_folder and isinstance(output_folder, list):
output_folder = output_folder[0]
output_sub_folder = getattr(task_generator, 'output_sub_folder', None)
arch = task_generator.env['ARCH'] if task_generator.env else None
self._generate_build_config_list(target_settings, output_folder, output_sub_folder, arch)
self.add_prepare_for_archive_build_phase_to_target()
self.add_waf_phase_to_target('build_{}_$CONFIGURATION -p{} --package-projects-automatically=False'.format(platform_name, project_spec))
self.add_waf_phase_to_target('package_{}_$CONFIGURATION -p{} --run-xcode-for-packaging=False'.format(platform_name, project_spec))
self.add_cleanup_after_archive_build_phase_to_target()
self.name = target
self.productName = task_generator.output_file_name
self.productType = 'com.apple.product-type.application'
self.productReference = PBXFileReference(target, node.abspath(), 'wrapper.application', 'BUILT_PRODUCTS_DIR')
def init_as_test(self, project, target_executable, settings):
ctx = self._ctx
name = '{}Tests'.format(target_executable.name)
target_product_name = target_executable.productName
product_name = '{}Tests'.format(target_product_name)
target_settings = copy.copy(settings)
target_settings['PRODUCT_NAME'] = product_name
target_settings['INFOPLIST_FILE'] = os.path.join(ctx.engine_path, XCTEST_WRAPPER_REL_PATH, 'Info.plist')
target_settings['TEST_HOST'] = '$(BUILT_PRODUCTS_DIR)/{}.app/{}'.format(target_product_name, target_product_name)
target_settings['BUNDLE_LOADER'] = '$(TEST_HOST)'
target_settings['LD_RUNPATH_SEARCH_PATHS'] = [
'$(inherited)',
'@executable_path/Frameworks',
'@loader_path/Frameworks'
]
target_settings['COPY_PHASE_STRIP'] = 'NO'
target_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
self._generate_build_config_list(target_settings)
test_wrapper_source = []
for test_file in XCTEST_WRAPPER_SOURCE:
file_ref = project.find_pbx_file_reference(os.path.join(XCTEST_WRAPPER_REL_PATH, test_file))
if file_ref:
test_wrapper_source.append(file_ref)
if len(test_wrapper_source) != len(XCTEST_WRAPPER_SOURCE):
ctx.fatal('[ERROR] Failed to find the Lumberyard XCTest Wrapper reference source file(s) in project')
ui_kit = project.find_pbx_file_reference(os.path.join('Frameworks', 'UIKit.framework'))
if not ui_kit:
ctx.fatal('[ERROR] Failed to find the UIKit.framework reference in project')
self.buildPhases = [
PBXSourcesBuildPhase(test_wrapper_source),
PBXFrameworksBuildPhase([ ui_kit ])
]
self.dependencies = [ PBXTargetDependency(target_executable) ]
self.name = name
self.productName = product_name
self.productType = 'com.apple.product-type.bundle.unit-test'
test_target = '{}.xctest'.format(self.productName)
test_target_path = os.path.join(target_executable.productReference.path, 'Plugins', test_target)
self.productReference = PBXFileReference(test_target, test_target_path, sourcetree = 'BUILT_PRODUCTS_DIR', explicit_filetype = 'wrapper.cfbundle')
def add_waf_phase_to_target(self, build_action):
global root_dir
cwd = XCodeEscapeSpacesForShell(root_dir)
lmbr_waf_script = XCodeEscapeSpacesForShell(os.path.join(self._ctx.engine_path, LMBR_WAF_SCRIPT_REL_PATH))
script = (
'if [ \\"${RUN_WAF_BUILD}\\" = \\"YES\\" ]; then\n'
'\t\\"\\%s\\" \\"%s\\" -cwd \\"%s\\" %s\n'
'fi'
) % (sys.executable, lmbr_waf_script, cwd, build_action)
waf_build_phase = PBXShellScriptBuildPhase()
waf_build_phase.shellScript = script
self.buildPhases.append(waf_build_phase)
def add_prepare_for_archive_build_phase_to_target(self):
'''
When doing archive builds we need to first clean the target build directory, and create a symlink to the actual archive location.
Xcode normally does this itself as a built-in archive step, but it fails if we've already built the project because waf will have
created a directory in the same place that Xcode attempts to create the symlink, resulting in an archive with no executable in it!
'''
prepare_for_archive_build_phase = PBXShellScriptBuildPhase()
prepare_for_archive_build_phase.runOnlyForDeploymentPostprocessing = 1 # Only run this build step for archive builds
prepare_for_archive_build_phase.shellScript = (
'rm -rf \\"$BUILT_PRODUCTS_DIR/$WRAPPER_NAME\\"\n'
'ln -sf \\"$TARGET_BUILD_DIR/$WRAPPER_NAME\\" \\"$BUILT_PRODUCTS_DIR/$WRAPPER_NAME\\"'
)
self.buildPhases.append(prepare_for_archive_build_phase)
def add_cleanup_after_archive_build_phase_to_target(self):
'''
After doing archive builds we need to delete the symlink that gets created to the target build directory,
otherwise subsequent build or clean commands will fails when trying to create the project structure.
'''
cleanup_after_archive_build_phase = PBXShellScriptBuildPhase()
cleanup_after_archive_build_phase.runOnlyForDeploymentPostprocessing = 1 # Only run this build step for archive builds
cleanup_after_archive_build_phase.shellScript = 'rm -rf \\"$BUILT_PRODUCTS_DIR/$WRAPPER_NAME\\"'
self.buildPhases.append(cleanup_after_archive_build_phase)
def add_resources_build_phase_to_target(self, files):
self.buildPhases.append(PBXResourcesBuildPhase(files))
def add_remove_embedded_provisioning_build_phase_to_target(self):
'''
Remove the embedded provisioning file before each deploy to force xcode to sign the application.
There are times when the executable is copied over but xcode does not sign it. When xcode then
deploys the app it fails because the executable was not signed. Deleting the embeded manifest
file forces xcode to regenerate it and sign the executable.
'''
remove_embedded_provisioning_build_phase = PBXShellScriptBuildPhase()
remove_embedded_provisioning_build_phase.shellScript = 'rm -f \\"$TARGET_BUILD_DIR/$WRAPPER_NAME/embedded.mobileprovision\\"'
self.buildPhases.append(remove_embedded_provisioning_build_phase)
def _generate_build_config_list(self, settings, output_folder_override = None, output_sub_folder = None, archs_override = None):
ctx = self._ctx
platform_name = self._platform_name
# Because of how wscript files are executed when we recurse (project_generator phase) the
# tools that go into the lmbr_setup tools output are incomplete. For this case we call
# get_lmbr_setup_tools_output_folder ourselves with a platform and config override to get
# the correct output folder.
is_lmbr_setup_tool = False
if output_folder_override and (output_folder_override in ctx.get_lmbr_setup_tools_output_folder()):
is_lmbr_setup_tool = True
build_configurations = []
for config in ctx.get_target_configurations():
config_settings = copy.copy(settings)
build_dir = ''
if is_lmbr_setup_tool:
output_folder_override = ctx.get_lmbr_setup_tools_output_folder(platform_name, config)
build_dir = os.path.join(ctx.engine_path, output_folder_override)
else:
build_dir = ctx.get_output_folders(platform_name, config)[0].abspath()
if output_sub_folder:
build_dir = os.path.join(build_dir, output_sub_folder)
config_settings['CONFIGURATION_BUILD_DIR'] = os.path.normpath(build_dir)
if archs_override:
config_settings['ARCHS'] = ' '.join(archs_override)
build_configurations.append(XCBuildConfiguration(config, config_settings))
self.buildConfigurationList = XCConfigurationList(build_configurations)
# Root project object
class PBXProject(XCodeNode):
def __init__(self, name, version, ctx):
XCodeNode.__init__(self)
build_configuration_list = []
for config in ctx.get_target_configurations():
build_configuration_list.append(XCBuildConfiguration(config))
self.buildConfigurationList = XCConfigurationList(build_configuration_list)
self.compatibilityVersion = version[0]
self.hasScannedForEncodings = 1;
self.mainGroup = PBXGroup(name)
self.projectRoot = ""
self.projectDirPath = ""
self.project_spec = 'all'
self.platform_name = 'darwin_x64'
self.config = 'debug'
self.settings = {}
self.targets = []
self._objectVersion = version[1]
self._output = PBXGroup('out')
self.mainGroup.children.append(self._output)
def set_project_spec(self, project_spec):
if (project_spec):
self.project_spec = project_spec
def set_platform_name(self, platform):
self.platform_name = platform
def set_config(self, config):
self.config = config
def set_settings(self, settings):
self.settings = settings
def write(self, file):
w = file.write
w("// !$*UTF8*$!\n")
w("{\n")
w("\tarchiveVersion = 1;\n")
w("\tclasses = {\n")
w("\t};\n")
w("\tobjectVersion = %d;\n" % self._objectVersion)
w("\tobjects = {\n\n")
XCodeNode.write(self, file)
w("\t};\n")
w("\trootObject = %s;\n" % self._id)
w("}\n")
def find_pbx_file_reference(self, file_path):
'''
Search the entire project source tree for a PBXFileReference specified by a relative file path.
'''
node = self.mainGroup
path_parts = file_path.split('/')
for path_part in path_parts:
# if we've somehow managed to get a node that isn't a PBXGroup before exhausting the path,
# the file doesn't exist
if not isinstance(node, PBXGroup):
return None
for child in node.children:
if child.name == path_part:
node = child
break
# the file doesn't exist if all children were exhausted
else:
return None
if isinstance(node, PBXFileReference):
return node
return None
def add_task_gen(self, task_generator, ctx):
target = PBXNativeTarget(self.platform_name, ctx)
target.init_as_exectuable(task_generator, self.settings, self.project_spec)
self.targets.append(target)
self._output.children.append(target.productReference)
if self.platform_name in XCTEST_WRAPPER_TARGETS:
test_target = PBXNativeTarget(self.platform_name, ctx)
test_target.init_as_test(self, target, self.settings)
self.targets.append(test_target)
self._output.children.append(test_target.productReference)
return target
class xcode(Build.BuildContext):
def get_launcher_name(self, platform_name):
return 'ClientLauncher'
def get_settings(self):
settings = {}
return settings
def get_dev_source_assets_subdir(self):
return self.get_bootstrap_assets(self.get_target_platform_name())
def get_release_source_assets_subdir(self):
return '{}_paks'.format(self.get_dev_source_assets_subdir())
def collect_source(self, task_generator):
source_files = task_generator.to_nodes(getattr(task_generator, 'source', []))
plist_files = task_generator.to_nodes(getattr(task_generator, 'mac_plist', []))
source = list(set(source_files + plist_files))
return source
def execute(self):
global root_dir
self.restore()
if not self.all_envs:
self.load_envs()
self.load_user_settings()
self.recurse([self.run_dir])
root_dir = Context.launch_dir
xcode_project_name = self.get_xcode_project_name()
if not xcode_project_name:
xcode_project_name = getattr(Context.g_module, Context.APPNAME, 'project')
project = PBXProject(xcode_project_name, ('Xcode 3.2', 46), self)
project.set_project_spec(self.options.project_spec)
platform_name = self.get_target_platform_name()
project.set_platform_name(platform_name)
project.set_settings(self.get_settings())
resource_group = PBXGroup("Resources")
project.mainGroup.children.append(resource_group)
spec_modules = self.spec_modules(project.project_spec)
project_modules = [];
for project_name in self.get_enabled_game_project_list():
project_modules = project_modules + self.project_and_platform_modules(project_name, platform_name)
target_platforms = ['all']
for platform in self.get_target_platforms():
target_platforms.append(self.platform_to_platform_alias(platform))
target_platforms.append(platform)
target_platforms = set(target_platforms)
# add the xctest wrapper source files to the project for platforms that leverage it for commandline installation
if platform_name in XCTEST_WRAPPER_TARGETS:
xctest_root_node = self.srcnode.make_node(XCTEST_WRAPPER_REL_PATH.split('/'))
test_files = [ xctest_root_node.make_node(test_file) for test_file in XCTEST_WRAPPER_FILES ]
project.mainGroup.add(self.srcnode, test_files)
xcrun_cmd = [
'xcrun',
'--sdk',
PLATFORM_SDK_NAME[platform_name],
'--show-sdk-path'
]
sdk_path = subprocess.check_output(xcrun_cmd).decode(sys.stdout.encoding or 'iso8859-1', 'replace').strip()
# the installTest requires UIKit to show a dialog
frameworks_group = PBXGroup('Frameworks')
ui_kit = PBXFileReference('UIKit.framework', os.path.join(sdk_path, FRAMEWORKS_REL_PATH, 'UIKit.framework'), 'wrapper.framework', '')
frameworks_group.children.append(ui_kit)
project.mainGroup.children.append(frameworks_group)
source_files = []
for group in self.groups:
for task_generator in group:
if not isinstance(task_generator, TaskGen.task_gen):
continue
if (task_generator.target not in spec_modules) and (task_generator.target not in project_modules):
Logs.debug('xcode: Skipping %s because it is not part of the spec', task_generator.name)
continue
task_generator.post()
platforms = target_platforms.intersection(task_generator.platforms)
if not platforms:
Logs.debug('xcode: Skipping %s because it is not supported on platform %s', task_generator.name, platform_name)
continue
source_files = list(set(source_files + self.collect_source(task_generator)))
# Match any C/C++ program feature
features = Utils.to_list(getattr(task_generator, 'features', ''))
have_feature_match = False
for a_feature in features:
if re.search("c.*program", a_feature) != None:
have_feature_match = True
break
else:
Logs.debug('xcode: Skipping %s because it is not a program', task_generator.name)
continue
pbx_native_target = project.add_task_gen(task_generator, self)
xcassets_path = getattr(task_generator, 'darwin_xcassets', None)
if xcassets_path:
app_resources_group = PBXGroup(task_generator.name)
resource_group.children.append(app_resources_group)
xcassets_folder_node = self.engine_node.make_node(xcassets_path)
xcode_assets_folder_ref = PBXFileReference('xcassets', xcassets_folder_node.abspath(), 'folder.assetcatalog')
app_resources_group.children.append(xcode_assets_folder_ref)
pbx_native_target.add_resources_build_phase_to_target([xcode_assets_folder_ref])
project.mainGroup.add(self.srcnode, source_files)
project.targets.sort(key=lambda target: [isinstance(target, PBXLegacyTarget), target.name])
# Create a dummy target that builds all source files so Xcode find file/symbol functionality works
dummy_target = PBXNativeTarget(project.platform_name, self)
source_file_references = []
project.mainGroup.get_child_file_references_recursive(source_file_references)
dummy_target.buildPhases = [PBXSourcesBuildPhase(source_file_references)]
dummy_target.name = 'DummyTargetForSymbols'
dummy_target.productName = 'DummyTargetForSymbols'
dummy_target.productType = 'com.apple.product-type.tool'
dummy_target.productReference = PBXFileReference('DummyTargetForSymbols', 'DummyTargetForSymbols', 'compiled.mach-o.executable', 'BUILT_PRODUCTS_DIR')
project._output.children.append(dummy_target.productReference)
project.targets.append(dummy_target)
# Create game resource group/folder structure and attach it to the native
# projects
root_assets_folder = self.srcnode.make_node("Cache")
for game_project in self.get_enabled_game_project_list():
game_resources_group = PBXGroup(game_project)
resource_group.children.append(game_resources_group)
dev_assets_folder = root_assets_folder.make_node(game_project).make_node(self.get_dev_source_assets_subdir())
dev_assets_folder_ref = PBXFileReference('assets_dev', dev_assets_folder.abspath(), 'folder')
game_resources_group.children.append(dev_assets_folder_ref)
release_assets_folder = root_assets_folder.make_node(game_project).make_node(self.get_release_source_assets_subdir())
release_assets_folder_ref = PBXFileReference('assets_release', release_assets_folder.abspath(), 'folder')
game_resources_group.children.append(release_assets_folder_ref)
xcode_assets_folder = self.launch_node().make_node(self.game_code_folder(game_project) + self.get_xcode_source_assets_subdir())
xcode_assets_folder_ref = PBXFileReference('xcassets', xcode_assets_folder.abspath(), 'folder.assetcatalog')
game_resources_group.children.append(xcode_assets_folder_ref)
for target in project.targets:
launcher_name = self.get_launcher_name(platform_name) or ''
if isinstance(target, PBXNativeTarget) and target.name == game_project + launcher_name:
target.add_remove_embedded_provisioning_build_phase_to_target()
target.add_resources_build_phase_to_target([xcode_assets_folder_ref])
project.mainGroup.sort_recursive()
projectDir = self.srcnode.make_node("/%s.xcodeproj" % xcode_project_name)
projectDir.mkdir()
node = projectDir.make_node('project.pbxproj')
project.write(open(node.abspath(), 'w'))
# Generate settings to make Xcode use the Legacy Build System
project_ws_node = projectDir.make_node('project.xcworkspace')
project_ws_node.mkdir()
shared_data_node = project_ws_node.make_node("xcshareddata")
shared_data_node.mkdir()
wpfile = shared_data_node.make_node("WorkspaceSettings.xcsettings")
with open(wpfile.abspath(),"w") as f:
f.write(XCODE_WORKSPACE_SETTINGS)
class xcode_mac(xcode):
'''Generate an xcode project for Mac'''
cmd = 'xcode_mac'
is_project_generator = True
def get_target_platforms(self):
return ['darwin_x64']
def get_target_platform_name(self):
return 'darwin_x64'
def get_target_configurations(self):
return PLATFORM_MAP['darwin_x64'].get_configuration_names()
def get_xcode_project_name(self):
return self.get_mac_project_name()
def get_target_assets_subdir(self):
return 'Contents/Resources/assets'
def get_xcode_source_assets_subdir(self):
return '/Resources/MacLauncher/Images.xcassets'
def get_app_icon(self, project):
return self.get_mac_app_icon(project)
def get_launch_image(self, project):
return self.get_mac_launch_image(project)
class xcode_ios(xcode):
'''Generate an xcode project for iOS'''
cmd = 'xcode_ios'
is_project_generator = True
def get_target_platforms(self):
return ['ios']
def get_target_platform_name(self):
return 'ios'
def get_target_configurations(self):
return PLATFORM_MAP['ios'].get_configuration_names()
def get_settings(self):
settings = xcode.get_settings(self)
get_arm_settings_func = getattr(self, 'get_{}_xcode_settings'.format(self.get_target_platform_name()), lambda : dict())
settings.update(get_arm_settings_func())
return settings
def get_xcode_project_name(self):
return self.get_ios_project_name()
def get_target_assets_subdir(self):
return 'assets'
def get_xcode_source_assets_subdir(self):
return '/Resources/IOSLauncher/Images.xcassets'
def get_app_icon(self, project):
return self.get_ios_app_icon(project)
def get_launch_image(self, project):
return self.get_ios_launch_image(project)
class xcode_appletv(xcode_ios):
'''Generate an xcode project for apple tv'''
cmd = 'xcode_appletv'
is_project_generator = True
def get_target_platforms(self):
return ['appletv']
def get_target_platform_name(self):
return 'appletv'
def get_target_configurations(self):
return PLATFORM_MAP['appletv'].get_configuration_names()
def get_xcode_project_name(self):
return self.get_appletv_project_name()
def get_xcode_source_assets_subdir(self):
return '/Resources/AppleTVLauncher/Images.xcassets'
def get_app_icon(self, project):
return self.get_appletv_app_icon(project)
def get_launch_image(self, project):
return self.get_appletv_launch_image(project)
@conf
def get_project_overrides(ctx, target):
# Stub to satisfy context methods used for project/solution overrides. Update if we want to
# add similar support for xcode
return {}
@conf
def get_file_overrides(ctx, target):
# Stub to satisfy context methods used for project/solution overrides. Update if we want to
# add similar support for xcode
return {}
@conf
def get_solution_overrides(self):
# Stub to satisfy context methods used for project/solution overrides. Update if we want to
# add similar support for xcode
return {}
@feature("parse_vcxproj")
def xcode_stub_parse_vcxproj(self):
pass
@conf_event(after_methods=['load_compile_rules_for_enabled_platforms'],
after_events=['inject_generate_uber_command', 'inject_generate_module_def_command'])
def inject_xcode_commands(conf):
"""
Make sure optional xcode project command(s) are injected into the configuration pipeline as needed
"""
if not isinstance(conf, ConfigurationContext):
return
if not Utils.unversioned_sys_platform() == 'darwin':
return
def _add_optioned_command(option, command):
if conf.is_option_true(option):
# Workflow improvement: for all builds generate projects after the build
# except when using the default build target 'utilities' then do it before
if 'build' in Options.commands:
build_cmd_idx = Options.commands.index('build')
Options.commands.insert(build_cmd_idx, command)
else:
Options.commands.append(command)
# Insert the optional xcode project generation commands in the configure pipeline
_add_optioned_command('generate_ios_projects_automatically', 'xcode_ios')
_add_optioned_command('generate_appletv_projects_automatically', 'xcode_appletv')
_add_optioned_command('generate_mac_projects_automatically', 'xcode_mac')