2015-10-29 11:16:05 +01:00
#! /usr/bin/env python
# encoding: utf-8
2016-07-18 13:57:23 +02:00
# XCode 3/XCode 4/XCode 6/Xcode 7 generator for Waf
2015-10-29 11:53:19 +01:00
# Based on work by Nicolas Mercier 2011
# Extended by Simon Warg 2015, https://github.com/mimon
2015-10-29 11:16:05 +01:00
# XCode project file format based on http://www.monobjc.net/xcode-project-file-format.html
"""
2017-04-15 16:08:52 +02:00
See playground / xcode6 / for usage examples .
2015-10-29 11:16:05 +01:00
2017-04-15 16:08:52 +02:00
"""
2015-10-29 11:16:05 +01:00
2016-09-04 13:23:12 +02:00
from waflib import Context , TaskGen , Build , Utils , Errors , Logs
2016-05-28 16:18:51 +02:00
import os , sys
2015-10-29 11:16:05 +01:00
2017-04-08 01:57:46 +02:00
# FIXME too few extensions
XCODE_EXTS = [ ' .c ' , ' .cpp ' , ' .m ' , ' .mm ' ]
2015-10-29 11:16:05 +01:00
HEADERS_GLOB = ' **/(*.h|*.hpp|*.H|*.inl) '
MAP_EXT = {
2015-11-08 10:06:03 +01:00
' ' : " folder " ,
2015-10-29 11:16:05 +01:00
' .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 " ,
}
# Used in PBXNativeTarget elements
PRODUCT_TYPE_APPLICATION = ' com.apple.product-type.application '
PRODUCT_TYPE_FRAMEWORK = ' com.apple.product-type.framework '
PRODUCT_TYPE_EXECUTABLE = ' com.apple.product-type.tool '
PRODUCT_TYPE_LIB_STATIC = ' com.apple.product-type.library.static '
PRODUCT_TYPE_LIB_DYNAMIC = ' com.apple.product-type.library.dynamic '
PRODUCT_TYPE_EXTENSION = ' com.apple.product-type.kernel-extension '
PRODUCT_TYPE_IOKIT = ' com.apple.product-type.kernel-extension.iokit '
# Used in PBXFileReference elements
FILE_TYPE_APPLICATION = ' wrapper.cfbundle '
FILE_TYPE_FRAMEWORK = ' wrapper.framework '
FILE_TYPE_LIB_DYNAMIC = ' compiled.mach-o.dylib '
FILE_TYPE_LIB_STATIC = ' archive.ar '
FILE_TYPE_EXECUTABLE = ' compiled.mach-o.executable '
# Tuple packs of the above
TARGET_TYPE_FRAMEWORK = ( PRODUCT_TYPE_FRAMEWORK , FILE_TYPE_FRAMEWORK , ' .framework ' )
TARGET_TYPE_APPLICATION = ( PRODUCT_TYPE_APPLICATION , FILE_TYPE_APPLICATION , ' .app ' )
TARGET_TYPE_DYNAMIC_LIB = ( PRODUCT_TYPE_LIB_DYNAMIC , FILE_TYPE_LIB_DYNAMIC , ' .dylib ' )
TARGET_TYPE_STATIC_LIB = ( PRODUCT_TYPE_LIB_STATIC , FILE_TYPE_LIB_STATIC , ' .a ' )
TARGET_TYPE_EXECUTABLE = ( PRODUCT_TYPE_EXECUTABLE , FILE_TYPE_EXECUTABLE , ' ' )
# Maps target type string to its data
TARGET_TYPES = {
' framework ' : TARGET_TYPE_FRAMEWORK ,
' app ' : TARGET_TYPE_APPLICATION ,
' dylib ' : TARGET_TYPE_DYNAMIC_LIB ,
' stlib ' : TARGET_TYPE_STATIC_LIB ,
' exe ' : TARGET_TYPE_EXECUTABLE ,
}
2016-09-25 23:57:43 +02:00
def delete_invalid_values ( dct ) :
""" Deletes entries that are dictionaries or sets """
for k , v in list ( dct . items ( ) ) :
if isinstance ( v , dict ) or isinstance ( v , set ) :
del dct [ k ]
return dct
2015-10-29 11:16:05 +01:00
"""
Configuration of the global project settings . Sets an environment variable ' PROJ_CONFIGURATION '
which is a dictionary of configuration name and buildsettings pair .
E . g . :
env . PROJ_CONFIGURATION = {
' Debug ' : {
' ARCHS ' : ' x86 ' ,
. . .
}
' Release ' : {
' ARCHS ' x86_64 '
. . .
}
}
The user can define a completely customized dictionary in configure ( ) stage . Otherwise a default Debug / Release will be created
based on env variable
"""
def configure ( self ) :
if not self . env . PROJ_CONFIGURATION :
self . to_log ( " A default project configuration was created since no custom one was given in the configure(conf) stage. Define your custom project settings by adding PROJ_CONFIGURATION to env. The env.PROJ_CONFIGURATION must be a dictionary with at least one key, where each key is the configuration name, and the value is a dictionary of key/value settings. \n " )
# Check for any added config files added by the tool 'c_config'.
if ' cfg_files ' in self . env :
self . env . INCLUDES = Utils . to_list ( self . env . INCLUDES ) + [ os . path . abspath ( os . path . dirname ( f ) ) for f in self . env . cfg_files ]
# Create default project configuration?
if ' PROJ_CONFIGURATION ' not in self . env :
2016-09-25 23:57:43 +02:00
defaults = delete_invalid_values ( self . env . get_merged_dict ( ) )
2015-10-29 11:16:05 +01:00
self . env . PROJ_CONFIGURATION = {
2016-09-25 23:57:43 +02:00
" Debug " : defaults ,
" Release " : defaults ,
2015-10-29 11:16:05 +01:00
}
# Some build settings are required to be present by XCode. We will supply default values
# if user hasn't defined any.
defaults_required = [ ( ' PRODUCT_NAME ' , ' $(TARGET_NAME) ' ) ]
2018-03-07 22:34:47 +01:00
for cfgname , settings in self . env . PROJ_CONFIGURATION . items ( ) :
2015-10-29 11:16:05 +01:00
for default_var , default_val in defaults_required :
if default_var not in settings :
settings [ default_var ] = default_val
# Error check customization
if not isinstance ( self . env . PROJ_CONFIGURATION , dict ) :
raise Errors . ConfigurationError ( " The env.PROJ_CONFIGURATION must be a dictionary with at least one key, where each key is the configuration name, and the value is a dictionary of key/value settings. " )
part1 = 0
part2 = 10000
part3 = 0
id = 562000999
def newid ( ) :
global id
2016-05-22 00:45:47 +02:00
id + = 1
2015-10-29 11:16:05 +01:00
return " %04X %04X %04X %012d " % ( 0 , 10000 , 0 , id )
2017-04-15 16:08:52 +02:00
"""
Represents a tree node in the XCode project plist file format .
When written to a file , all attributes of XCodeNode are stringified together with
its value . However , attributes starting with an underscore _ are ignored
2018-07-29 01:46:58 +02:00
during that process and allows you to store arbitrary values that are not supposed
2017-04-15 16:08:52 +02:00
to be written out .
"""
2017-04-17 12:24:33 +02:00
class XCodeNode ( object ) :
2015-10-29 11:16:05 +01:00
def __init__ ( self ) :
self . _id = newid ( )
self . _been_written = False
def tostring ( self , value ) :
if isinstance ( value , dict ) :
result = " { \n "
for k , v in 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 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 ) :
if not self . _been_written :
self . _been_written = True
for attribute , value in self . __dict__ . items ( ) :
if attribute [ 0 ] != ' _ ' :
self . write_recursive ( value , file )
w = file . write
w ( " \t %s = { \n " % self . _id )
w ( " \t \t isa = %s ; \n " % self . __class__ . __name__ )
for attribute , value in 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 = { } , env = None ) :
XCodeNode . __init__ ( self )
self . baseConfigurationReference = " "
self . buildSettings = settings
self . name = name
if env and env . ARCH :
settings [ ' ARCHS ' ] = " " . join ( env . ARCH )
class XCConfigurationList ( XCodeNode ) :
def __init__ ( self , configlst ) :
""" :param configlst: list of XCConfigurationList """
XCodeNode . __init__ ( self )
self . buildConfigurations = configlst
self . defaultConfigurationIsVisible = 0
self . defaultConfigurationName = configlst and configlst [ 0 ] . name or " "
# Group/Files
class PBXFileReference ( XCodeNode ) :
def __init__ ( self , name , path , filetype = ' ' , sourcetree = " SOURCE_ROOT " ) :
2017-04-15 16:08:52 +02:00
2015-10-29 11:16:05 +01:00
XCodeNode . __init__ ( self )
self . fileEncoding = 4
if not filetype :
_ , ext = os . path . splitext ( name )
filetype = MAP_EXT . get ( ext , ' text ' )
self . lastKnownFileType = filetype
2017-04-08 01:57:46 +02:00
self . explicitFileType = filetype
2015-10-29 11:16:05 +01:00
self . name = name
self . path = path
self . sourceTree = sourcetree
def __hash__ ( self ) :
return ( self . path + self . name ) . __hash__ ( )
def __eq__ ( self , other ) :
return ( self . path , self . name ) == ( other . path , other . name )
class PBXBuildFile ( XCodeNode ) :
""" This element indicate a file reference that is used in a PBXBuildPhase (either as an include or resource). """
def __init__ ( self , fileRef , settings = { } ) :
XCodeNode . __init__ ( self )
2017-04-08 01:57:46 +02:00
2015-10-29 11:16:05 +01:00
# fileRef is a reference to a PBXFileReference object
self . fileRef = fileRef
2018-07-29 01:46:58 +02:00
# A map of key/value pairs for additional settings.
2015-10-29 11:16:05 +01:00
self . settings = settings
2015-11-08 10:06:03 +01:00
def __hash__ ( self ) :
return ( self . fileRef ) . __hash__ ( )
def __eq__ ( self , other ) :
return self . fileRef == other . fileRef
2015-10-29 11:16:05 +01:00
class PBXGroup ( XCodeNode ) :
2017-04-15 16:08:52 +02:00
def __init__ ( self , name , sourcetree = ' SOURCE_TREE ' ) :
2015-10-29 11:16:05 +01:00
XCodeNode . __init__ ( self )
self . children = [ ]
self . name = name
self . sourceTree = sourcetree
2017-04-15 16:08:52 +02:00
# Maintain a lookup table for all PBXFileReferences
# that are contained in this group.
self . _filerefs = { }
2015-10-29 11:16:05 +01:00
def add ( self , sources ) :
2017-04-15 16:08:52 +02:00
"""
Add a list of PBXFileReferences to this group
: param sources : list of PBXFileReferences objects
"""
self . _filerefs . update ( dict ( zip ( sources , sources ) ) )
2015-10-29 11:16:05 +01:00
self . children . extend ( sources )
2017-04-15 16:08:52 +02:00
def get_sub_groups ( self ) :
"""
Returns all child PBXGroup objects contained in this group
"""
return list ( filter ( lambda x : isinstance ( x , PBXGroup ) , self . children ) )
def find_fileref ( self , fileref ) :
"""
Recursively search this group for an existing PBXFileReference . Returns None
if none were found .
2017-08-03 06:43:50 +02:00
The reason you ' d want to reuse existing PBXFileReferences from a PBXGroup is that XCode doesn ' t like PBXFileReferences that aren ' t part of a PBXGroup hierarchy.
2017-04-15 16:08:52 +02:00
If it isn ' t, the consequence is that certain UI features like ' Reveal in Finder '
stops working .
"""
if fileref in self . _filerefs :
return self . _filerefs [ fileref ]
elif self . children :
for childgroup in self . get_sub_groups ( ) :
f = childgroup . find_fileref ( fileref )
if f :
return f
return None
2015-10-29 11:16:05 +01:00
class PBXContainerItemProxy ( XCodeNode ) :
""" This is the element for to decorate a target item. """
def __init__ ( self , containerPortal , remoteGlobalIDString , remoteInfo = ' ' , proxyType = 1 ) :
XCodeNode . __init__ ( self )
self . containerPortal = containerPortal # PBXProject
self . remoteGlobalIDString = remoteGlobalIDString # PBXNativeTarget
self . remoteInfo = remoteInfo # Target name
self . proxyType = proxyType
class PBXTargetDependency ( XCodeNode ) :
""" This is the element for referencing other target through content proxies. """
def __init__ ( self , native_target , proxy ) :
XCodeNode . __init__ ( self )
self . target = native_target
self . targetProxy = proxy
2017-04-08 01:57:46 +02:00
2015-10-29 11:16:05 +01:00
class PBXFrameworksBuildPhase ( XCodeNode ) :
""" This is the element for the framework link build phase, i.e. linking to frameworks """
def __init__ ( self , pbxbuildfiles ) :
XCodeNode . __init__ ( self )
self . buildActionMask = 2147483647
self . runOnlyForDeploymentPostprocessing = 0
self . files = pbxbuildfiles #List of PBXBuildFile (.o, .framework, .dylib)
class PBXHeadersBuildPhase ( XCodeNode ) :
""" This is the element for adding header files to be packaged into the .framework """
def __init__ ( self , pbxbuildfiles ) :
XCodeNode . __init__ ( self )
self . buildActionMask = 2147483647
self . runOnlyForDeploymentPostprocessing = 0
self . files = pbxbuildfiles #List of PBXBuildFile (.o, .framework, .dylib)
2015-11-08 10:06:03 +01:00
class PBXCopyFilesBuildPhase ( XCodeNode ) :
"""
Represents the PBXCopyFilesBuildPhase section . PBXBuildFile
can be added to this node to copy files after build is done .
"""
def __init__ ( self , pbxbuildfiles , dstpath , dstSubpathSpec = 0 , * args , * * kwargs ) :
XCodeNode . __init__ ( self )
self . files = pbxbuildfiles
self . dstPath = dstpath
self . dstSubfolderSpec = dstSubpathSpec
2015-10-29 11:16:05 +01:00
class PBXSourcesBuildPhase ( XCodeNode ) :
""" Represents the ' Compile Sources ' build phase in a Xcode target """
def __init__ ( self , buildfiles ) :
XCodeNode . __init__ ( self )
self . files = buildfiles # List of PBXBuildFile objects
class PBXLegacyTarget ( XCodeNode ) :
def __init__ ( self , action , target = ' ' ) :
XCodeNode . __init__ ( self )
self . buildConfigurationList = XCConfigurationList ( [ XCBuildConfiguration ( ' waf ' , { } ) ] )
if not target :
self . buildArgumentsString = " %s %s " % ( sys . argv [ 0 ] , action )
else :
self . buildArgumentsString = " %s %s --targets= %s " % ( sys . argv [ 0 ] , action , 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 , action , target ) :
XCodeNode . __init__ ( self )
self . buildActionMask = 2147483647
self . files = [ ]
self . inputPaths = [ ]
self . outputPaths = [ ]
self . runOnlyForDeploymentPostProcessing = 0
self . shellPath = " /bin/sh "
self . shellScript = " %s %s %s --targets= %s " % ( sys . executable , sys . argv [ 0 ] , action , target )
class PBXNativeTarget ( XCodeNode ) :
2015-11-08 10:06:03 +01:00
""" Represents a target in XCode, e.g. App, DyLib, Framework etc. """
2015-10-29 11:16:05 +01:00
def __init__ ( self , target , node , target_type = TARGET_TYPE_APPLICATION , configlist = [ ] , buildphases = [ ] ) :
XCodeNode . __init__ ( self )
product_type = target_type [ 0 ]
file_type = target_type [ 1 ]
self . buildConfigurationList = XCConfigurationList ( configlist )
self . buildPhases = buildphases
self . buildRules = [ ]
self . dependencies = [ ]
self . name = target
self . productName = target
self . productType = product_type # See TARGET_TYPE_ tuples constants
self . productReference = PBXFileReference ( node . name , node . abspath ( ) , file_type , ' ' )
def add_configuration ( self , cf ) :
""" :type cf: XCBuildConfiguration """
self . buildConfigurationList . buildConfigurations . append ( cf )
def add_build_phase ( self , phase ) :
# Some build phase types may appear only once. If a phase type already exists, then merge them.
if ( ( phase . __class__ == PBXFrameworksBuildPhase )
or ( phase . __class__ == PBXSourcesBuildPhase ) ) :
for b in self . buildPhases :
if b . __class__ == phase . __class__ :
b . files . extend ( phase . files )
return
self . buildPhases . append ( phase )
def add_dependency ( self , depnd ) :
self . dependencies . append ( depnd )
# Root project object
class PBXProject ( XCodeNode ) :
def __init__ ( self , name , version , env ) :
XCodeNode . __init__ ( self )
if not isinstance ( env . PROJ_CONFIGURATION , dict ) :
raise Errors . WafError ( " Error: env.PROJ_CONFIGURATION must be a dictionary. This is done for you if you do not define one yourself. However, did you load the xcode module at the end of your wscript configure() ? " )
2017-02-16 07:03:43 +01:00
# Retrieve project configuration
2015-10-29 11:16:05 +01:00
configurations = [ ]
for config_name , settings in env . PROJ_CONFIGURATION . items ( ) :
cf = XCBuildConfiguration ( config_name , settings )
configurations . append ( cf )
self . buildConfigurationList = XCConfigurationList ( configurations )
self . compatibilityVersion = version [ 0 ]
2016-02-19 23:19:56 +01:00
self . hasScannedForEncodings = 1
2015-10-29 11:16:05 +01:00
self . mainGroup = PBXGroup ( name )
self . projectRoot = " "
self . projectDirPath = " "
self . targets = [ ]
self . _objectVersion = version [ 1 ]
def create_target_dependency ( self , target , name ) :
""" : param target : PXBNativeTarget """
proxy = PBXContainerItemProxy ( self , target , name )
2018-07-29 01:46:58 +02:00
dependency = PBXTargetDependency ( target , proxy )
return dependency
2015-10-29 11:16:05 +01:00
def write ( self , file ) :
# Make sure this is written only once
if self . _been_written :
return
w = file . write
w ( " // !$*UTF8*$! \n " )
w ( " { \n " )
w ( " \t archiveVersion = 1; \n " )
w ( " \t classes = { \n " )
w ( " \t }; \n " )
w ( " \t objectVersion = %d ; \n " % self . _objectVersion )
w ( " \t objects = { \n \n " )
XCodeNode . write ( self , file )
w ( " \t }; \n " )
w ( " \t rootObject = %s ; \n " % self . _id )
w ( " } \n " )
def add_target ( self , target ) :
self . targets . append ( target )
def get_target ( self , name ) :
""" Get a reference to PBXNativeTarget if it exists """
for t in self . targets :
if t . name == name :
return t
return None
2017-04-09 11:28:25 +02:00
@TaskGen.feature ( ' c ' , ' cxx ' )
2017-08-02 00:38:11 +02:00
@TaskGen.after ( ' propagate_uselib_vars ' , ' apply_incpaths ' )
2017-04-09 11:28:25 +02:00
def process_xcode ( self ) :
2017-04-15 13:50:14 +02:00
bld = self . bld
try :
p = bld . project
except AttributeError :
return
2017-04-09 11:28:25 +02:00
if not hasattr ( self , ' target_type ' ) :
return
products_group = bld . products_group
2017-04-15 13:50:14 +02:00
target_group = PBXGroup ( self . name )
2017-04-09 11:28:25 +02:00
p . mainGroup . children . append ( target_group )
# Determine what type to build - framework, app bundle etc.
2017-04-15 13:50:14 +02:00
target_type = getattr ( self , ' target_type ' , ' app ' )
2017-04-09 11:28:25 +02:00
if target_type not in TARGET_TYPES :
2017-04-15 13:50:14 +02:00
raise Errors . WafError ( " Target type ' %s ' does not exists. Available options are ' %s ' . In target ' %s ' " % ( target_type , " ' , ' " . join ( TARGET_TYPES . keys ( ) ) , self . name ) )
2017-04-09 11:28:25 +02:00
else :
target_type = TARGET_TYPES [ target_type ]
file_ext = target_type [ 2 ]
# Create the output node
2017-04-15 13:50:14 +02:00
target_node = self . path . find_or_declare ( self . name + file_ext )
target = PBXNativeTarget ( self . name , target_node , target_type , [ ] , [ ] )
2017-04-09 11:28:25 +02:00
products_group . children . append ( target . productReference )
2017-04-15 16:08:52 +02:00
# Pull source files from the 'source' attribute and assign them to a UI group.
# Use a default UI group named 'Source' unless the user
# provides a 'group_files' dictionary to customize the UI grouping.
sources = getattr ( self , ' source ' , [ ] )
if hasattr ( self , ' group_files ' ) :
group_files = getattr ( self , ' group_files ' , [ ] )
for grpname , files in group_files . items ( ) :
group = bld . create_group ( grpname , files )
2017-04-09 11:28:25 +02:00
target_group . children . append ( group )
2017-04-15 16:08:52 +02:00
else :
group = bld . create_group ( ' Source ' , sources )
target_group . children . append ( group )
# Create a PBXFileReference for each source file.
# If the source file already exists as a PBXFileReference in any of the UI groups, then
# reuse that PBXFileReference object (XCode does not like it if we don't reuse)
for idx , path in enumerate ( sources ) :
fileref = PBXFileReference ( path . name , path . abspath ( ) )
existing_fileref = target_group . find_fileref ( fileref )
if existing_fileref :
sources [ idx ] = existing_fileref
else :
sources [ idx ] = fileref
2017-04-09 11:28:25 +02:00
2017-04-15 16:08:52 +02:00
# If the 'source' attribute contains any file extension that XCode can't work with,
# then remove it. The allowed file extensions are defined in XCODE_EXTS.
is_valid_file_extension = lambda file : os . path . splitext ( file . path ) [ 1 ] in XCODE_EXTS
sources = list ( filter ( is_valid_file_extension , sources ) )
2017-04-09 11:28:25 +02:00
2017-09-21 21:37:36 +02:00
buildfiles = [ bld . unique_buildfile ( PBXBuildFile ( x ) ) for x in sources ]
2017-04-15 16:08:52 +02:00
target . add_build_phase ( PBXSourcesBuildPhase ( buildfiles ) )
2017-04-09 11:28:25 +02:00
# Check if any framework to link against is some other target we've made
2017-04-15 13:50:14 +02:00
libs = getattr ( self , ' tmp_use_seen ' , [ ] )
2017-04-09 11:28:25 +02:00
for lib in libs :
use_target = p . get_target ( lib )
if use_target :
# Create an XCode dependency so that XCode knows to build the other target before this target
2017-04-15 16:08:52 +02:00
dependency = p . create_target_dependency ( use_target , use_target . name )
target . add_dependency ( dependency )
buildphase = PBXFrameworksBuildPhase ( [ PBXBuildFile ( use_target . productReference ) ] )
target . add_build_phase ( buildphase )
2017-04-15 13:50:14 +02:00
if lib in self . env . LIB :
self . env . LIB = list ( filter ( lambda x : x != lib , self . env . LIB ) )
2017-04-09 11:28:25 +02:00
# If 'export_headers' is present, add files to the Headers build phase in xcode.
# These are files that'll get packed into the Framework for instance.
2017-04-15 13:50:14 +02:00
exp_hdrs = getattr ( self , ' export_headers ' , [ ] )
2017-04-09 11:28:25 +02:00
hdrs = bld . as_nodes ( Utils . to_list ( exp_hdrs ) )
2017-04-15 16:08:52 +02:00
files = [ p . mainGroup . find_fileref ( PBXFileReference ( n . name , n . abspath ( ) ) ) for n in hdrs ]
files = [ PBXBuildFile ( f , { ' ATTRIBUTES ' : ( ' Public ' , ) } ) for f in files ]
buildphase = PBXHeadersBuildPhase ( files )
target . add_build_phase ( buildphase )
2017-04-09 11:28:25 +02:00
# Merge frameworks and libs into one list, and prefix the frameworks
2017-06-18 12:42:07 +02:00
frameworks = Utils . to_list ( self . env . FRAMEWORK )
frameworks = ' ' . join ( [ ' -framework %s ' % ( f . split ( ' .framework ' ) [ 0 ] ) for f in frameworks ] )
libs = Utils . to_list ( self . env . STLIB ) + Utils . to_list ( self . env . LIB )
libs = ' ' . join ( bld . env [ ' STLIB_ST ' ] % t for t in libs )
2017-04-09 11:28:25 +02:00
# Override target specific build settings
bldsettings = {
2017-04-15 13:50:14 +02:00
' HEADER_SEARCH_PATHS ' : [ ' $(inherited) ' ] + self . env [ ' INCPATHS ' ] ,
2017-06-18 12:42:07 +02:00
' LIBRARY_SEARCH_PATHS ' : [ ' $(inherited) ' ] + Utils . to_list ( self . env . LIBPATH ) + Utils . to_list ( self . env . STLIBPATH ) + Utils . to_list ( self . env . LIBDIR ) ,
2017-04-15 13:50:14 +02:00
' FRAMEWORK_SEARCH_PATHS ' : [ ' $(inherited) ' ] + Utils . to_list ( self . env . FRAMEWORKPATH ) ,
2017-06-18 12:42:07 +02:00
' OTHER_LDFLAGS ' : libs + ' ' + frameworks ,
' OTHER_LIBTOOLFLAGS ' : bld . env [ ' LINKFLAGS ' ] ,
' OTHER_CPLUSPLUSFLAGS ' : Utils . to_list ( self . env [ ' CXXFLAGS ' ] ) ,
' OTHER_CFLAGS ' : Utils . to_list ( self . env [ ' CFLAGS ' ] ) ,
2017-04-09 11:28:25 +02:00
' INSTALL_PATH ' : [ ]
}
# Install path
2017-04-15 13:50:14 +02:00
installpaths = Utils . to_list ( getattr ( self , ' install ' , [ ] ) )
2017-04-09 11:28:25 +02:00
prodbuildfile = PBXBuildFile ( target . productReference )
for instpath in installpaths :
bldsettings [ ' INSTALL_PATH ' ] . append ( instpath )
target . add_build_phase ( PBXCopyFilesBuildPhase ( [ prodbuildfile ] , instpath ) )
if not bldsettings [ ' INSTALL_PATH ' ] :
del bldsettings [ ' INSTALL_PATH ' ]
2017-04-15 16:08:52 +02:00
# Create build settings which can override the project settings. Defaults to none if user
# did not pass argument. This will be filled up with target specific
# search paths, libs to link etc.
settings = getattr ( self , ' settings ' , { } )
2017-04-09 11:28:25 +02:00
# The keys represents different build configuration, e.g. Debug, Release and so on..
# Insert our generated build settings to all configuration names
2020-05-14 22:26:43 +02:00
keys = set ( settings . keys ( ) ) | set ( bld . env . PROJ_CONFIGURATION . keys ( ) )
2017-04-09 11:28:25 +02:00
for k in keys :
if k in settings :
settings [ k ] . update ( bldsettings )
else :
settings [ k ] = bldsettings
for k , v in settings . items ( ) :
target . add_configuration ( XCBuildConfiguration ( k , v ) )
p . add_target ( target )
2015-10-29 11:16:05 +01:00
class xcode ( Build . BuildContext ) :
2015-10-29 11:52:58 +01:00
cmd = ' xcode6 '
2015-10-29 11:16:05 +01:00
fun = ' build '
def as_nodes ( self , files ) :
""" Returns a list of waflib.Nodes from a list of string of file paths """
nodes = [ ]
for x in files :
if not isinstance ( x , str ) :
d = x
else :
d = self . srcnode . find_node ( x )
2017-04-15 16:08:52 +02:00
if not d :
raise Errors . WafError ( ' File \' %s \' was not found ' % x )
2015-10-29 11:16:05 +01:00
nodes . append ( d )
return nodes
def create_group ( self , name , files ) :
2015-11-08 10:06:03 +01:00
"""
2017-04-08 01:57:46 +02:00
Returns a new PBXGroup containing the files ( paths ) passed in the files arg
: type files : string
2015-10-29 11:16:05 +01:00
"""
group = PBXGroup ( name )
2015-11-08 10:06:03 +01:00
"""
Do not use unique file reference here , since XCode seem to allow only one file reference
to be referenced by a group .
"""
2017-04-15 16:08:52 +02:00
files_ = [ ]
for d in self . as_nodes ( Utils . to_list ( files ) ) :
fileref = PBXFileReference ( d . name , d . abspath ( ) )
files_ . append ( fileref )
group . add ( files_ )
2015-10-29 11:16:05 +01:00
return group
2015-11-08 10:06:03 +01:00
def unique_buildfile ( self , buildfile ) :
"""
Returns a unique buildfile , possibly an existing one .
Use this after you ' ve constructed a PBXBuildFile to make sure there is
only one PBXBuildFile for the same file in the same project .
"""
2017-04-09 11:28:25 +02:00
try :
build_files = self . build_files
except AttributeError :
build_files = self . build_files = { }
if buildfile not in build_files :
build_files [ buildfile ] = buildfile
return build_files [ buildfile ]
2015-11-08 10:06:03 +01:00
2015-10-29 11:16:05 +01:00
def execute ( self ) :
"""
Entry point
"""
self . restore ( )
if not self . all_envs :
self . load_envs ( )
self . recurse ( [ self . run_dir ] )
appname = getattr ( Context . g_module , Context . APPNAME , os . path . basename ( self . srcnode . abspath ( ) ) )
p = PBXProject ( appname , ( ' Xcode 3.2 ' , 46 ) , self . env )
2017-04-08 01:57:46 +02:00
2015-11-08 10:06:03 +01:00
# If we don't create a Products group, then
# XCode will create one, which entails that
# we'll start to see duplicate files in the UI
# for some reason.
products_group = PBXGroup ( ' Products ' )
p . mainGroup . children . append ( products_group )
2015-10-29 11:16:05 +01:00
2017-04-09 11:28:25 +02:00
self . project = p
self . products_group = products_group
# post all task generators
# the process_xcode method above will be called for each target
Fixed a crash when building a subset of xcode6 targets
E.g. when running 'waf xcode6 --targets=some-target'
File "/waf/waflib/Scripting.py", line 167, in waf_entry_point
run_commands()
File "/waf/waflib/Scripting.py", line 268, in run_commands
ctx = run_command(cmd_name)
File "/waf/waflib/Scripting.py", line 252, in run_command
ctx.execute()
File "/waf/waflib/extras/xcode6.py", line 679, in execute
self.post_group()
File "/waf/waflib/Build.py", line 767, in post_group
if self.current_group < self._min_grp:
AttributeError: 'xcode' object has no attribute '_min_grp'
2017-07-01 13:23:04 +02:00
if self . targets and self . targets != ' * ' :
( self . _min_grp , self . _exact_tg ) = self . get_targets ( )
2017-04-15 15:12:03 +02:00
self . current_group = 0
while self . current_group < len ( self . groups ) :
2017-04-09 11:28:25 +02:00
self . post_group ( )
2017-04-15 15:12:03 +02:00
self . current_group + = 1
2017-04-08 01:57:46 +02:00
2015-10-29 11:16:05 +01:00
node = self . bldnode . make_node ( ' %s .xcodeproj ' % appname )
node . mkdir ( )
node = node . make_node ( ' project.pbxproj ' )
2017-04-08 01:57:46 +02:00
with open ( node . abspath ( ) , ' w ' ) as f :
p . write ( f )
Logs . pprint ( ' GREEN ' , ' Wrote %r ' % node . abspath ( ) )
def bind_fun ( tgtype ) :
def fun ( self , * k , * * kw ) :
tgtype = fun . __name__
if tgtype == ' shlib ' or tgtype == ' dylib ' :
features = ' cxx cxxshlib '
tgtype = ' dylib '
elif tgtype == ' framework ' :
features = ' cxx cxxshlib '
tgtype = ' framework '
elif tgtype == ' program ' :
features = ' cxx cxxprogram '
tgtype = ' exe '
elif tgtype == ' app ' :
features = ' cxx cxxprogram '
tgtype = ' app '
elif tgtype == ' stlib ' :
features = ' cxx cxxstlib '
tgtype = ' stlib '
2017-09-21 21:37:36 +02:00
lst = kw [ ' features ' ] = Utils . to_list ( kw . get ( ' features ' , [ ] ) )
for x in features . split ( ) :
if not x in kw [ ' features ' ] :
lst . append ( x )
2016-09-04 13:23:12 +02:00
2015-10-29 11:16:05 +01:00
kw [ ' target_type ' ] = tgtype
return self ( * k , * * kw )
2017-04-08 01:57:46 +02:00
fun . __name__ = tgtype
setattr ( Build . BuildContext , tgtype , fun )
return fun
for xx in ' app framework dylib shlib stlib program ' . split ( ) :
bind_fun ( xx )
2015-10-29 11:16:05 +01:00