1#! /usr/bin/env python
2# encoding: utf-8
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
7
8"""
9See playground/xcode6/ for usage examples.
10
11"""
12
13from waflib import Context, TaskGen, Build, Utils, Errors, Logs
14import os, sys
15
16# FIXME too few extensions
17XCODE_EXTS = ['.c', '.cpp', '.m', '.mm']
18
19HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
20
21MAP_EXT = {
22	'': "folder",
23	'.h' :  "sourcecode.c.h",
24
25	'.hh':  "sourcecode.cpp.h",
26	'.inl': "sourcecode.cpp.h",
27	'.hpp': "sourcecode.cpp.h",
28
29	'.c':   "sourcecode.c.c",
30
31	'.m':   "sourcecode.c.objc",
32
33	'.mm':  "sourcecode.cpp.objcpp",
34
35	'.cc':  "sourcecode.cpp.cpp",
36
37	'.cpp': "sourcecode.cpp.cpp",
38	'.C':   "sourcecode.cpp.cpp",
39	'.cxx': "sourcecode.cpp.cpp",
40	'.c++': "sourcecode.cpp.cpp",
41
42	'.l':   "sourcecode.lex", # luthor
43	'.ll':  "sourcecode.lex",
44
45	'.y':   "sourcecode.yacc",
46	'.yy':  "sourcecode.yacc",
47
48	'.plist': "text.plist.xml",
49	".nib":   "wrapper.nib",
50	".xib":   "text.xib",
51}
52
53# Used in PBXNativeTarget elements
54PRODUCT_TYPE_APPLICATION = 'com.apple.product-type.application'
55PRODUCT_TYPE_FRAMEWORK = 'com.apple.product-type.framework'
56PRODUCT_TYPE_EXECUTABLE = 'com.apple.product-type.tool'
57PRODUCT_TYPE_LIB_STATIC = 'com.apple.product-type.library.static'
58PRODUCT_TYPE_LIB_DYNAMIC = 'com.apple.product-type.library.dynamic'
59PRODUCT_TYPE_EXTENSION = 'com.apple.product-type.kernel-extension'
60PRODUCT_TYPE_IOKIT = 'com.apple.product-type.kernel-extension.iokit'
61
62# Used in PBXFileReference elements
63FILE_TYPE_APPLICATION = 'wrapper.cfbundle'
64FILE_TYPE_FRAMEWORK = 'wrapper.framework'
65FILE_TYPE_LIB_DYNAMIC = 'compiled.mach-o.dylib'
66FILE_TYPE_LIB_STATIC = 'archive.ar'
67FILE_TYPE_EXECUTABLE = 'compiled.mach-o.executable'
68
69# Tuple packs of the above
70TARGET_TYPE_FRAMEWORK = (PRODUCT_TYPE_FRAMEWORK, FILE_TYPE_FRAMEWORK, '.framework')
71TARGET_TYPE_APPLICATION = (PRODUCT_TYPE_APPLICATION, FILE_TYPE_APPLICATION, '.app')
72TARGET_TYPE_DYNAMIC_LIB = (PRODUCT_TYPE_LIB_DYNAMIC, FILE_TYPE_LIB_DYNAMIC, '.dylib')
73TARGET_TYPE_STATIC_LIB = (PRODUCT_TYPE_LIB_STATIC, FILE_TYPE_LIB_STATIC, '.a')
74TARGET_TYPE_EXECUTABLE = (PRODUCT_TYPE_EXECUTABLE, FILE_TYPE_EXECUTABLE, '')
75
76# Maps target type string to its data
77TARGET_TYPES = {
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,
83}
84
85def 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):
89			del dct[k]
90	return dct
91
92"""
93Configuration of the global project settings. Sets an environment variable 'PROJ_CONFIGURATION'
94which is a dictionary of configuration name and buildsettings pair.
95E.g.:
96env.PROJ_CONFIGURATION = {
97	'Debug': {
98		'ARCHS': 'x86',
99		...
100	}
101	'Release': {
102		'ARCHS' x86_64'
103		...
104	}
105}
106The user can define a completely customized dictionary in configure() stage. Otherwise a default Debug/Release will be created
107based on env variable
108"""
109def configure(self):
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")
112
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]
116
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 = {
121			"Debug": defaults,
122			"Release": defaults,
123		}
124
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
132
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.")
136
137part1 = 0
138part2 = 10000
139part3 = 0
140id = 562000999
141def newid():
142	global id
143	id += 1
144	return "%04X%04X%04X%012d" % (0, 10000, 0, id)
145
146"""
147Represents a tree node in the XCode project plist file format.
148When written to a file, all attributes of XCodeNode are stringified together with
149its value. However, attributes starting with an underscore _ are ignored
150during that process and allows you to store arbitrary values that are not supposed
151to be written out.
152"""
153class XCodeNode(object):
154	def __init__(self):
155		self._id = newid()
156		self._been_written = False
157
158	def tostring(self, value):
159		if isinstance(value, dict):
160			result = "{\n"
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}"
164			return result
165		elif isinstance(value, str):
166			return "\"%s\"" % value
167		elif isinstance(value, list):
168			result = "(\n"
169			for i in value:
170				result = result + "\t\t\t%s,\n" % self.tostring(i)
171			result = result + "\t\t)"
172			return result
173		elif isinstance(value, XCodeNode):
174			return value._id
175		else:
176			return str(value)
177
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):
183			for i in value:
184				self.write_recursive(i, file)
185		elif isinstance(value, XCodeNode):
186			value.write(file)
187
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)
194			w = file.write
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)))
200			w("\t};\n\n")
201
202# Configurations
203class XCBuildConfiguration(XCodeNode):
204	def __init__(self, name, settings = {}, env=None):
205		XCodeNode.__init__(self)
206		self.baseConfigurationReference = ""
207		self.buildSettings = settings
208		self.name = name
209		if env and env.ARCH:
210			settings['ARCHS'] = " ".join(env.ARCH)
211
212
213class 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 ""
220
221# Group/Files
222class PBXFileReference(XCodeNode):
223	def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT"):
224
225		XCodeNode.__init__(self)
226		self.fileEncoding = 4
227		if not filetype:
228			_, ext = os.path.splitext(name)
229			filetype = MAP_EXT.get(ext, 'text')
230		self.lastKnownFileType = filetype
231		self.explicitFileType = filetype
232		self.name = name
233		self.path = path
234		self.sourceTree = sourcetree
235
236	def __hash__(self):
237		return (self.path+self.name).__hash__()
238
239	def __eq__(self, other):
240		return (self.path, self.name) == (other.path, other.name)
241
242class 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)
246
247		# fileRef is a reference to a PBXFileReference object
248		self.fileRef = fileRef
249
250		# A map of key/value pairs for additional settings.
251		self.settings = settings
252
253	def __hash__(self):
254		return (self.fileRef).__hash__()
255
256	def __eq__(self, other):
257		return self.fileRef == other.fileRef
258
259class PBXGroup(XCodeNode):
260	def __init__(self, name, sourcetree = 'SOURCE_TREE'):
261		XCodeNode.__init__(self)
262		self.children = []
263		self.name = name
264		self.sourceTree = sourcetree
265
266		# Maintain a lookup table for all PBXFileReferences
267		# that are contained in this group.
268		self._filerefs = {}
269
270	def add(self, sources):
271		"""
272		Add a list of PBXFileReferences to this group
273
274		:param sources: list of PBXFileReferences objects
275		"""
276		self._filerefs.update(dict(zip(sources, sources)))
277		self.children.extend(sources)
278
279	def get_sub_groups(self):
280		"""
281		Returns all child PBXGroup objects contained in this group
282		"""
283		return list(filter(lambda x: isinstance(x, PBXGroup), self.children))
284
285	def find_fileref(self, fileref):
286		"""
287		Recursively search this group for an existing PBXFileReference. Returns None
288		if none were found.
289
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'
292		stops working.
293		"""
294		if fileref in self._filerefs:
295			return self._filerefs[fileref]
296		elif self.children:
297			for childgroup in self.get_sub_groups():
298				f = childgroup.find_fileref(fileref)
299				if f:
300					return f
301		return None
302
303class 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
311
312class 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
318
319class 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)
326
327class 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)
334
335class PBXCopyFilesBuildPhase(XCodeNode):
336	"""
337	Represents the PBXCopyFilesBuildPhase section. PBXBuildFile
338	can be added to this node to copy files after build is done.
339	"""
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
345
346class 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
351
352class PBXLegacyTarget(XCodeNode):
353	def __init__(self, action, target=''):
354		XCodeNode.__init__(self)
355		self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})])
356		if not target:
357			self.buildArgumentsString = "%s %s" % (sys.argv[0], action)
358		else:
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
367
368class PBXShellScriptBuildPhase(XCodeNode):
369	def __init__(self, action, target):
370		XCodeNode.__init__(self)
371		self.buildActionMask = 2147483647
372		self.files = []
373		self.inputPaths = []
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)
378
379class 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]
385
386		self.buildConfigurationList = XCConfigurationList(configlist)
387		self.buildPhases = buildphases
388		self.buildRules = []
389		self.dependencies = []
390		self.name = target
391		self.productName = target
392		self.productType = product_type # See TARGET_TYPE_ tuples constants
393		self.productReference = PBXFileReference(node.name, node.abspath(), file_type, '')
394
395	def add_configuration(self, cf):
396		""" :type cf: XCBuildConfiguration """
397		self.buildConfigurationList.buildConfigurations.append(cf)
398
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)
406					return
407		self.buildPhases.append(phase)
408
409	def add_dependency(self, depnd):
410		self.dependencies.append(depnd)
411
412# Root project object
413class PBXProject(XCodeNode):
414	def __init__(self, name, version, env):
415		XCodeNode.__init__(self)
416
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() ?")
419
420		# Retrieve project configuration
421		configurations = []
422		for config_name, settings in env.PROJ_CONFIGURATION.items():
423			cf = XCBuildConfiguration(config_name, settings)
424			configurations.append(cf)
425
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 = ""
432		self.targets = []
433		self._objectVersion = version[1]
434
435	def create_target_dependency(self, target, name):
436		""" : param target : PXBNativeTarget """
437		proxy = PBXContainerItemProxy(self, target, name)
438		dependency = PBXTargetDependency(target, proxy)
439		return dependency
440
441	def write(self, file):
442
443		# Make sure this is written only once
444		if self._been_written:
445			return
446
447		w = file.write
448		w("// !$*UTF8*$!\n")
449		w("{\n")
450		w("\tarchiveVersion = 1;\n")
451		w("\tclasses = {\n")
452		w("\t};\n")
453		w("\tobjectVersion = %d;\n" % self._objectVersion)
454		w("\tobjects = {\n\n")
455
456		XCodeNode.write(self, file)
457
458		w("\t};\n")
459		w("\trootObject = %s;\n" % self._id)
460		w("}\n")
461
462	def add_target(self, target):
463		self.targets.append(target)
464
465	def get_target(self, name):
466		""" Get a reference to PBXNativeTarget if it exists """
467		for t in self.targets:
468			if t.name == name:
469				return t
470		return None
471
472@TaskGen.feature('c', 'cxx')
473@TaskGen.after('propagate_uselib_vars', 'apply_incpaths')
474def process_xcode(self):
475	bld = self.bld
476	try:
477		p = bld.project
478	except AttributeError:
479		return
480
481	if not hasattr(self, 'target_type'):
482		return
483
484	products_group = bld.products_group
485
486	target_group = PBXGroup(self.name)
487	p.mainGroup.children.append(target_group)
488
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))
493	else:
494		target_type = TARGET_TYPES[target_type]
495	file_ext = target_type[2]
496
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, [], [])
500
501	products_group.children.append(target.productReference)
502
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)
512	else:
513		group = bld.create_group('Source', sources)
514		target_group.children.append(group)
515
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)
522		if existing_fileref:
523			sources[idx] = existing_fileref
524		else:
525			sources[idx] = fileref
526
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))
531
532	buildfiles = [bld.unique_buildfile(PBXBuildFile(x)) for x in sources]
533	target.add_build_phase(PBXSourcesBuildPhase(buildfiles))
534
535	# Check if any framework to link against is some other target we've made
536	libs = getattr(self, 'tmp_use_seen', [])
537	for lib in libs:
538		use_target = p.get_target(lib)
539		if use_target:
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)
543
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))
548
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)
557
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])
561
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)
564
565	# Override target specific build settings
566	bldsettings = {
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,
571		'OTHER_LIBTOOLFLAGS': bld.env['LINKFLAGS'],
572		'OTHER_CPLUSPLUSFLAGS': Utils.to_list(self.env['CXXFLAGS']),
573		'OTHER_CFLAGS': Utils.to_list(self.env['CFLAGS']),
574		'INSTALL_PATH': []
575	}
576
577	# Install path
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))
583
584	if not bldsettings['INSTALL_PATH']:
585		del bldsettings['INSTALL_PATH']
586
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', {})
591
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() + bld.env.PROJ_CONFIGURATION.keys())
595	for k in keys:
596		if k in settings:
597			settings[k].update(bldsettings)
598		else:
599			settings[k] = bldsettings
600
601	for k,v in settings.items():
602		target.add_configuration(XCBuildConfiguration(k, v))
603
604	p.add_target(target)
605
606
607class xcode(Build.BuildContext):
608	cmd = 'xcode6'
609	fun = 'build'
610
611	def as_nodes(self, files):
612		""" Returns a list of waflib.Nodes from a list of string of file paths """
613		nodes = []
614		for x in files:
615			if not isinstance(x, str):
616				d = x
617			else:
618				d = self.srcnode.find_node(x)
619				if not d:
620					raise Errors.WafError('File \'%s\' was not found' % x)
621			nodes.append(d)
622		return nodes
623
624	def create_group(self, name, files):
625		"""
626		Returns a new PBXGroup containing the files (paths) passed in the files arg
627		:type files: string
628		"""
629		group = PBXGroup(name)
630		"""
631		Do not use unique file reference here, since XCode seem to allow only one file reference
632		to be referenced by a group.
633		"""
634		files_ = []
635		for d in self.as_nodes(Utils.to_list(files)):
636			fileref = PBXFileReference(d.name, d.abspath())
637			files_.append(fileref)
638		group.add(files_)
639		return group
640
641	def unique_buildfile(self, buildfile):
642		"""
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.
646		"""
647		try:
648			build_files = self.build_files
649		except AttributeError:
650			build_files = self.build_files = {}
651
652		if buildfile not in build_files:
653			build_files[buildfile] = buildfile
654		return build_files[buildfile]
655
656	def execute(self):
657		"""
658		Entry point
659		"""
660		self.restore()
661		if not self.all_envs:
662			self.load_envs()
663		self.recurse([self.run_dir])
664
665		appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath()))
666
667		p = PBXProject(appname, ('Xcode 3.2', 46), self.env)
668
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
672		# for some reason.
673		products_group = PBXGroup('Products')
674		p.mainGroup.children.append(products_group)
675
676		self.project = p
677		self.products_group = products_group
678
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()
683
684		self.current_group = 0
685		while self.current_group < len(self.groups):
686			self.post_group()
687			self.current_group += 1
688
689		node = self.bldnode.make_node('%s.xcodeproj' % appname)
690		node.mkdir()
691		node = node.make_node('project.pbxproj')
692		with open(node.abspath(), 'w') as f:
693			p.write(f)
694		Logs.pprint('GREEN', 'Wrote %r' % node.abspath())
695
696def bind_fun(tgtype):
697	def fun(self, *k, **kw):
698		tgtype = fun.__name__
699		if tgtype == 'shlib' or tgtype == 'dylib':
700			features = 'cxx cxxshlib'
701			tgtype = 'dylib'
702		elif tgtype == 'framework':
703			features = 'cxx cxxshlib'
704			tgtype = 'framework'
705		elif tgtype == 'program':
706			features = 'cxx cxxprogram'
707			tgtype = 'exe'
708		elif tgtype == 'app':
709			features = 'cxx cxxprogram'
710			tgtype = 'app'
711		elif tgtype == 'stlib':
712			features = 'cxx cxxstlib'
713			tgtype = 'stlib'
714		lst = kw['features'] = Utils.to_list(kw.get('features', []))
715		for x in features.split():
716			if not x in kw['features']:
717				lst.append(x)
718
719		kw['target_type'] = tgtype
720		return self(*k, **kw)
721	fun.__name__ = tgtype
722	setattr(Build.BuildContext, tgtype, fun)
723	return fun
724
725for xx in 'app framework dylib shlib stlib program'.split():
726	bind_fun(xx)
727
728