3 # XCode 3/XCode 4/XCode 6/Xcode 7 generator for Waf
4 # Based on work by Nicolas Mercier 2011
5 # Extended by Simon Warg 2015, https://github.com/mimon
6 # XCode project file format based on http://www.monobjc.net/xcode-project-file-format.html
9 See playground/xcode6/ for usage examples.
13 from waflib
import Context
, TaskGen
, Build
, Utils
, Errors
, Logs
16 # FIXME too few extensions
17 XCODE_EXTS
= ['.c', '.cpp', '.m', '.mm']
19 HEADERS_GLOB
= '**/(*.h|*.hpp|*.H|*.inl)'
23 '.h' : "sourcecode.c.h",
25 '.hh': "sourcecode.cpp.h",
26 '.inl': "sourcecode.cpp.h",
27 '.hpp': "sourcecode.cpp.h",
29 '.c': "sourcecode.c.c",
31 '.m': "sourcecode.c.objc",
33 '.mm': "sourcecode.cpp.objcpp",
35 '.cc': "sourcecode.cpp.cpp",
37 '.cpp': "sourcecode.cpp.cpp",
38 '.C': "sourcecode.cpp.cpp",
39 '.cxx': "sourcecode.cpp.cpp",
40 '.c++': "sourcecode.cpp.cpp",
42 '.l': "sourcecode.lex", # luthor
43 '.ll': "sourcecode.lex",
45 '.y': "sourcecode.yacc",
46 '.yy': "sourcecode.yacc",
48 '.plist': "text.plist.xml",
49 ".nib": "wrapper.nib",
53 # Used in PBXNativeTarget elements
54 PRODUCT_TYPE_APPLICATION
= 'com.apple.product-type.application'
55 PRODUCT_TYPE_FRAMEWORK
= 'com.apple.product-type.framework'
56 PRODUCT_TYPE_EXECUTABLE
= 'com.apple.product-type.tool'
57 PRODUCT_TYPE_LIB_STATIC
= 'com.apple.product-type.library.static'
58 PRODUCT_TYPE_LIB_DYNAMIC
= 'com.apple.product-type.library.dynamic'
59 PRODUCT_TYPE_EXTENSION
= 'com.apple.product-type.kernel-extension'
60 PRODUCT_TYPE_IOKIT
= 'com.apple.product-type.kernel-extension.iokit'
62 # Used in PBXFileReference elements
63 FILE_TYPE_APPLICATION
= 'wrapper.cfbundle'
64 FILE_TYPE_FRAMEWORK
= 'wrapper.framework'
65 FILE_TYPE_LIB_DYNAMIC
= 'compiled.mach-o.dylib'
66 FILE_TYPE_LIB_STATIC
= 'archive.ar'
67 FILE_TYPE_EXECUTABLE
= 'compiled.mach-o.executable'
69 # Tuple packs of the above
70 TARGET_TYPE_FRAMEWORK
= (PRODUCT_TYPE_FRAMEWORK
, FILE_TYPE_FRAMEWORK
, '.framework')
71 TARGET_TYPE_APPLICATION
= (PRODUCT_TYPE_APPLICATION
, FILE_TYPE_APPLICATION
, '.app')
72 TARGET_TYPE_DYNAMIC_LIB
= (PRODUCT_TYPE_LIB_DYNAMIC
, FILE_TYPE_LIB_DYNAMIC
, '.dylib')
73 TARGET_TYPE_STATIC_LIB
= (PRODUCT_TYPE_LIB_STATIC
, FILE_TYPE_LIB_STATIC
, '.a')
74 TARGET_TYPE_EXECUTABLE
= (PRODUCT_TYPE_EXECUTABLE
, FILE_TYPE_EXECUTABLE
, '')
76 # Maps target type string to its data
78 'framework': TARGET_TYPE_FRAMEWORK
,
79 'app': TARGET_TYPE_APPLICATION
,
80 'dylib': TARGET_TYPE_DYNAMIC_LIB
,
81 'stlib': TARGET_TYPE_STATIC_LIB
,
82 'exe' :TARGET_TYPE_EXECUTABLE
,
85 def delete_invalid_values(dct
):
86 """ Deletes entries that are dictionaries or sets """
87 for k
, v
in list(dct
.items()):
88 if isinstance(v
, dict) or isinstance(v
, set):
93 Configuration of the global project settings. Sets an environment variable 'PROJ_CONFIGURATION'
94 which is a dictionary of configuration name and buildsettings pair.
96 env.PROJ_CONFIGURATION = {
106 The user can define a completely customized dictionary in configure() stage. Otherwise a default Debug/Release will be created
107 based on env variable
110 if not self
.env
.PROJ_CONFIGURATION
:
111 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")
113 # Check for any added config files added by the tool 'c_config'.
114 if 'cfg_files' in self
.env
:
115 self
.env
.INCLUDES
= Utils
.to_list(self
.env
.INCLUDES
) + [os
.path
.abspath(os
.path
.dirname(f
)) for f
in self
.env
.cfg_files
]
117 # Create default project configuration?
118 if 'PROJ_CONFIGURATION' not in self
.env
:
119 defaults
= delete_invalid_values(self
.env
.get_merged_dict())
120 self
.env
.PROJ_CONFIGURATION
= {
125 # Some build settings are required to be present by XCode. We will supply default values
126 # if user hasn't defined any.
127 defaults_required
= [('PRODUCT_NAME', '$(TARGET_NAME)')]
128 for cfgname
,settings
in self
.env
.PROJ_CONFIGURATION
.items():
129 for default_var
, default_val
in defaults_required
:
130 if default_var
not in settings
:
131 settings
[default_var
] = default_val
133 # Error check customization
134 if not isinstance(self
.env
.PROJ_CONFIGURATION
, dict):
135 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.")
144 return "%04X%04X%04X%012d" % (0, 10000, 0, id)
147 Represents a tree node in the XCode project plist file format.
148 When written to a file, all attributes of XCodeNode are stringified together with
149 its value. However, attributes starting with an underscore _ are ignored
150 during that process and allows you to store arbitrary values that are not supposed
153 class XCodeNode(object):
156 self
._been
_written
= False
158 def tostring(self
, value
):
159 if isinstance(value
, dict):
161 for k
,v
in value
.items():
162 result
= result
+ "\t\t\t%s = %s;\n" % (k
, self
.tostring(v
))
163 result
= result
+ "\t\t}"
165 elif isinstance(value
, str):
166 return '"%s"' % value
.replace('"', '\\\\\\"')
167 elif isinstance(value
, list):
170 result
= result
+ "\t\t\t\t%s,\n" % self
.tostring(i
)
171 result
= result
+ "\t\t\t)"
173 elif isinstance(value
, XCodeNode
):
178 def write_recursive(self
, value
, file):
179 if isinstance(value
, dict):
180 for k
,v
in value
.items():
181 self
.write_recursive(v
, file)
182 elif isinstance(value
, list):
184 self
.write_recursive(i
, file)
185 elif isinstance(value
, XCodeNode
):
188 def write(self
, file):
189 if not self
._been
_written
:
190 self
._been
_written
= True
191 for attribute
,value
in self
.__dict
__.items():
192 if attribute
[0] != '_':
193 self
.write_recursive(value
, file)
195 w("\t%s = {\n" % self
._id
)
196 w("\t\tisa = %s;\n" % self
.__class
__.__name
__)
197 for attribute
,value
in self
.__dict
__.items():
198 if attribute
[0] != '_':
199 w("\t\t%s = %s;\n" % (attribute
, self
.tostring(value
)))
203 class XCBuildConfiguration(XCodeNode
):
204 def __init__(self
, name
, settings
= {}, env
=None):
205 XCodeNode
.__init
__(self
)
206 self
.baseConfigurationReference
= ""
207 self
.buildSettings
= settings
210 settings
['ARCHS'] = " ".join(env
.ARCH
)
213 class XCConfigurationList(XCodeNode
):
214 def __init__(self
, configlst
):
215 """ :param configlst: list of XCConfigurationList """
216 XCodeNode
.__init
__(self
)
217 self
.buildConfigurations
= configlst
218 self
.defaultConfigurationIsVisible
= 0
219 self
.defaultConfigurationName
= configlst
and configlst
[0].name
or ""
222 class PBXFileReference(XCodeNode
):
223 def __init__(self
, name
, path
, filetype
= '', sourcetree
= "SOURCE_ROOT"):
225 XCodeNode
.__init
__(self
)
226 self
.fileEncoding
= 4
228 _
, ext
= os
.path
.splitext(name
)
229 filetype
= MAP_EXT
.get(ext
, 'text')
230 self
.lastKnownFileType
= filetype
231 self
.explicitFileType
= filetype
234 self
.sourceTree
= sourcetree
237 return (self
.path
+self
.name
).__hash
__()
239 def __eq__(self
, other
):
240 return (self
.path
, self
.name
) == (other
.path
, other
.name
)
242 class PBXBuildFile(XCodeNode
):
243 """ This element indicate a file reference that is used in a PBXBuildPhase (either as an include or resource). """
244 def __init__(self
, fileRef
, settings
={}):
245 XCodeNode
.__init
__(self
)
247 # fileRef is a reference to a PBXFileReference object
248 self
.fileRef
= fileRef
250 # A map of key/value pairs for additional settings.
251 self
.settings
= settings
254 return (self
.fileRef
).__hash
__()
256 def __eq__(self
, other
):
257 return self
.fileRef
== other
.fileRef
259 class PBXGroup(XCodeNode
):
260 def __init__(self
, name
, sourcetree
= 'SOURCE_TREE'):
261 XCodeNode
.__init
__(self
)
264 self
.sourceTree
= sourcetree
266 # Maintain a lookup table for all PBXFileReferences
267 # that are contained in this group.
270 def add(self
, sources
):
272 Add a list of PBXFileReferences to this group
274 :param sources: list of PBXFileReferences objects
276 self
._filerefs
.update(dict(zip(sources
, sources
)))
277 self
.children
.extend(sources
)
279 def get_sub_groups(self
):
281 Returns all child PBXGroup objects contained in this group
283 return list(filter(lambda x
: isinstance(x
, PBXGroup
), self
.children
))
285 def find_fileref(self
, fileref
):
287 Recursively search this group for an existing PBXFileReference. Returns None
290 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.
291 If it isn't, the consequence is that certain UI features like 'Reveal in Finder'
294 if fileref
in self
._filerefs
:
295 return self
._filerefs
[fileref
]
297 for childgroup
in self
.get_sub_groups():
298 f
= childgroup
.find_fileref(fileref
)
303 class PBXContainerItemProxy(XCodeNode
):
304 """ This is the element for to decorate a target item. """
305 def __init__(self
, containerPortal
, remoteGlobalIDString
, remoteInfo
='', proxyType
=1):
306 XCodeNode
.__init
__(self
)
307 self
.containerPortal
= containerPortal
# PBXProject
308 self
.remoteGlobalIDString
= remoteGlobalIDString
# PBXNativeTarget
309 self
.remoteInfo
= remoteInfo
# Target name
310 self
.proxyType
= proxyType
312 class PBXTargetDependency(XCodeNode
):
313 """ This is the element for referencing other target through content proxies. """
314 def __init__(self
, native_target
, proxy
):
315 XCodeNode
.__init
__(self
)
316 self
.target
= native_target
317 self
.targetProxy
= proxy
319 class PBXFrameworksBuildPhase(XCodeNode
):
320 """ This is the element for the framework link build phase, i.e. linking to frameworks """
321 def __init__(self
, pbxbuildfiles
):
322 XCodeNode
.__init
__(self
)
323 self
.buildActionMask
= 2147483647
324 self
.runOnlyForDeploymentPostprocessing
= 0
325 self
.files
= pbxbuildfiles
#List of PBXBuildFile (.o, .framework, .dylib)
327 class PBXHeadersBuildPhase(XCodeNode
):
328 """ This is the element for adding header files to be packaged into the .framework """
329 def __init__(self
, pbxbuildfiles
):
330 XCodeNode
.__init
__(self
)
331 self
.buildActionMask
= 2147483647
332 self
.runOnlyForDeploymentPostprocessing
= 0
333 self
.files
= pbxbuildfiles
#List of PBXBuildFile (.o, .framework, .dylib)
335 class PBXCopyFilesBuildPhase(XCodeNode
):
337 Represents the PBXCopyFilesBuildPhase section. PBXBuildFile
338 can be added to this node to copy files after build is done.
340 def __init__(self
, pbxbuildfiles
, dstpath
, dstSubpathSpec
=0, *args
, **kwargs
):
341 XCodeNode
.__init
__(self
)
342 self
.files
= pbxbuildfiles
343 self
.dstPath
= dstpath
344 self
.dstSubfolderSpec
= dstSubpathSpec
346 class PBXSourcesBuildPhase(XCodeNode
):
347 """ Represents the 'Compile Sources' build phase in a Xcode target """
348 def __init__(self
, buildfiles
):
349 XCodeNode
.__init
__(self
)
350 self
.files
= buildfiles
# List of PBXBuildFile objects
352 class PBXLegacyTarget(XCodeNode
):
353 def __init__(self
, action
, target
=''):
354 XCodeNode
.__init
__(self
)
355 self
.buildConfigurationList
= XCConfigurationList([XCBuildConfiguration('waf', {})])
357 self
.buildArgumentsString
= "%s %s" % (sys
.argv
[0], action
)
359 self
.buildArgumentsString
= "%s %s --targets=%s" % (sys
.argv
[0], action
, target
)
360 self
.buildPhases
= []
361 self
.buildToolPath
= sys
.executable
362 self
.buildWorkingDirectory
= ""
363 self
.dependencies
= []
364 self
.name
= target
or action
365 self
.productName
= target
or action
366 self
.passBuildSettingsInEnvironment
= 0
368 class PBXShellScriptBuildPhase(XCodeNode
):
369 def __init__(self
, action
, target
):
370 XCodeNode
.__init
__(self
)
371 self
.buildActionMask
= 2147483647
374 self
.outputPaths
= []
375 self
.runOnlyForDeploymentPostProcessing
= 0
376 self
.shellPath
= "/bin/sh"
377 self
.shellScript
= "%s %s %s --targets=%s" % (sys
.executable
, sys
.argv
[0], action
, target
)
379 class PBXNativeTarget(XCodeNode
):
380 """ Represents a target in XCode, e.g. App, DyLib, Framework etc. """
381 def __init__(self
, target
, node
, target_type
=TARGET_TYPE_APPLICATION
, configlist
=[], buildphases
=[]):
382 XCodeNode
.__init
__(self
)
383 product_type
= target_type
[0]
384 file_type
= target_type
[1]
386 self
.buildConfigurationList
= XCConfigurationList(configlist
)
387 self
.buildPhases
= buildphases
389 self
.dependencies
= []
391 self
.productName
= target
392 self
.productType
= product_type
# See TARGET_TYPE_ tuples constants
393 self
.productReference
= PBXFileReference(node
.name
, node
.abspath(), file_type
, '')
395 def add_configuration(self
, cf
):
396 """ :type cf: XCBuildConfiguration """
397 self
.buildConfigurationList
.buildConfigurations
.append(cf
)
399 def add_build_phase(self
, phase
):
400 # Some build phase types may appear only once. If a phase type already exists, then merge them.
401 if ( (phase
.__class
__ == PBXFrameworksBuildPhase
)
402 or (phase
.__class
__ == PBXSourcesBuildPhase
) ):
403 for b
in self
.buildPhases
:
404 if b
.__class
__ == phase
.__class
__:
405 b
.files
.extend(phase
.files
)
407 self
.buildPhases
.append(phase
)
409 def add_dependency(self
, depnd
):
410 self
.dependencies
.append(depnd
)
412 # Root project object
413 class PBXProject(XCodeNode
):
414 def __init__(self
, name
, version
, env
):
415 XCodeNode
.__init
__(self
)
417 if not isinstance(env
.PROJ_CONFIGURATION
, dict):
418 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() ?")
420 # Retrieve project configuration
422 for config_name
, settings
in env
.PROJ_CONFIGURATION
.items():
423 cf
= XCBuildConfiguration(config_name
, settings
)
424 configurations
.append(cf
)
426 self
.buildConfigurationList
= XCConfigurationList(configurations
)
427 self
.compatibilityVersion
= version
[0]
428 self
.hasScannedForEncodings
= 1
429 self
.mainGroup
= PBXGroup(name
)
430 self
.projectRoot
= ""
431 self
.projectDirPath
= ""
433 self
._objectVersion
= version
[1]
435 def create_target_dependency(self
, target
, name
):
436 """ : param target : PXBNativeTarget """
437 proxy
= PBXContainerItemProxy(self
, target
, name
)
438 dependency
= PBXTargetDependency(target
, proxy
)
441 def write(self
, file):
443 # Make sure this is written only once
444 if self
._been
_written
:
450 w("\tarchiveVersion = 1;\n")
453 w("\tobjectVersion = %d;\n" % self
._objectVersion
)
454 w("\tobjects = {\n\n")
456 XCodeNode
.write(self
, file)
459 w("\trootObject = %s;\n" % self
._id
)
462 def add_target(self
, target
):
463 self
.targets
.append(target
)
465 def get_target(self
, name
):
466 """ Get a reference to PBXNativeTarget if it exists """
467 for t
in self
.targets
:
472 @TaskGen.feature('c', 'cxx')
473 @TaskGen.after('propagate_uselib_vars', 'apply_incpaths')
474 def process_xcode(self
):
478 except AttributeError:
481 if not hasattr(self
, 'target_type'):
484 products_group
= bld
.products_group
486 target_group
= PBXGroup(self
.name
)
487 p
.mainGroup
.children
.append(target_group
)
489 # Determine what type to build - framework, app bundle etc.
490 target_type
= getattr(self
, 'target_type', 'app')
491 if target_type
not in TARGET_TYPES
:
492 raise Errors
.WafError("Target type '%s' does not exists. Available options are '%s'. In target '%s'" % (target_type
, "', '".join(TARGET_TYPES
.keys()), self
.name
))
494 target_type
= TARGET_TYPES
[target_type
]
495 file_ext
= target_type
[2]
497 # Create the output node
498 target_node
= self
.path
.find_or_declare(self
.name
+file_ext
)
499 target
= PBXNativeTarget(self
.name
, target_node
, target_type
, [], [])
501 products_group
.children
.append(target
.productReference
)
503 # Pull source files from the 'source' attribute and assign them to a UI group.
504 # Use a default UI group named 'Source' unless the user
505 # provides a 'group_files' dictionary to customize the UI grouping.
506 sources
= getattr(self
, 'source', [])
507 if hasattr(self
, 'group_files'):
508 group_files
= getattr(self
, 'group_files', [])
509 for grpname
,files
in group_files
.items():
510 group
= bld
.create_group(grpname
, files
)
511 target_group
.children
.append(group
)
513 group
= bld
.create_group('Source', sources
)
514 target_group
.children
.append(group
)
516 # Create a PBXFileReference for each source file.
517 # If the source file already exists as a PBXFileReference in any of the UI groups, then
518 # reuse that PBXFileReference object (XCode does not like it if we don't reuse)
519 for idx
, path
in enumerate(sources
):
520 fileref
= PBXFileReference(path
.name
, path
.abspath())
521 existing_fileref
= target_group
.find_fileref(fileref
)
523 sources
[idx
] = existing_fileref
525 sources
[idx
] = fileref
527 # If the 'source' attribute contains any file extension that XCode can't work with,
528 # then remove it. The allowed file extensions are defined in XCODE_EXTS.
529 is_valid_file_extension
= lambda file: os
.path
.splitext(file.path
)[1] in XCODE_EXTS
530 sources
= list(filter(is_valid_file_extension
, sources
))
532 buildfiles
= [bld
.unique_buildfile(PBXBuildFile(x
)) for x
in sources
]
533 target
.add_build_phase(PBXSourcesBuildPhase(buildfiles
))
535 # Check if any framework to link against is some other target we've made
536 libs
= getattr(self
, 'tmp_use_seen', [])
538 use_target
= p
.get_target(lib
)
540 # Create an XCode dependency so that XCode knows to build the other target before this target
541 dependency
= p
.create_target_dependency(use_target
, use_target
.name
)
542 target
.add_dependency(dependency
)
544 buildphase
= PBXFrameworksBuildPhase([PBXBuildFile(use_target
.productReference
)])
545 target
.add_build_phase(buildphase
)
546 if lib
in self
.env
.LIB
:
547 self
.env
.LIB
= list(filter(lambda x
: x
!= lib
, self
.env
.LIB
))
549 # If 'export_headers' is present, add files to the Headers build phase in xcode.
550 # These are files that'll get packed into the Framework for instance.
551 exp_hdrs
= getattr(self
, 'export_headers', [])
552 hdrs
= bld
.as_nodes(Utils
.to_list(exp_hdrs
))
553 files
= [p
.mainGroup
.find_fileref(PBXFileReference(n
.name
, n
.abspath())) for n
in hdrs
]
554 files
= [PBXBuildFile(f
, {'ATTRIBUTES': ('Public',)}) for f
in files
]
555 buildphase
= PBXHeadersBuildPhase(files
)
556 target
.add_build_phase(buildphase
)
558 # Merge frameworks and libs into one list, and prefix the frameworks
559 frameworks
= Utils
.to_list(self
.env
.FRAMEWORK
)
560 frameworks
= ' '.join(['-framework %s' % (f
.split('.framework')[0]) for f
in frameworks
])
562 libs
= Utils
.to_list(self
.env
.STLIB
) + Utils
.to_list(self
.env
.LIB
)
563 libs
= ' '.join(bld
.env
['STLIB_ST'] % t
for t
in libs
)
565 # Override target specific build settings
567 'HEADER_SEARCH_PATHS': ['$(inherited)'] + self
.env
['INCPATHS'],
568 'LIBRARY_SEARCH_PATHS': ['$(inherited)'] + Utils
.to_list(self
.env
.LIBPATH
) + Utils
.to_list(self
.env
.STLIBPATH
) + Utils
.to_list(self
.env
.LIBDIR
),
569 'FRAMEWORK_SEARCH_PATHS': ['$(inherited)'] + Utils
.to_list(self
.env
.FRAMEWORKPATH
),
570 'OTHER_LDFLAGS': libs
+ ' ' + frameworks
+ ' ' + ' '.join(bld
.env
['LINKFLAGS']),
571 'OTHER_CPLUSPLUSFLAGS': Utils
.to_list(self
.env
['CXXFLAGS']),
572 'OTHER_CFLAGS': Utils
.to_list(self
.env
['CFLAGS']),
574 'GCC_PREPROCESSOR_DEFINITIONS': self
.env
['DEFINES']
578 installpaths
= Utils
.to_list(getattr(self
, 'install', []))
579 prodbuildfile
= PBXBuildFile(target
.productReference
)
580 for instpath
in installpaths
:
581 bldsettings
['INSTALL_PATH'].append(instpath
)
582 target
.add_build_phase(PBXCopyFilesBuildPhase([prodbuildfile
], instpath
))
584 if not bldsettings
['INSTALL_PATH']:
585 del bldsettings
['INSTALL_PATH']
587 # Create build settings which can override the project settings. Defaults to none if user
588 # did not pass argument. This will be filled up with target specific
589 # search paths, libs to link etc.
590 settings
= getattr(self
, 'settings', {})
592 # The keys represents different build configuration, e.g. Debug, Release and so on..
593 # Insert our generated build settings to all configuration names
594 keys
= set(settings
.keys()) |
set(bld
.env
.PROJ_CONFIGURATION
.keys())
597 settings
[k
].update(bldsettings
)
599 settings
[k
] = bldsettings
601 for k
,v
in settings
.items():
602 target
.add_configuration(XCBuildConfiguration(k
, v
))
607 class xcode(Build
.BuildContext
):
611 def as_nodes(self
, files
):
612 """ Returns a list of waflib.Nodes from a list of string of file paths """
615 if not isinstance(x
, str):
618 d
= self
.srcnode
.find_node(x
)
620 raise Errors
.WafError('File \'%s\' was not found' % x
)
624 def create_group(self
, name
, files
):
626 Returns a new PBXGroup containing the files (paths) passed in the files arg
629 group
= PBXGroup(name
)
631 Do not use unique file reference here, since XCode seem to allow only one file reference
632 to be referenced by a group.
635 for d
in self
.as_nodes(Utils
.to_list(files
)):
636 fileref
= PBXFileReference(d
.name
, d
.abspath())
637 files_
.append(fileref
)
641 def unique_buildfile(self
, buildfile
):
643 Returns a unique buildfile, possibly an existing one.
644 Use this after you've constructed a PBXBuildFile to make sure there is
645 only one PBXBuildFile for the same file in the same project.
648 build_files
= self
.build_files
649 except AttributeError:
650 build_files
= self
.build_files
= {}
652 if buildfile
not in build_files
:
653 build_files
[buildfile
] = buildfile
654 return build_files
[buildfile
]
661 if not self
.all_envs
:
663 self
.recurse([self
.run_dir
])
665 appname
= getattr(Context
.g_module
, Context
.APPNAME
, os
.path
.basename(self
.srcnode
.abspath()))
667 p
= PBXProject(appname
, ('Xcode 3.2', 46), self
.env
)
669 # If we don't create a Products group, then
670 # XCode will create one, which entails that
671 # we'll start to see duplicate files in the UI
673 products_group
= PBXGroup('Products')
674 p
.mainGroup
.children
.append(products_group
)
677 self
.products_group
= products_group
679 # post all task generators
680 # the process_xcode method above will be called for each target
681 if self
.targets
and self
.targets
!= '*':
682 (self
._min
_grp
, self
._exact
_tg
) = self
.get_targets()
684 self
.current_group
= 0
685 while self
.current_group
< len(self
.groups
):
687 self
.current_group
+= 1
689 node
= self
.bldnode
.make_node('%s.xcodeproj' % appname
)
691 node
= node
.make_node('project.pbxproj')
692 with
open(node
.abspath(), 'w') as f
:
694 Logs
.pprint('GREEN', 'Wrote %r' % node
.abspath())
696 def bind_fun(tgtype
):
697 def fun(self
, *k
, **kw
):
698 tgtype
= fun
.__name
__
699 if tgtype
== 'shlib' or tgtype
== 'dylib':
700 features
= 'cxx cxxshlib'
702 elif tgtype
== 'framework':
703 features
= 'cxx cxxshlib'
705 elif tgtype
== 'program':
706 features
= 'cxx cxxprogram'
708 elif tgtype
== 'app':
709 features
= 'cxx cxxprogram'
711 elif tgtype
== 'stlib':
712 features
= 'cxx cxxstlib'
714 lst
= kw
['features'] = Utils
.to_list(kw
.get('features', []))
715 for x
in features
.split():
716 if not x
in kw
['features']:
719 kw
['target_type'] = tgtype
720 return self(*k
, **kw
)
721 fun
.__name
__ = tgtype
722 setattr(Build
.BuildContext
, tgtype
, fun
)
725 for xx
in 'app framework dylib shlib stlib program'.split():