xcode6.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  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. See playground/xcode6/ for usage examples.
  9. """
  10. from waflib import Context, TaskGen, Build, Utils, Errors, Logs
  11. import os, sys
  12. # FIXME too few extensions
  13. XCODE_EXTS = ['.c', '.cpp', '.m', '.mm']
  14. HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
  15. MAP_EXT = {
  16. '': "folder",
  17. '.h' : "sourcecode.c.h",
  18. '.hh': "sourcecode.cpp.h",
  19. '.inl': "sourcecode.cpp.h",
  20. '.hpp': "sourcecode.cpp.h",
  21. '.c': "sourcecode.c.c",
  22. '.m': "sourcecode.c.objc",
  23. '.mm': "sourcecode.cpp.objcpp",
  24. '.cc': "sourcecode.cpp.cpp",
  25. '.cpp': "sourcecode.cpp.cpp",
  26. '.C': "sourcecode.cpp.cpp",
  27. '.cxx': "sourcecode.cpp.cpp",
  28. '.c++': "sourcecode.cpp.cpp",
  29. '.l': "sourcecode.lex", # luthor
  30. '.ll': "sourcecode.lex",
  31. '.y': "sourcecode.yacc",
  32. '.yy': "sourcecode.yacc",
  33. '.plist': "text.plist.xml",
  34. ".nib": "wrapper.nib",
  35. ".xib": "text.xib",
  36. }
  37. # Used in PBXNativeTarget elements
  38. PRODUCT_TYPE_APPLICATION = 'com.apple.product-type.application'
  39. PRODUCT_TYPE_FRAMEWORK = 'com.apple.product-type.framework'
  40. PRODUCT_TYPE_EXECUTABLE = 'com.apple.product-type.tool'
  41. PRODUCT_TYPE_LIB_STATIC = 'com.apple.product-type.library.static'
  42. PRODUCT_TYPE_LIB_DYNAMIC = 'com.apple.product-type.library.dynamic'
  43. PRODUCT_TYPE_EXTENSION = 'com.apple.product-type.kernel-extension'
  44. PRODUCT_TYPE_IOKIT = 'com.apple.product-type.kernel-extension.iokit'
  45. # Used in PBXFileReference elements
  46. FILE_TYPE_APPLICATION = 'wrapper.cfbundle'
  47. FILE_TYPE_FRAMEWORK = 'wrapper.framework'
  48. FILE_TYPE_LIB_DYNAMIC = 'compiled.mach-o.dylib'
  49. FILE_TYPE_LIB_STATIC = 'archive.ar'
  50. FILE_TYPE_EXECUTABLE = 'compiled.mach-o.executable'
  51. # Tuple packs of the above
  52. TARGET_TYPE_FRAMEWORK = (PRODUCT_TYPE_FRAMEWORK, FILE_TYPE_FRAMEWORK, '.framework')
  53. TARGET_TYPE_APPLICATION = (PRODUCT_TYPE_APPLICATION, FILE_TYPE_APPLICATION, '.app')
  54. TARGET_TYPE_DYNAMIC_LIB = (PRODUCT_TYPE_LIB_DYNAMIC, FILE_TYPE_LIB_DYNAMIC, '.dylib')
  55. TARGET_TYPE_STATIC_LIB = (PRODUCT_TYPE_LIB_STATIC, FILE_TYPE_LIB_STATIC, '.a')
  56. TARGET_TYPE_EXECUTABLE = (PRODUCT_TYPE_EXECUTABLE, FILE_TYPE_EXECUTABLE, '')
  57. # Maps target type string to its data
  58. TARGET_TYPES = {
  59. 'framework': TARGET_TYPE_FRAMEWORK,
  60. 'app': TARGET_TYPE_APPLICATION,
  61. 'dylib': TARGET_TYPE_DYNAMIC_LIB,
  62. 'stlib': TARGET_TYPE_STATIC_LIB,
  63. 'exe' :TARGET_TYPE_EXECUTABLE,
  64. }
  65. def delete_invalid_values(dct):
  66. """ Deletes entries that are dictionaries or sets """
  67. for k, v in list(dct.items()):
  68. if isinstance(v, dict) or isinstance(v, set):
  69. del dct[k]
  70. return dct
  71. """
  72. Configuration of the global project settings. Sets an environment variable 'PROJ_CONFIGURATION'
  73. which is a dictionary of configuration name and buildsettings pair.
  74. E.g.:
  75. env.PROJ_CONFIGURATION = {
  76. 'Debug': {
  77. 'ARCHS': 'x86',
  78. ...
  79. }
  80. 'Release': {
  81. 'ARCHS' x86_64'
  82. ...
  83. }
  84. }
  85. The user can define a completely customized dictionary in configure() stage. Otherwise a default Debug/Release will be created
  86. based on env variable
  87. """
  88. def configure(self):
  89. if not self.env.PROJ_CONFIGURATION:
  90. 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")
  91. # Check for any added config files added by the tool 'c_config'.
  92. if 'cfg_files' in self.env:
  93. self.env.INCLUDES = Utils.to_list(self.env.INCLUDES) + [os.path.abspath(os.path.dirname(f)) for f in self.env.cfg_files]
  94. # Create default project configuration?
  95. if 'PROJ_CONFIGURATION' not in self.env:
  96. defaults = delete_invalid_values(self.env.get_merged_dict())
  97. self.env.PROJ_CONFIGURATION = {
  98. "Debug": defaults,
  99. "Release": defaults,
  100. }
  101. # Some build settings are required to be present by XCode. We will supply default values
  102. # if user hasn't defined any.
  103. defaults_required = [('PRODUCT_NAME', '$(TARGET_NAME)')]
  104. for cfgname,settings in self.env.PROJ_CONFIGURATION.items():
  105. for default_var, default_val in defaults_required:
  106. if default_var not in settings:
  107. settings[default_var] = default_val
  108. # Error check customization
  109. if not isinstance(self.env.PROJ_CONFIGURATION, dict):
  110. 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.")
  111. part1 = 0
  112. part2 = 10000
  113. part3 = 0
  114. id = 562000999
  115. def newid():
  116. global id
  117. id += 1
  118. return "%04X%04X%04X%012d" % (0, 10000, 0, id)
  119. """
  120. Represents a tree node in the XCode project plist file format.
  121. When written to a file, all attributes of XCodeNode are stringified together with
  122. its value. However, attributes starting with an underscore _ are ignored
  123. during that process and allows you to store arbitray values that are not supposed
  124. to be written out.
  125. """
  126. class XCodeNode(object):
  127. def __init__(self):
  128. self._id = newid()
  129. self._been_written = False
  130. def tostring(self, value):
  131. if isinstance(value, dict):
  132. result = "{\n"
  133. for k,v in value.items():
  134. result = result + "\t\t\t%s = %s;\n" % (k, self.tostring(v))
  135. result = result + "\t\t}"
  136. return result
  137. elif isinstance(value, str):
  138. return "\"%s\"" % value
  139. elif isinstance(value, list):
  140. result = "(\n"
  141. for i in value:
  142. result = result + "\t\t\t%s,\n" % self.tostring(i)
  143. result = result + "\t\t)"
  144. return result
  145. elif isinstance(value, XCodeNode):
  146. return value._id
  147. else:
  148. return str(value)
  149. def write_recursive(self, value, file):
  150. if isinstance(value, dict):
  151. for k,v in value.items():
  152. self.write_recursive(v, file)
  153. elif isinstance(value, list):
  154. for i in value:
  155. self.write_recursive(i, file)
  156. elif isinstance(value, XCodeNode):
  157. value.write(file)
  158. def write(self, file):
  159. if not self._been_written:
  160. self._been_written = True
  161. for attribute,value in self.__dict__.items():
  162. if attribute[0] != '_':
  163. self.write_recursive(value, file)
  164. w = file.write
  165. w("\t%s = {\n" % self._id)
  166. w("\t\tisa = %s;\n" % self.__class__.__name__)
  167. for attribute,value in self.__dict__.items():
  168. if attribute[0] != '_':
  169. w("\t\t%s = %s;\n" % (attribute, self.tostring(value)))
  170. w("\t};\n\n")
  171. # Configurations
  172. class XCBuildConfiguration(XCodeNode):
  173. def __init__(self, name, settings = {}, env=None):
  174. XCodeNode.__init__(self)
  175. self.baseConfigurationReference = ""
  176. self.buildSettings = settings
  177. self.name = name
  178. if env and env.ARCH:
  179. settings['ARCHS'] = " ".join(env.ARCH)
  180. class XCConfigurationList(XCodeNode):
  181. def __init__(self, configlst):
  182. """ :param configlst: list of XCConfigurationList """
  183. XCodeNode.__init__(self)
  184. self.buildConfigurations = configlst
  185. self.defaultConfigurationIsVisible = 0
  186. self.defaultConfigurationName = configlst and configlst[0].name or ""
  187. # Group/Files
  188. class PBXFileReference(XCodeNode):
  189. def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT"):
  190. XCodeNode.__init__(self)
  191. self.fileEncoding = 4
  192. if not filetype:
  193. _, ext = os.path.splitext(name)
  194. filetype = MAP_EXT.get(ext, 'text')
  195. self.lastKnownFileType = filetype
  196. self.explicitFileType = filetype
  197. self.name = name
  198. self.path = path
  199. self.sourceTree = sourcetree
  200. def __hash__(self):
  201. return (self.path+self.name).__hash__()
  202. def __eq__(self, other):
  203. return (self.path, self.name) == (other.path, other.name)
  204. class PBXBuildFile(XCodeNode):
  205. """ This element indicate a file reference that is used in a PBXBuildPhase (either as an include or resource). """
  206. def __init__(self, fileRef, settings={}):
  207. XCodeNode.__init__(self)
  208. # fileRef is a reference to a PBXFileReference object
  209. self.fileRef = fileRef
  210. # A map of key/value pairs for additionnal settings.
  211. self.settings = settings
  212. def __hash__(self):
  213. return (self.fileRef).__hash__()
  214. def __eq__(self, other):
  215. return self.fileRef == other.fileRef
  216. class PBXGroup(XCodeNode):
  217. def __init__(self, name, sourcetree = 'SOURCE_TREE'):
  218. XCodeNode.__init__(self)
  219. self.children = []
  220. self.name = name
  221. self.sourceTree = sourcetree
  222. # Maintain a lookup table for all PBXFileReferences
  223. # that are contained in this group.
  224. self._filerefs = {}
  225. def add(self, sources):
  226. """
  227. Add a list of PBXFileReferences to this group
  228. :param sources: list of PBXFileReferences objects
  229. """
  230. self._filerefs.update(dict(zip(sources, sources)))
  231. self.children.extend(sources)
  232. def get_sub_groups(self):
  233. """
  234. Returns all child PBXGroup objects contained in this group
  235. """
  236. return list(filter(lambda x: isinstance(x, PBXGroup), self.children))
  237. def find_fileref(self, fileref):
  238. """
  239. Recursively search this group for an existing PBXFileReference. Returns None
  240. if none were found.
  241. 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.
  242. If it isn't, the consequence is that certain UI features like 'Reveal in Finder'
  243. stops working.
  244. """
  245. if fileref in self._filerefs:
  246. return self._filerefs[fileref]
  247. elif self.children:
  248. for childgroup in self.get_sub_groups():
  249. f = childgroup.find_fileref(fileref)
  250. if f:
  251. return f
  252. return None
  253. class PBXContainerItemProxy(XCodeNode):
  254. """ This is the element for to decorate a target item. """
  255. def __init__(self, containerPortal, remoteGlobalIDString, remoteInfo='', proxyType=1):
  256. XCodeNode.__init__(self)
  257. self.containerPortal = containerPortal # PBXProject
  258. self.remoteGlobalIDString = remoteGlobalIDString # PBXNativeTarget
  259. self.remoteInfo = remoteInfo # Target name
  260. self.proxyType = proxyType
  261. class PBXTargetDependency(XCodeNode):
  262. """ This is the element for referencing other target through content proxies. """
  263. def __init__(self, native_target, proxy):
  264. XCodeNode.__init__(self)
  265. self.target = native_target
  266. self.targetProxy = proxy
  267. class PBXFrameworksBuildPhase(XCodeNode):
  268. """ This is the element for the framework link build phase, i.e. linking to frameworks """
  269. def __init__(self, pbxbuildfiles):
  270. XCodeNode.__init__(self)
  271. self.buildActionMask = 2147483647
  272. self.runOnlyForDeploymentPostprocessing = 0
  273. self.files = pbxbuildfiles #List of PBXBuildFile (.o, .framework, .dylib)
  274. class PBXHeadersBuildPhase(XCodeNode):
  275. """ This is the element for adding header files to be packaged into the .framework """
  276. def __init__(self, pbxbuildfiles):
  277. XCodeNode.__init__(self)
  278. self.buildActionMask = 2147483647
  279. self.runOnlyForDeploymentPostprocessing = 0
  280. self.files = pbxbuildfiles #List of PBXBuildFile (.o, .framework, .dylib)
  281. class PBXCopyFilesBuildPhase(XCodeNode):
  282. """
  283. Represents the PBXCopyFilesBuildPhase section. PBXBuildFile
  284. can be added to this node to copy files after build is done.
  285. """
  286. def __init__(self, pbxbuildfiles, dstpath, dstSubpathSpec=0, *args, **kwargs):
  287. XCodeNode.__init__(self)
  288. self.files = pbxbuildfiles
  289. self.dstPath = dstpath
  290. self.dstSubfolderSpec = dstSubpathSpec
  291. class PBXSourcesBuildPhase(XCodeNode):
  292. """ Represents the 'Compile Sources' build phase in a Xcode target """
  293. def __init__(self, buildfiles):
  294. XCodeNode.__init__(self)
  295. self.files = buildfiles # List of PBXBuildFile objects
  296. class PBXLegacyTarget(XCodeNode):
  297. def __init__(self, action, target=''):
  298. XCodeNode.__init__(self)
  299. self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})])
  300. if not target:
  301. self.buildArgumentsString = "%s %s" % (sys.argv[0], action)
  302. else:
  303. self.buildArgumentsString = "%s %s --targets=%s" % (sys.argv[0], action, target)
  304. self.buildPhases = []
  305. self.buildToolPath = sys.executable
  306. self.buildWorkingDirectory = ""
  307. self.dependencies = []
  308. self.name = target or action
  309. self.productName = target or action
  310. self.passBuildSettingsInEnvironment = 0
  311. class PBXShellScriptBuildPhase(XCodeNode):
  312. def __init__(self, action, target):
  313. XCodeNode.__init__(self)
  314. self.buildActionMask = 2147483647
  315. self.files = []
  316. self.inputPaths = []
  317. self.outputPaths = []
  318. self.runOnlyForDeploymentPostProcessing = 0
  319. self.shellPath = "/bin/sh"
  320. self.shellScript = "%s %s %s --targets=%s" % (sys.executable, sys.argv[0], action, target)
  321. class PBXNativeTarget(XCodeNode):
  322. """ Represents a target in XCode, e.g. App, DyLib, Framework etc. """
  323. def __init__(self, target, node, target_type=TARGET_TYPE_APPLICATION, configlist=[], buildphases=[]):
  324. XCodeNode.__init__(self)
  325. product_type = target_type[0]
  326. file_type = target_type[1]
  327. self.buildConfigurationList = XCConfigurationList(configlist)
  328. self.buildPhases = buildphases
  329. self.buildRules = []
  330. self.dependencies = []
  331. self.name = target
  332. self.productName = target
  333. self.productType = product_type # See TARGET_TYPE_ tuples constants
  334. self.productReference = PBXFileReference(node.name, node.abspath(), file_type, '')
  335. def add_configuration(self, cf):
  336. """ :type cf: XCBuildConfiguration """
  337. self.buildConfigurationList.buildConfigurations.append(cf)
  338. def add_build_phase(self, phase):
  339. # Some build phase types may appear only once. If a phase type already exists, then merge them.
  340. if ( (phase.__class__ == PBXFrameworksBuildPhase)
  341. or (phase.__class__ == PBXSourcesBuildPhase) ):
  342. for b in self.buildPhases:
  343. if b.__class__ == phase.__class__:
  344. b.files.extend(phase.files)
  345. return
  346. self.buildPhases.append(phase)
  347. def add_dependency(self, depnd):
  348. self.dependencies.append(depnd)
  349. # Root project object
  350. class PBXProject(XCodeNode):
  351. def __init__(self, name, version, env):
  352. XCodeNode.__init__(self)
  353. if not isinstance(env.PROJ_CONFIGURATION, dict):
  354. 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() ?")
  355. # Retrieve project configuration
  356. configurations = []
  357. for config_name, settings in env.PROJ_CONFIGURATION.items():
  358. cf = XCBuildConfiguration(config_name, settings)
  359. configurations.append(cf)
  360. self.buildConfigurationList = XCConfigurationList(configurations)
  361. self.compatibilityVersion = version[0]
  362. self.hasScannedForEncodings = 1
  363. self.mainGroup = PBXGroup(name)
  364. self.projectRoot = ""
  365. self.projectDirPath = ""
  366. self.targets = []
  367. self._objectVersion = version[1]
  368. def create_target_dependency(self, target, name):
  369. """ : param target : PXBNativeTarget """
  370. proxy = PBXContainerItemProxy(self, target, name)
  371. dependecy = PBXTargetDependency(target, proxy)
  372. return dependecy
  373. def write(self, file):
  374. # Make sure this is written only once
  375. if self._been_written:
  376. return
  377. w = file.write
  378. w("// !$*UTF8*$!\n")
  379. w("{\n")
  380. w("\tarchiveVersion = 1;\n")
  381. w("\tclasses = {\n")
  382. w("\t};\n")
  383. w("\tobjectVersion = %d;\n" % self._objectVersion)
  384. w("\tobjects = {\n\n")
  385. XCodeNode.write(self, file)
  386. w("\t};\n")
  387. w("\trootObject = %s;\n" % self._id)
  388. w("}\n")
  389. def add_target(self, target):
  390. self.targets.append(target)
  391. def get_target(self, name):
  392. """ Get a reference to PBXNativeTarget if it exists """
  393. for t in self.targets:
  394. if t.name == name:
  395. return t
  396. return None
  397. @TaskGen.feature('c', 'cxx')
  398. @TaskGen.after('propagate_uselib_vars', 'apply_incpaths')
  399. def process_xcode(self):
  400. bld = self.bld
  401. try:
  402. p = bld.project
  403. except AttributeError:
  404. return
  405. if not hasattr(self, 'target_type'):
  406. return
  407. products_group = bld.products_group
  408. target_group = PBXGroup(self.name)
  409. p.mainGroup.children.append(target_group)
  410. # Determine what type to build - framework, app bundle etc.
  411. target_type = getattr(self, 'target_type', 'app')
  412. if target_type not in TARGET_TYPES:
  413. raise Errors.WafError("Target type '%s' does not exists. Available options are '%s'. In target '%s'" % (target_type, "', '".join(TARGET_TYPES.keys()), self.name))
  414. else:
  415. target_type = TARGET_TYPES[target_type]
  416. file_ext = target_type[2]
  417. # Create the output node
  418. target_node = self.path.find_or_declare(self.name+file_ext)
  419. target = PBXNativeTarget(self.name, target_node, target_type, [], [])
  420. products_group.children.append(target.productReference)
  421. # Pull source files from the 'source' attribute and assign them to a UI group.
  422. # Use a default UI group named 'Source' unless the user
  423. # provides a 'group_files' dictionary to customize the UI grouping.
  424. sources = getattr(self, 'source', [])
  425. if hasattr(self, 'group_files'):
  426. group_files = getattr(self, 'group_files', [])
  427. for grpname,files in group_files.items():
  428. group = bld.create_group(grpname, files)
  429. target_group.children.append(group)
  430. else:
  431. group = bld.create_group('Source', sources)
  432. target_group.children.append(group)
  433. # Create a PBXFileReference for each source file.
  434. # If the source file already exists as a PBXFileReference in any of the UI groups, then
  435. # reuse that PBXFileReference object (XCode does not like it if we don't reuse)
  436. for idx, path in enumerate(sources):
  437. fileref = PBXFileReference(path.name, path.abspath())
  438. existing_fileref = target_group.find_fileref(fileref)
  439. if existing_fileref:
  440. sources[idx] = existing_fileref
  441. else:
  442. sources[idx] = fileref
  443. # If the 'source' attribute contains any file extension that XCode can't work with,
  444. # then remove it. The allowed file extensions are defined in XCODE_EXTS.
  445. is_valid_file_extension = lambda file: os.path.splitext(file.path)[1] in XCODE_EXTS
  446. sources = list(filter(is_valid_file_extension, sources))
  447. buildfiles = [bld.unique_buildfile(PBXBuildFile(x)) for x in sources]
  448. target.add_build_phase(PBXSourcesBuildPhase(buildfiles))
  449. # Check if any framework to link against is some other target we've made
  450. libs = getattr(self, 'tmp_use_seen', [])
  451. for lib in libs:
  452. use_target = p.get_target(lib)
  453. if use_target:
  454. # Create an XCode dependency so that XCode knows to build the other target before this target
  455. dependency = p.create_target_dependency(use_target, use_target.name)
  456. target.add_dependency(dependency)
  457. buildphase = PBXFrameworksBuildPhase([PBXBuildFile(use_target.productReference)])
  458. target.add_build_phase(buildphase)
  459. if lib in self.env.LIB:
  460. self.env.LIB = list(filter(lambda x: x != lib, self.env.LIB))
  461. # If 'export_headers' is present, add files to the Headers build phase in xcode.
  462. # These are files that'll get packed into the Framework for instance.
  463. exp_hdrs = getattr(self, 'export_headers', [])
  464. hdrs = bld.as_nodes(Utils.to_list(exp_hdrs))
  465. files = [p.mainGroup.find_fileref(PBXFileReference(n.name, n.abspath())) for n in hdrs]
  466. files = [PBXBuildFile(f, {'ATTRIBUTES': ('Public',)}) for f in files]
  467. buildphase = PBXHeadersBuildPhase(files)
  468. target.add_build_phase(buildphase)
  469. # Merge frameworks and libs into one list, and prefix the frameworks
  470. frameworks = Utils.to_list(self.env.FRAMEWORK)
  471. frameworks = ' '.join(['-framework %s' % (f.split('.framework')[0]) for f in frameworks])
  472. libs = Utils.to_list(self.env.STLIB) + Utils.to_list(self.env.LIB)
  473. libs = ' '.join(bld.env['STLIB_ST'] % t for t in libs)
  474. # Override target specific build settings
  475. bldsettings = {
  476. 'HEADER_SEARCH_PATHS': ['$(inherited)'] + self.env['INCPATHS'],
  477. 'LIBRARY_SEARCH_PATHS': ['$(inherited)'] + Utils.to_list(self.env.LIBPATH) + Utils.to_list(self.env.STLIBPATH) + Utils.to_list(self.env.LIBDIR) ,
  478. 'FRAMEWORK_SEARCH_PATHS': ['$(inherited)'] + Utils.to_list(self.env.FRAMEWORKPATH),
  479. 'OTHER_LDFLAGS': libs + ' ' + frameworks,
  480. 'OTHER_LIBTOOLFLAGS': bld.env['LINKFLAGS'],
  481. 'OTHER_CPLUSPLUSFLAGS': Utils.to_list(self.env['CXXFLAGS']),
  482. 'OTHER_CFLAGS': Utils.to_list(self.env['CFLAGS']),
  483. 'INSTALL_PATH': []
  484. }
  485. # Install path
  486. installpaths = Utils.to_list(getattr(self, 'install', []))
  487. prodbuildfile = PBXBuildFile(target.productReference)
  488. for instpath in installpaths:
  489. bldsettings['INSTALL_PATH'].append(instpath)
  490. target.add_build_phase(PBXCopyFilesBuildPhase([prodbuildfile], instpath))
  491. if not bldsettings['INSTALL_PATH']:
  492. del bldsettings['INSTALL_PATH']
  493. # Create build settings which can override the project settings. Defaults to none if user
  494. # did not pass argument. This will be filled up with target specific
  495. # search paths, libs to link etc.
  496. settings = getattr(self, 'settings', {})
  497. # The keys represents different build configuration, e.g. Debug, Release and so on..
  498. # Insert our generated build settings to all configuration names
  499. keys = set(settings.keys() + bld.env.PROJ_CONFIGURATION.keys())
  500. for k in keys:
  501. if k in settings:
  502. settings[k].update(bldsettings)
  503. else:
  504. settings[k] = bldsettings
  505. for k,v in settings.items():
  506. target.add_configuration(XCBuildConfiguration(k, v))
  507. p.add_target(target)
  508. class xcode(Build.BuildContext):
  509. cmd = 'xcode6'
  510. fun = 'build'
  511. def as_nodes(self, files):
  512. """ Returns a list of waflib.Nodes from a list of string of file paths """
  513. nodes = []
  514. for x in files:
  515. if not isinstance(x, str):
  516. d = x
  517. else:
  518. d = self.srcnode.find_node(x)
  519. if not d:
  520. raise Errors.WafError('File \'%s\' was not found' % x)
  521. nodes.append(d)
  522. return nodes
  523. def create_group(self, name, files):
  524. """
  525. Returns a new PBXGroup containing the files (paths) passed in the files arg
  526. :type files: string
  527. """
  528. group = PBXGroup(name)
  529. """
  530. Do not use unique file reference here, since XCode seem to allow only one file reference
  531. to be referenced by a group.
  532. """
  533. files_ = []
  534. for d in self.as_nodes(Utils.to_list(files)):
  535. fileref = PBXFileReference(d.name, d.abspath())
  536. files_.append(fileref)
  537. group.add(files_)
  538. return group
  539. def unique_buildfile(self, buildfile):
  540. """
  541. Returns a unique buildfile, possibly an existing one.
  542. Use this after you've constructed a PBXBuildFile to make sure there is
  543. only one PBXBuildFile for the same file in the same project.
  544. """
  545. try:
  546. build_files = self.build_files
  547. except AttributeError:
  548. build_files = self.build_files = {}
  549. if buildfile not in build_files:
  550. build_files[buildfile] = buildfile
  551. return build_files[buildfile]
  552. def execute(self):
  553. """
  554. Entry point
  555. """
  556. self.restore()
  557. if not self.all_envs:
  558. self.load_envs()
  559. self.recurse([self.run_dir])
  560. appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath()))
  561. p = PBXProject(appname, ('Xcode 3.2', 46), self.env)
  562. # If we don't create a Products group, then
  563. # XCode will create one, which entails that
  564. # we'll start to see duplicate files in the UI
  565. # for some reason.
  566. products_group = PBXGroup('Products')
  567. p.mainGroup.children.append(products_group)
  568. self.project = p
  569. self.products_group = products_group
  570. # post all task generators
  571. # the process_xcode method above will be called for each target
  572. if self.targets and self.targets != '*':
  573. (self._min_grp, self._exact_tg) = self.get_targets()
  574. self.current_group = 0
  575. while self.current_group < len(self.groups):
  576. self.post_group()
  577. self.current_group += 1
  578. node = self.bldnode.make_node('%s.xcodeproj' % appname)
  579. node.mkdir()
  580. node = node.make_node('project.pbxproj')
  581. with open(node.abspath(), 'w') as f:
  582. p.write(f)
  583. Logs.pprint('GREEN', 'Wrote %r' % node.abspath())
  584. def bind_fun(tgtype):
  585. def fun(self, *k, **kw):
  586. tgtype = fun.__name__
  587. if tgtype == 'shlib' or tgtype == 'dylib':
  588. features = 'cxx cxxshlib'
  589. tgtype = 'dylib'
  590. elif tgtype == 'framework':
  591. features = 'cxx cxxshlib'
  592. tgtype = 'framework'
  593. elif tgtype == 'program':
  594. features = 'cxx cxxprogram'
  595. tgtype = 'exe'
  596. elif tgtype == 'app':
  597. features = 'cxx cxxprogram'
  598. tgtype = 'app'
  599. elif tgtype == 'stlib':
  600. features = 'cxx cxxstlib'
  601. tgtype = 'stlib'
  602. lst = kw['features'] = Utils.to_list(kw.get('features', []))
  603. for x in features.split():
  604. if not x in kw['features']:
  605. lst.append(x)
  606. kw['target_type'] = tgtype
  607. return self(*k, **kw)
  608. fun.__name__ = tgtype
  609. setattr(Build.BuildContext, tgtype, fun)
  610. return fun
  611. for xx in 'app framework dylib shlib stlib program'.split():
  612. bind_fun(xx)