xcode_pebble.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. #! /usr/bin/env python
  2. # Copyright 2024 Google LLC
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. # encoding: utf-8
  16. # XCode 3/XCode 4 generator for Waf
  17. # Nicolas Mercier 2011
  18. """
  19. Usage:
  20. def options(opt):
  21. opt.load('xcode')
  22. $ waf configure xcode
  23. """
  24. # TODO: support iOS projects
  25. from waflib import Context, TaskGen, Build, Utils
  26. import os, sys, random, time
  27. HEADERS_GLOB = '**/(*.h|*.hpp|*.H|*.inl)'
  28. MAP_EXT = {
  29. '.h' : "sourcecode.c.h",
  30. '.hh': "sourcecode.cpp.h",
  31. '.inl': "sourcecode.cpp.h",
  32. '.hpp': "sourcecode.cpp.h",
  33. '.c': "sourcecode.c.c",
  34. '.m': "sourcecode.c.objc",
  35. '.mm': "sourcecode.cpp.objcpp",
  36. '.cc': "sourcecode.cpp.cpp",
  37. '.cpp': "sourcecode.cpp.cpp",
  38. '.C': "sourcecode.cpp.cpp",
  39. '.cxx': "sourcecode.cpp.cpp",
  40. '.c++': "sourcecode.cpp.cpp",
  41. '.l': "sourcecode.lex", # luthor
  42. '.ll': "sourcecode.lex",
  43. '.y': "sourcecode.yacc",
  44. '.yy': "sourcecode.yacc",
  45. '.plist': "text.plist.xml",
  46. ".nib": "wrapper.nib",
  47. ".xib": "text.xib",
  48. }
  49. SOURCE_EXT = frozenset(['.c', '.cpp', '.m', '.cxx', '.c++', '.C', '.cc', '.s', '.S'])
  50. part1 = 0
  51. part2 = 10000
  52. part3 = 0
  53. id = 562000999
  54. def newid():
  55. global id
  56. id = id + 1
  57. return "%04X%04X%04X%012d" % (0, 10000, 0, id)
  58. class XCodeNode:
  59. def __init__(self):
  60. self._id = newid()
  61. def tostring(self, value):
  62. if isinstance(value, dict):
  63. result = "{\n"
  64. for k,v in value.items():
  65. result = result + "\t\t\t%s = %s;\n" % (k, self.tostring(v))
  66. result = result + "\t\t}"
  67. return result
  68. elif isinstance(value, str):
  69. return "\"%s\"" % value
  70. elif isinstance(value, list):
  71. result = "(\n"
  72. for i in value:
  73. result = result + "\t\t\t%s,\n" % self.tostring(i)
  74. result = result + "\t\t)"
  75. return result
  76. elif isinstance(value, XCodeNode):
  77. return value._id
  78. else:
  79. return str(value)
  80. def write_recursive(self, value, file):
  81. if isinstance(value, dict):
  82. for k,v in value.items():
  83. self.write_recursive(v, file)
  84. elif isinstance(value, list):
  85. for i in value:
  86. self.write_recursive(i, file)
  87. elif isinstance(value, XCodeNode):
  88. value.write(file)
  89. def write(self, file):
  90. for attribute,value in self.__dict__.items():
  91. if attribute[0] != '_':
  92. self.write_recursive(value, file)
  93. w = file.write
  94. w("\t%s = {\n" % self._id)
  95. w("\t\tisa = %s;\n" % self.__class__.__name__)
  96. for attribute,value in self.__dict__.items():
  97. if attribute[0] != '_':
  98. w("\t\t%s = %s;\n" % (attribute, self.tostring(value)))
  99. w("\t};\n\n")
  100. # Configurations
  101. class XCBuildConfiguration(XCodeNode):
  102. def __init__(self, name, settings = {}, env=None):
  103. XCodeNode.__init__(self)
  104. self.baseConfigurationReference = ""
  105. self.buildSettings = settings
  106. self.name = name
  107. if env and env.ARCH:
  108. settings['ARCHS'] = " ".join(env.ARCH)
  109. settings['COMBINE_HIDPI_IMAGES'] = 'YES'
  110. settings['ONLY_ACTIVE_ARCH'] = 'YES'
  111. def config_octest(self):
  112. self.buildSettings = {'PRODUCT_NAME':'$(TARGET_NAME)', 'WRAPPER_EXTENSION':'octest', 'COMBINE_HIDPI_IMAGES':'YES', 'ONLY_ACTIVE_ARCH':'YES'}
  113. class XCConfigurationList(XCodeNode):
  114. def __init__(self, settings):
  115. XCodeNode.__init__(self)
  116. self.buildConfigurations = settings
  117. self.defaultConfigurationIsVisible = 0
  118. self.defaultConfigurationName = settings and settings[0].name or ""
  119. # Group/Files
  120. class PBXFileReference(XCodeNode):
  121. def __init__(self, name, path, filetype = '', sourcetree = "<group>"):
  122. XCodeNode.__init__(self)
  123. self.fileEncoding = 4
  124. if not filetype:
  125. _, ext = os.path.splitext(name)
  126. filetype = MAP_EXT.get(ext, 'text')
  127. self.lastKnownFileType = filetype
  128. self.name = name
  129. if os.path.isabs(path):
  130. sourcetree = '<absolute>'
  131. self.path = path
  132. else:
  133. sourcetree = '<group>'
  134. self.path = os.path.basename(path)
  135. class PBXGroup(XCodeNode):
  136. def __init__(self, name, sourcetree = "<group>"):
  137. XCodeNode.__init__(self)
  138. self.children = []
  139. self.name = name
  140. self.path = name
  141. self.sourceTree = sourcetree
  142. def add(self, root, sources):
  143. folders = {}
  144. def folder(n):
  145. if n == root:
  146. return self
  147. try:
  148. return folders[n]
  149. except KeyError:
  150. f = PBXGroup(n.name)
  151. p = folder(n.parent)
  152. folders[n] = f
  153. p.children.append(f)
  154. return f
  155. for s in sources:
  156. f = folder(s.parent)
  157. source = PBXFileReference(s.name, s.abspath())
  158. f.children.append(source)
  159. def add_all_files_from_folder_path(self, directory):
  160. files = []
  161. def should_skip(filepath):
  162. name = os.path.basename(os.path.abspath(filepath))
  163. return name.startswith('.') or os.path.splitext(name)[1] == '.xcodeproj' or name == 'build' # or has_hidden_attribute(filepath)
  164. for name in os.listdir(directory):
  165. path = os.path.join(directory, name)
  166. if should_skip(path):
  167. continue
  168. if os.path.isfile(path):
  169. fileref=PBXFileReference(os.path.basename(path), path)
  170. self.children.append(fileref)
  171. files.append(fileref)
  172. elif os.path.isdir(path):
  173. subgroup = PBXGroup(name)
  174. files.extend(subgroup.add_all_files_from_folder_path(path))
  175. self.children.append(subgroup)
  176. return files
  177. # Targets
  178. class PBXLegacyTarget(XCodeNode):
  179. def __init__(self,target=''):
  180. XCodeNode.__init__(self)
  181. self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf')])
  182. self.buildArgumentsString="$(ACTION)"
  183. self.buildPhases = []
  184. self.buildToolPath="./waf-xcode.sh"
  185. self.buildWorkingDirectory = ""
  186. self.dependencies = []
  187. self.name = target
  188. self.productName = target
  189. self.passBuildSettingsInEnvironment = 0
  190. class PBXShellScriptBuildPhase(XCodeNode):
  191. def __init__(self, script):
  192. XCodeNode.__init__(self)
  193. self.buildActionMask = 2147483647
  194. self.files = []
  195. self.inputPaths = []
  196. self.outputPaths = []
  197. self.runOnlyForDeploymentPostProcessing = 1
  198. self.shellPath = "/bin/sh"
  199. self.shellScript = script
  200. class PBXNativeTarget(XCodeNode):
  201. def __init__(self, action=None, target=None, node=None, env=None, script=None, productType="com.apple.product-type.application"):
  202. XCodeNode.__init__(self)
  203. opts = {'PRODUCT_NAME':target, 'HEADER_SEARCH_PATHS': "$(SRCROOT)/../src/**"}
  204. if node:
  205. opts['CONFIGURATION_BUILD_DIR'] = node.parent.abspath()
  206. conf = XCBuildConfiguration('waf', opts, env)
  207. self.buildConfigurationList = XCConfigurationList([conf])
  208. self.buildPhases = []
  209. if script != None:
  210. self.buildPhases.append(PBXShellScriptBuildPhase(script))
  211. self.buildRules = []
  212. self.dependencies = []
  213. self.name = target
  214. self.productName = target
  215. self.productType = productType
  216. if node: product_dir = node.abspath()
  217. else: product_dir = ""
  218. self.productReference = PBXFileReference(target, product_dir, 'wrapper.application', 'BUILT_PRODUCTS_DIR')
  219. def config_octest_target(self):
  220. conf = XCBuildConfiguration('waf', {}, None)
  221. conf.config_octest()
  222. self.buildConfigurationList = XCConfigurationList([conf])
  223. self.productType = "com.apple.product-type.bundle"
  224. class PBXSourcesBuildPhase(XCodeNode):
  225. def __init__(self):
  226. XCodeNode.__init__(self)
  227. self.buildActionMask = 2147483647
  228. self.runOnlyForDeploymentPostprocessing = 0
  229. self.files = []
  230. def add_files(self, files):
  231. for f in files:
  232. _, ext = os.path.splitext(f.name)
  233. if ext in SOURCE_EXT:
  234. bf = PBXBuildFile(f)
  235. self.files.append(bf)
  236. class PBXBuildFile(XCodeNode):
  237. def __init__(self, fileRef):
  238. XCodeNode.__init__(self)
  239. self.fileRef = fileRef
  240. # Root project object
  241. class PBXProject(XCodeNode):
  242. def __init__(self, name, version):
  243. XCodeNode.__init__(self)
  244. self.buildConfigurationList = XCConfigurationList([XCBuildConfiguration('waf', {})])
  245. self.compatibilityVersion = version[0]
  246. self.hasScannedForEncodings = 1;
  247. self.mainGroup = PBXGroup(name)
  248. self.projectRoot = ""
  249. self.projectDirPath = ""
  250. self.targets = []
  251. self._objectVersion = version[1]
  252. self._output = PBXGroup('out')
  253. self.mainGroup.children.append(self._output)
  254. def write(self, file):
  255. w = file.write
  256. w("// !$*UTF8*$!\n")
  257. w("{\n")
  258. w("\tarchiveVersion = 1;\n")
  259. w("\tclasses = {\n")
  260. w("\t};\n")
  261. w("\tobjectVersion = %d;\n" % self._objectVersion)
  262. w("\tobjects = {\n\n")
  263. XCodeNode.write(self, file)
  264. w("\t};\n")
  265. w("\trootObject = %s;\n" % self._id)
  266. w("}\n")
  267. class xcode_pebble(Build.BuildContext):
  268. """creates an xcode project file"""
  269. cmd = 'xcode'
  270. fun = 'build'
  271. def collect_source(self, tg):
  272. source_files = tg.to_nodes(getattr(tg, 'source', []))
  273. plist_files = tg.to_nodes(getattr(tg, 'mac_plist', []))
  274. resource_files = [tg.path.find_node(i) for i in Utils.to_list(getattr(tg, 'mac_resources', []))]
  275. include_dirs = Utils.to_list(getattr(tg, 'includes', [])) + Utils.to_list(getattr(tg, 'export_dirs', []))
  276. include_files = []
  277. for x in include_dirs:
  278. if not isinstance(x, str):
  279. include_files.append(x)
  280. continue
  281. d = tg.path.find_node(x)
  282. if d:
  283. lst = [y for y in d.ant_glob(HEADERS_GLOB, flat=False)]
  284. include_files.extend(lst)
  285. # remove duplicates
  286. source = list(set(source_files + plist_files + resource_files + include_files))
  287. source.sort(key=lambda x: x.abspath())
  288. return source
  289. def execute(self):
  290. """
  291. Entry point
  292. """
  293. self.restore()
  294. if not self.all_envs:
  295. self.load_envs()
  296. self.recurse([self.run_dir])
  297. root = os.path.basename(self.srcnode.abspath())
  298. appname = getattr(Context.g_module, Context.APPNAME, root)
  299. p = PBXProject(appname, ('Xcode 3.2', 46))
  300. # Xcode Target that invokes waf-xcode.sh:
  301. target = PBXLegacyTarget('waf')
  302. p.targets.append(target)
  303. # Add references to all files:
  304. p.mainGroup.path = "../"
  305. files = p.mainGroup.add_all_files_from_folder_path(self.srcnode.abspath())
  306. # FIXME: How to get SDK path?
  307. sdk_path = os.path.join(os.path.dirname(Context.__file__), '..', '..')
  308. if sdk_path and os.path.exists(sdk_path):
  309. sdk_include_path = os.path.abspath(os.path.join(sdk_path, 'include'))
  310. if os.path.exists(sdk_include_path):
  311. sdk_headers = p.mainGroup.add_all_files_from_folder_path(sdk_include_path)
  312. files.extend(sdk_headers)
  313. # Create dummy native app that is needed to trigger Xcode's code completion + indexing:
  314. index_dummy_target = PBXNativeTarget(None, "index_dummy", productType="com.apple.product-type.tool")
  315. index_dummy_sources_phase = PBXSourcesBuildPhase()
  316. index_dummy_sources_phase.add_files(files)
  317. index_dummy_target.buildPhases.append(index_dummy_sources_phase)
  318. p.targets.append(index_dummy_target)
  319. # Create fake .octest bundle to invoke ./waf test:
  320. clar_tests_target = PBXNativeTarget(None, "clar_tests", script="export ACTION=test\n./waf-xcode.sh")
  321. clar_tests_target.config_octest_target()
  322. p.targets.append(clar_tests_target)
  323. # Xcode Target that invokes waf test
  324. target = PBXLegacyTarget('waf test')
  325. target.buildArgumentsString = "test"
  326. p.targets.append(target)
  327. # Write generated project to disk:
  328. node = self.srcnode.make_node('xcode/%s.xcodeproj' % appname)
  329. node.mkdir()
  330. node = node.make_node('project.pbxproj')
  331. p.write(open(node.abspath(), 'w'))
  332. # Generate waf-xcode.sh shim script
  333. xcscript_node=self.srcnode.make_node('xcode/waf-xcode.sh')
  334. xcscript_path=xcscript_node.abspath()
  335. f = open(xcscript_path,'w')
  336. f.write("#!/bin/bash\n\
  337. # Expecting PebbleSDK + arm toolchain + openocd binaries to be in $PATH after sourcing .bash_profile:\n\
  338. export PATH=`python ../tools/strip_xcode_paths.py`\n\
  339. source ~/.bash_profile\n\
  340. cd ..\n\
  341. ACTION=$@\n\
  342. if [ -z $ACTION ]; then\n\
  343. ACTION=build\n\
  344. fi\n\
  345. # Use pypy if available\n\
  346. if ! which pypy &> /dev/null; then\n\
  347. # Check if waf is on the path:\n\
  348. if ! type \"waf\" &> /dev/null; then\n\
  349. ./waf $ACTION\n\
  350. else\n\
  351. waf $ACTION\n\
  352. fi\n\
  353. else\n\
  354. echo \"Using pypy\"\n\
  355. pypy waf $ACTION\n\
  356. fi\n\
  357. ")
  358. os.chmod(xcscript_path, 0o0755)
  359. f.close()