jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

313 lines
8.2KB

  1. #! /usr/bin/env python
  2. # encoding: utf-8
  3. # XCode 3/XCode 4 generator for Waf
  4. # Nicolas Mercier 2011
  5. """
  6. Usage:
  7. def options(opt):
  8. opt.load('xcode')
  9. $ waf configure xcode
  10. """
  11. # TODO: support iOS projects
  12. from waflib import Context, TaskGen, Build, Utils
  13. import os, sys
  14. HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
  15. MAP_EXT = {
  16. '.h' : "sourcecode.c.h",
  17. '.hh': "sourcecode.cpp.h",
  18. '.inl': "sourcecode.cpp.h",
  19. '.hpp': "sourcecode.cpp.h",
  20. '.c': "sourcecode.c.c",
  21. '.m': "sourcecode.c.objc",
  22. '.mm': "sourcecode.cpp.objcpp",
  23. '.cc': "sourcecode.cpp.cpp",
  24. '.cpp': "sourcecode.cpp.cpp",
  25. '.C': "sourcecode.cpp.cpp",
  26. '.cxx': "sourcecode.cpp.cpp",
  27. '.c++': "sourcecode.cpp.cpp",
  28. '.l': "sourcecode.lex", # luthor
  29. '.ll': "sourcecode.lex",
  30. '.y': "sourcecode.yacc",
  31. '.yy': "sourcecode.yacc",
  32. '.plist': "text.plist.xml",
  33. ".nib": "wrapper.nib",
  34. ".xib": "text.xib",
  35. }
  36. part1 = 0
  37. part2 = 10000
  38. part3 = 0
  39. id = 562000999
  40. def newid():
  41. global id
  42. id = id + 1
  43. return "%04X%04X%04X%012d" % (0, 10000, 0, id)
  44. class XCodeNode:
  45. def __init__(self):
  46. self._id = newid()
  47. def tostring(self, value):
  48. if isinstance(value, dict):
  49. result = "{\n"
  50. for k,v in value.items():
  51. result = result + "\t\t\t%s = %s;\n" % (k, self.tostring(v))
  52. result = result + "\t\t}"
  53. return result
  54. elif isinstance(value, str):
  55. return "\"%s\"" % value
  56. elif isinstance(value, list):
  57. result = "(\n"
  58. for i in value:
  59. result = result + "\t\t\t%s,\n" % self.tostring(i)
  60. result = result + "\t\t)"
  61. return result
  62. elif isinstance(value, XCodeNode):
  63. return value._id
  64. else:
  65. return str(value)
  66. def write_recursive(self, value, file):
  67. if isinstance(value, dict):
  68. for k,v in value.items():
  69. self.write_recursive(v, file)
  70. elif isinstance(value, list):
  71. for i in value:
  72. self.write_recursive(i, file)
  73. elif isinstance(value, XCodeNode):
  74. value.write(file)
  75. def write(self, file):
  76. for attribute,value in self.__dict__.items():
  77. if attribute[0] != '_':
  78. self.write_recursive(value, file)
  79. w = file.write
  80. w("\t%s = {\n" % self._id)
  81. w("\t\tisa = %s;\n" % self.__class__.__name__)
  82. for attribute,value in self.__dict__.items():
  83. if attribute[0] != '_':
  84. w("\t\t%s = %s;\n" % (attribute, self.tostring(value)))
  85. w("\t};\n\n")
  86. # Configurations
  87. class XCBuildConfiguration(XCodeNode):
  88. def __init__(self, name, settings = {}, env=None):
  89. XCodeNode.__init__(self)
  90. self.baseConfigurationReference = ""
  91. self.buildSettings = settings
  92. self.name = name
  93. if env and env.ARCH:
  94. settings['ARCHS'] = " ".join(env.ARCH)
  95. class XCConfigurationList(XCodeNode):
  96. def __init__(self, settings):
  97. XCodeNode.__init__(self)
  98. self.buildConfigurations = settings
  99. self.defaultConfigurationIsVisible = 0
  100. self.defaultConfigurationName = settings and settings[0].name or ""
  101. # Group/Files
  102. class PBXFileReference(XCodeNode):
  103. def __init__(self, name, path, filetype = '', sourcetree = "SOURCE_ROOT"):
  104. XCodeNode.__init__(self)
  105. self.fileEncoding = 4
  106. if not filetype:
  107. _, ext = os.path.splitext(name)
  108. filetype = MAP_EXT.get(ext, 'text')
  109. self.lastKnownFileType = filetype
  110. self.name = name
  111. self.path = path
  112. self.sourceTree = sourcetree
  113. class PBXGroup(XCodeNode):
  114. def __init__(self, name, sourcetree = "<group>"):
  115. XCodeNode.__init__(self)
  116. self.children = []
  117. self.name = name
  118. self.sourceTree = sourcetree
  119. def add(self, root, sources):
  120. folders = {}
  121. def folder(n):
  122. if not n.is_child_of(root):
  123. return self
  124. try:
  125. return folders[n]
  126. except KeyError:
  127. f = PBXGroup(n.name)
  128. p = folder(n.parent)
  129. folders[n] = f
  130. p.children.append(f)
  131. return f
  132. for s in sources:
  133. f = folder(s.parent)
  134. source = PBXFileReference(s.name, s.abspath())
  135. f.children.append(source)
  136. # Targets
  137. class PBXLegacyTarget(XCodeNode):
  138. def __init__(self, action, target=''):
  139. XCodeNode.__init__(self)
  140. self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})])
  141. if not target:
  142. self.buildArgumentsString = "%s %s" % (sys.argv[0], action)
  143. else:
  144. self.buildArgumentsString = "%s %s --targets=%s" % (sys.argv[0], action, target)
  145. self.buildPhases = []
  146. self.buildToolPath = sys.executable
  147. self.buildWorkingDirectory = ""
  148. self.dependencies = []
  149. self.name = target or action
  150. self.productName = target or action
  151. self.passBuildSettingsInEnvironment = 0
  152. class PBXShellScriptBuildPhase(XCodeNode):
  153. def __init__(self, action, target):
  154. XCodeNode.__init__(self)
  155. self.buildActionMask = 2147483647
  156. self.files = []
  157. self.inputPaths = []
  158. self.outputPaths = []
  159. self.runOnlyForDeploymentPostProcessing = 0
  160. self.shellPath = "/bin/sh"
  161. self.shellScript = "%s %s %s --targets=%s" % (sys.executable, sys.argv[0], action, target)
  162. class PBXNativeTarget(XCodeNode):
  163. def __init__(self, action, target, node, env):
  164. XCodeNode.__init__(self)
  165. conf = XCBuildConfiguration('waf', {'PRODUCT_NAME':target, 'CONFIGURATION_BUILD_DIR':node.parent.abspath()}, env)
  166. self.buildConfigurationList = XCConfigurationList([conf])
  167. self.buildPhases = [PBXShellScriptBuildPhase(action, target)]
  168. self.buildRules = []
  169. self.dependencies = []
  170. self.name = target
  171. self.productName = target
  172. self.productType = "com.apple.product-type.application"
  173. self.productReference = PBXFileReference(target, node.abspath(), 'wrapper.application', 'BUILT_PRODUCTS_DIR')
  174. # Root project object
  175. class PBXProject(XCodeNode):
  176. def __init__(self, name, version):
  177. XCodeNode.__init__(self)
  178. self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})])
  179. self.compatibilityVersion = version[0]
  180. self.hasScannedForEncodings = 1
  181. self.mainGroup = PBXGroup(name)
  182. self.projectRoot = ""
  183. self.projectDirPath = ""
  184. self.targets = []
  185. self._objectVersion = version[1]
  186. self._output = PBXGroup('out')
  187. self.mainGroup.children.append(self._output)
  188. def write(self, file):
  189. w = file.write
  190. w("// !$*UTF8*$!\n")
  191. w("{\n")
  192. w("\tarchiveVersion = 1;\n")
  193. w("\tclasses = {\n")
  194. w("\t};\n")
  195. w("\tobjectVersion = %d;\n" % self._objectVersion)
  196. w("\tobjects = {\n\n")
  197. XCodeNode.write(self, file)
  198. w("\t};\n")
  199. w("\trootObject = %s;\n" % self._id)
  200. w("}\n")
  201. def add_task_gen(self, tg):
  202. if not getattr(tg, 'mac_app', False):
  203. self.targets.append(PBXLegacyTarget('build', tg.name))
  204. else:
  205. target = PBXNativeTarget('build', tg.name, tg.link_task.outputs[0].change_ext('.app'), tg.env)
  206. self.targets.append(target)
  207. self._output.children.append(target.productReference)
  208. class xcode(Build.BuildContext):
  209. cmd = 'xcode'
  210. fun = 'build'
  211. def collect_source(self, tg):
  212. source_files = tg.to_nodes(getattr(tg, 'source', []))
  213. plist_files = tg.to_nodes(getattr(tg, 'mac_plist', []))
  214. resource_files = [tg.path.find_node(i) for i in Utils.to_list(getattr(tg, 'mac_resources', []))]
  215. include_dirs = Utils.to_list(getattr(tg, 'includes', [])) + Utils.to_list(getattr(tg, 'export_dirs', []))
  216. include_files = []
  217. for x in include_dirs:
  218. if not isinstance(x, str):
  219. include_files.append(x)
  220. continue
  221. d = tg.path.find_node(x)
  222. if d:
  223. lst = [y for y in d.ant_glob(HEADERS_GLOB, flat=False)]
  224. include_files.extend(lst)
  225. # remove duplicates
  226. source = list(set(source_files + plist_files + resource_files + include_files))
  227. source.sort(key=lambda x: x.abspath())
  228. return source
  229. def execute(self):
  230. """
  231. Entry point
  232. """
  233. self.restore()
  234. if not self.all_envs:
  235. self.load_envs()
  236. self.recurse([self.run_dir])
  237. appname = getattr(Context.g_module, Context.APPNAME, os.path.basename(self.srcnode.abspath()))
  238. p = PBXProject(appname, ('Xcode 3.2', 46))
  239. for g in self.groups:
  240. for tg in g:
  241. if not isinstance(tg, TaskGen.task_gen):
  242. continue
  243. tg.post()
  244. features = Utils.to_list(getattr(tg, 'features', ''))
  245. group = PBXGroup(tg.name)
  246. group.add(tg.path, self.collect_source(tg))
  247. p.mainGroup.children.append(group)
  248. if 'cprogram' or 'cxxprogram' in features:
  249. p.add_task_gen(tg)
  250. # targets that don't produce the executable but that you might want to run
  251. p.targets.append(PBXLegacyTarget('configure'))
  252. p.targets.append(PBXLegacyTarget('dist'))
  253. p.targets.append(PBXLegacyTarget('install'))
  254. p.targets.append(PBXLegacyTarget('check'))
  255. node = self.srcnode.make_node('%s.xcodeproj' % appname)
  256. node.mkdir()
  257. node = node.make_node('project.pbxproj')
  258. p.write(open(node.abspath(), 'w'))