mod_pbxproj.py 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624
  1. # Copyright 2012 Calvin Rien
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # A pbxproj file is an OpenStep format plist
  15. # {} represents dictionary of key=value pairs delimited by ;
  16. # () represents list of values delimited by ,
  17. # file starts with a comment specifying the character type
  18. # // !$*UTF8*$!
  19. # when adding a file to a project, create the PBXFileReference
  20. # add the PBXFileReference's guid to a group
  21. # create a PBXBuildFile with the PBXFileReference's guid
  22. # add the PBXBuildFile to the appropriate build phase
  23. # when adding a header search path add
  24. # HEADER_SEARCH_PATHS = "path/**";
  25. # to each XCBuildConfiguration object
  26. # Xcode4 will read either a OpenStep or XML plist.
  27. # this script uses `plutil` to validate, read and write
  28. # the pbxproj file. Plutil is available in OS X 10.2 and higher
  29. # Plutil can't write OpenStep plists, so I save as XML
  30. import datetime
  31. import json
  32. import ntpath
  33. import os
  34. import plistlib
  35. import re
  36. import shutil
  37. import subprocess
  38. import uuid
  39. from UserDict import IterableUserDict
  40. from UserList import UserList
  41. regex = '[a-zA-Z0-9\\._/-]*'
  42. class PBXEncoder(json.JSONEncoder):
  43. def default(self, obj):
  44. """Tests the input object, obj, to encode as JSON."""
  45. if isinstance(obj, (PBXList, PBXDict)):
  46. return obj.data
  47. return json.JSONEncoder.default(self, obj)
  48. class PBXDict(IterableUserDict):
  49. def __init__(self, d=None):
  50. if d:
  51. d = dict([(PBXType.Convert(k), PBXType.Convert(v)) for k, v in d.items()])
  52. IterableUserDict.__init__(self, d)
  53. def __setitem__(self, key, value):
  54. IterableUserDict.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value))
  55. def remove(self, key):
  56. self.data.pop(PBXType.Convert(key), None)
  57. class PBXList(UserList):
  58. def __init__(self, l=None):
  59. if isinstance(l, basestring):
  60. UserList.__init__(self)
  61. self.add(l)
  62. return
  63. elif l:
  64. l = [PBXType.Convert(v) for v in l]
  65. UserList.__init__(self, l)
  66. def add(self, value):
  67. value = PBXType.Convert(value)
  68. if value in self.data:
  69. return False
  70. self.data.append(value)
  71. return True
  72. def remove(self, value):
  73. value = PBXType.Convert(value)
  74. if value in self.data:
  75. self.data.remove(value)
  76. return True
  77. return False
  78. def __setitem__(self, key, value):
  79. UserList.__setitem__(self, PBXType.Convert(key), PBXType.Convert(value))
  80. class PBXType(PBXDict):
  81. def __init__(self, d=None):
  82. PBXDict.__init__(self, d)
  83. if 'isa' not in self:
  84. self['isa'] = self.__class__.__name__
  85. self.id = None
  86. @staticmethod
  87. def Convert(o):
  88. if isinstance(o, list):
  89. return PBXList(o)
  90. elif isinstance(o, dict):
  91. isa = o.get('isa')
  92. if not isa:
  93. return PBXDict(o)
  94. cls = globals().get(isa)
  95. if cls and issubclass(cls, PBXType):
  96. return cls(o)
  97. print 'warning: unknown PBX type: %s' % isa
  98. return PBXDict(o)
  99. else:
  100. return o
  101. @staticmethod
  102. def IsGuid(o):
  103. return re.match('^[A-F0-9]{24}$', str(o))
  104. @classmethod
  105. def GenerateId(cls):
  106. return ''.join(str(uuid.uuid4()).upper().split('-')[1:])
  107. @classmethod
  108. def Create(cls, *args, **kwargs):
  109. return cls(*args, **kwargs)
  110. class PBXFileReference(PBXType):
  111. def __init__(self, d=None):
  112. PBXType.__init__(self, d)
  113. self.build_phase = None
  114. types = {
  115. '.a': ('archive.ar', 'PBXFrameworksBuildPhase'),
  116. '.app': ('wrapper.application', None),
  117. '.s': ('sourcecode.asm', 'PBXSourcesBuildPhase'),
  118. '.c': ('sourcecode.c.c', 'PBXSourcesBuildPhase'),
  119. '.cpp': ('sourcecode.cpp.cpp', 'PBXSourcesBuildPhase'),
  120. '.framework': ('wrapper.framework', 'PBXFrameworksBuildPhase'),
  121. '.h': ('sourcecode.c.h', None),
  122. '.hpp': ('sourcecode.c.h', None),
  123. '.d': ('sourcecode.dtrace', 'PBXSourcesBuildPhase'),
  124. '.swift': ('sourcecode.swift', 'PBXSourcesBuildPhase'),
  125. '.icns': ('image.icns', 'PBXResourcesBuildPhase'),
  126. '.m': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
  127. '.j': ('sourcecode.c.objc', 'PBXSourcesBuildPhase'),
  128. '.mm': ('sourcecode.cpp.objcpp', 'PBXSourcesBuildPhase'),
  129. '.nib': ('wrapper.nib', 'PBXResourcesBuildPhase'),
  130. '.plist': ('text.plist.xml', 'PBXResourcesBuildPhase'),
  131. '.json': ('text.json', 'PBXResourcesBuildPhase'),
  132. '.png': ('image.png', 'PBXResourcesBuildPhase'),
  133. '.rtf': ('text.rtf', 'PBXResourcesBuildPhase'),
  134. '.tiff': ('image.tiff', 'PBXResourcesBuildPhase'),
  135. '.txt': ('text', 'PBXResourcesBuildPhase'),
  136. '.xcodeproj': ('wrapper.pb-project', None),
  137. '.xib': ('file.xib', 'PBXResourcesBuildPhase'),
  138. '.strings': ('text.plist.strings', 'PBXResourcesBuildPhase'),
  139. '.bundle': ('wrapper.plug-in', 'PBXResourcesBuildPhase'),
  140. '.dylib': ('compiled.mach-o.dylib', 'PBXFrameworksBuildPhase'),
  141. '.xcdatamodeld': ('wrapper.xcdatamodel', 'PBXSourcesBuildPhase'),
  142. '.xcassets': ('folder.assetcatalog', 'PBXResourcesBuildPhase'),
  143. '.tbd': ('sourcecode.text-based-dylib-definition', 'PBXFrameworksBuildPhase'),
  144. '.storyboardc': ('wrapper.storyboardc', 'PBXResourcesBuildPhase'),
  145. }
  146. trees = [
  147. '<absolute>',
  148. '<group>',
  149. 'BUILT_PRODUCTS_DIR',
  150. 'DEVELOPER_DIR',
  151. 'SDKROOT',
  152. 'SOURCE_ROOT',
  153. ]
  154. def guess_file_type(self, ignore_unknown_type=False):
  155. self.remove('explicitFileType')
  156. self.remove('lastKnownFileType')
  157. ext = os.path.splitext(self.get('name', ''))[1]
  158. if os.path.isdir(self.get('path')) and ext not in XcodeProject.special_folders:
  159. f_type = 'folder'
  160. build_phase = None
  161. ext = ''
  162. else:
  163. f_type, build_phase = PBXFileReference.types.get(ext, ('?', 'PBXResourcesBuildPhase'))
  164. self['lastKnownFileType'] = f_type
  165. self.build_phase = build_phase
  166. if f_type == '?' and not ignore_unknown_type:
  167. print 'unknown file extension: %s' % ext
  168. print 'please add extension and Xcode type to PBXFileReference.types'
  169. return f_type
  170. def set_file_type(self, ft):
  171. self.remove('explicitFileType')
  172. self.remove('lastKnownFileType')
  173. self['explicitFileType'] = ft
  174. @classmethod
  175. def Create(cls, os_path, tree='SOURCE_ROOT', ignore_unknown_type=False):
  176. if tree not in cls.trees:
  177. print 'Not a valid sourceTree type: %s' % tree
  178. return None
  179. fr = cls()
  180. fr.id = cls.GenerateId()
  181. fr['path'] = os_path
  182. fr['name'] = os.path.split(os_path)[1]
  183. fr['sourceTree'] = '<absolute>' if os.path.isabs(os_path) else tree
  184. fr.guess_file_type(ignore_unknown_type=ignore_unknown_type)
  185. return fr
  186. class PBXBuildFile(PBXType):
  187. def set_weak_link(self, weak=False):
  188. k_settings = 'settings'
  189. k_attributes = 'ATTRIBUTES'
  190. s = self.get(k_settings)
  191. if not s:
  192. if weak:
  193. self[k_settings] = PBXDict({k_attributes: PBXList(['Weak'])})
  194. return True
  195. atr = s.get(k_attributes)
  196. if not atr:
  197. if weak:
  198. atr = PBXList()
  199. else:
  200. return False
  201. if weak:
  202. atr.add('Weak')
  203. else:
  204. atr.remove('Weak')
  205. self[k_settings][k_attributes] = atr
  206. return True
  207. def add_compiler_flag(self, flag):
  208. k_settings = 'settings'
  209. k_attributes = 'COMPILER_FLAGS'
  210. if k_settings not in self:
  211. self[k_settings] = PBXDict()
  212. if k_attributes not in self[k_settings]:
  213. self[k_settings][k_attributes] = flag
  214. return True
  215. flags = self[k_settings][k_attributes].split(' ')
  216. if flag in flags:
  217. return False
  218. flags.append(flag)
  219. self[k_settings][k_attributes] = ' '.join(flags)
  220. def add_code_sign_on_copy(self):
  221. k_settings = 'settings'
  222. k_attributes = 'ATTRIBUTES'
  223. if k_settings not in self:
  224. self[k_settings] = PBXDict()
  225. if k_attributes not in self[k_settings]:
  226. attrs = PBXList()
  227. attrs.add("CodeSignOnCopy")
  228. attrs.add("RemoveHeadersOnCopy")
  229. self[k_settings][k_attributes]=attrs
  230. else:
  231. attrs = self[k_settings][k_attributes]
  232. attrs.add("CodeSignOnCopy")
  233. attrs.add("RemoveHeadersOnCopy")
  234. return True
  235. @classmethod
  236. def Create(cls, file_ref, weak=False):
  237. if isinstance(file_ref, PBXFileReference):
  238. file_ref = file_ref.id
  239. bf = cls()
  240. bf.id = cls.GenerateId()
  241. bf['fileRef'] = file_ref
  242. if weak:
  243. bf.set_weak_link(True)
  244. return bf
  245. class PBXGroup(PBXType):
  246. def add_child(self, ref):
  247. if not isinstance(ref, PBXDict):
  248. return None
  249. isa = ref.get('isa')
  250. if isa != 'PBXFileReference' and isa != 'PBXGroup':
  251. return None
  252. if 'children' not in self:
  253. self['children'] = PBXList()
  254. self['children'].add(ref.id)
  255. return ref.id
  256. def remove_child(self, id):
  257. if 'children' not in self:
  258. self['children'] = PBXList()
  259. return
  260. if not PBXType.IsGuid(id):
  261. id = id.id
  262. self['children'].remove(id)
  263. def has_child(self, id):
  264. if 'children' not in self:
  265. self['children'] = PBXList()
  266. return False
  267. if not PBXType.IsGuid(id):
  268. id = id.id
  269. return id in self['children']
  270. def get_name(self):
  271. path_name = os.path.split(self.get('path', ''))[1]
  272. return self.get('name', path_name)
  273. @classmethod
  274. def Create(cls, name, path=None, tree='SOURCE_ROOT'):
  275. grp = cls()
  276. grp.id = cls.GenerateId()
  277. grp['name'] = name
  278. grp['children'] = PBXList()
  279. if path:
  280. grp['path'] = path
  281. grp['sourceTree'] = tree
  282. else:
  283. grp['sourceTree'] = '<group>'
  284. return grp
  285. class PBXNativeTarget(PBXType):
  286. pass
  287. class PBXProject(PBXType):
  288. pass
  289. class PBXContainerItemProxy(PBXType):
  290. pass
  291. class PBXReferenceProxy(PBXType):
  292. pass
  293. class PBXVariantGroup(PBXType):
  294. pass
  295. class PBXTargetDependency(PBXType):
  296. pass
  297. class PBXAggregateTarget(PBXType):
  298. pass
  299. class PBXHeadersBuildPhase(PBXType):
  300. pass
  301. class XCVersionGroup(PBXType):
  302. pass
  303. class PBXLegacyTarget(PBXType):
  304. pass
  305. class PBXBuildPhase(PBXType):
  306. def add_build_file(self, bf):
  307. if bf.get('isa') != 'PBXBuildFile':
  308. return False
  309. if 'files' not in self:
  310. self['files'] = PBXList()
  311. self['files'].add(bf.id)
  312. return True
  313. def remove_build_file(self, id):
  314. if 'files' not in self:
  315. self['files'] = PBXList()
  316. return
  317. self['files'].remove(id)
  318. def has_build_file(self, id):
  319. if 'files' not in self:
  320. self['files'] = PBXList()
  321. return False
  322. if not PBXType.IsGuid(id):
  323. id = id.id
  324. return id in self['files']
  325. class PBXFrameworksBuildPhase(PBXBuildPhase):
  326. pass
  327. class PBXResourcesBuildPhase(PBXBuildPhase):
  328. pass
  329. class PBXShellScriptBuildPhase(PBXBuildPhase):
  330. @classmethod
  331. def Create(cls, script, shell="/bin/sh", files=[], input_paths=[], output_paths=[], show_in_log = '0'):
  332. bf = cls()
  333. bf.id = cls.GenerateId()
  334. bf['files'] = files
  335. bf['inputPaths'] = input_paths
  336. bf['outputPaths'] = output_paths
  337. bf['runOnlyForDeploymentPostprocessing'] = '0';
  338. bf['shellPath'] = shell
  339. bf['shellScript'] = script
  340. bf['showEnvVarsInLog'] = show_in_log
  341. return bf
  342. class PBXSourcesBuildPhase(PBXBuildPhase):
  343. pass
  344. class PBXCopyFilesBuildPhase(PBXBuildPhase):
  345. @classmethod
  346. def Create(cls, buildActionMask):
  347. bf = cls()
  348. bf.id = cls.GenerateId()
  349. bf['buildActionMask'] = str(buildActionMask)
  350. bf['dstPath'] = ""
  351. bf['dstSubfolderSpec'] = '10'
  352. bf['name'] = "Embed Frameworks"
  353. bf['runOnlyForDeploymentPostprocessing'] = '0'
  354. return bf
  355. class XCBuildConfiguration(PBXType):
  356. def add_search_paths(self, paths, base, key, recursive=True, escape=True):
  357. modified = False
  358. if not isinstance(paths, list):
  359. paths = [paths]
  360. if base not in self:
  361. self[base] = PBXDict()
  362. for path in paths:
  363. if recursive and not path.endswith('/**'):
  364. path = os.path.join(path, '**')
  365. if key not in self[base]:
  366. self[base][key] = PBXList()
  367. elif isinstance(self[base][key], basestring):
  368. self[base][key] = PBXList(self[base][key])
  369. if path == '$(inherited)':
  370. escape = False
  371. if escape:
  372. if self[base][key].add('"%s"' % path): # '\\"%s\\"' % path
  373. modified = True
  374. else:
  375. if self[base][key].add(path): # '\\"%s\\"' % path
  376. modified = True
  377. return modified
  378. def add_header_search_paths(self, paths, recursive=True):
  379. return self.add_search_paths(paths, 'buildSettings', 'HEADER_SEARCH_PATHS', recursive=recursive)
  380. def add_library_search_paths(self, paths, recursive=True):
  381. return self.add_search_paths(paths, 'buildSettings', 'LIBRARY_SEARCH_PATHS', recursive=recursive)
  382. def add_framework_search_paths(self, paths, recursive=True):
  383. return self.add_search_paths(paths, 'buildSettings', 'FRAMEWORK_SEARCH_PATHS', recursive=recursive)
  384. def add_other_cflags(self, flags):
  385. return self.add_flag('OTHER_CFLAGS', flags)
  386. def add_other_ldflags(self, flags):
  387. return self.add_flag('OTHER_LDFLAGS', flags)
  388. def add_flag(self, key, flags):
  389. modified = False
  390. base = 'buildSettings'
  391. if isinstance(flags, basestring):
  392. flags = PBXList(flags)
  393. if base not in self:
  394. self[base] = PBXDict()
  395. for flag in flags:
  396. if key not in self[base]:
  397. self[base][key] = PBXList()
  398. elif isinstance(self[base][key], basestring):
  399. self[base][key] = PBXList(self[base][key])
  400. if self[base][key].add(flag):
  401. self[base][key] = [e for e in self[base][key] if e]
  402. modified = True
  403. return modified
  404. def remove_flag(self, key, flags):
  405. modified = False
  406. base = 'buildSettings'
  407. if isinstance(flags, basestring):
  408. flags = PBXList(flags)
  409. if base in self: # there are flags, so we can "remove" something
  410. for flag in flags:
  411. if key not in self[base]:
  412. return False
  413. elif isinstance(self[base][key], basestring):
  414. self[base][key] = PBXList(self[base][key])
  415. if self[base][key].remove(flag):
  416. self[base][key] = [e for e in self[base][key] if e]
  417. modified = True
  418. if len(self[base][key]) == 0:
  419. self[base].pop(key, None)
  420. return modified
  421. def remove_other_ldflags(self, flags):
  422. return self.remove_flag('OTHER_LDFLAGS', flags)
  423. # Set a single-valued flag under buildSettings
  424. def add_single_valued_flag(self, flag, value):
  425. modified = False
  426. base = 'buildSettings'
  427. key = flag
  428. if not self.has_key(base):
  429. self[base] = PBXDict()
  430. if self[base].has_key(key):
  431. if self[base][key] == value:
  432. return False
  433. self[base][key] = value
  434. modified = True
  435. return modified
  436. # Remove a single-valued flag under buildSettings
  437. def remove_single_valued_flag(self, flag):
  438. modified = False
  439. base = 'buildSettings'
  440. key = flag
  441. if self.has_key(base) and self[base].has_key(key):
  442. self[base].pop(key, None)
  443. modified = True
  444. return modified
  445. class XCConfigurationList(PBXType):
  446. pass
  447. class XcodeProject(PBXDict):
  448. plutil_path = 'plutil'
  449. special_folders = ['.bundle', '.framework', '.xcodeproj', '.xcassets', '.xcdatamodeld', '.storyboardc']
  450. def __init__(self, d=None, path=None):
  451. if not path:
  452. path = os.path.join(os.getcwd(), 'project.pbxproj')
  453. self.pbxproj_path = os.path.abspath(path)
  454. self.source_root = os.path.abspath(os.path.join(os.path.split(path)[0], '..'))
  455. IterableUserDict.__init__(self, d)
  456. self.data = PBXDict(self.data)
  457. self.objects = self.get('objects')
  458. self.modified = False
  459. root_id = self.get('rootObject')
  460. if root_id:
  461. self.root_object = self.objects[root_id]
  462. root_group_id = self.root_object.get('mainGroup')
  463. self.root_group = self.objects[root_group_id]
  464. else:
  465. print "error: project has no root object"
  466. self.root_object = None
  467. self.root_group = None
  468. for k, v in self.objects.iteritems():
  469. v.id = k
  470. def add_other_cflags(self, flags):
  471. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  472. for b in build_configs:
  473. if b.add_other_cflags(flags):
  474. self.modified = True
  475. def add_other_ldflags(self, flags):
  476. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  477. for b in build_configs:
  478. if b.add_other_ldflags(flags):
  479. self.modified = True
  480. def remove_other_ldflags(self, flags):
  481. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  482. for b in build_configs:
  483. if b.remove_other_ldflags(flags):
  484. self.modified = True
  485. def add_header_search_paths(self, paths, recursive=True):
  486. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  487. for b in build_configs:
  488. if b.add_header_search_paths(paths, recursive):
  489. self.modified = True
  490. def add_framework_search_paths(self, paths, recursive=True):
  491. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  492. for b in build_configs:
  493. if b.add_framework_search_paths(paths, recursive):
  494. self.modified = True
  495. def add_library_search_paths(self, paths, recursive=True):
  496. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  497. for b in build_configs:
  498. if b.add_library_search_paths(paths, recursive):
  499. self.modified = True
  500. def add_flags(self, pairs, configuration='All'):
  501. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  502. # iterate over all the pairs of configurations
  503. for b in build_configs:
  504. if configuration != "All" and b.get('name') != configuration :
  505. continue
  506. for k in pairs:
  507. if b.add_flag(k, pairs[k]):
  508. self.modified = True
  509. def remove_flags(self, pairs, configuration='All'):
  510. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  511. # iterate over all the pairs of configurations
  512. for b in build_configs:
  513. if configuration != "All" and b.get('name') != configuration :
  514. continue
  515. for k in pairs:
  516. if b.remove_flag(k, pairs[k]):
  517. self.modified = True
  518. # Set a single-valued flag (whereas add_flags adds a flag to a list of flags with a given key)
  519. def add_single_valued_flag(self, flag, value, configuration='All'):
  520. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  521. for b in build_configs:
  522. if configuration != "All" and b.get('name') != configuration :
  523. continue
  524. if b.add_single_valued_flag(flag, value):
  525. self.modified = True
  526. # Remove a single-valued flag (whereas remove_flags deletes a flag from a list of flags with a given key)
  527. def remove_single_valued_flag(self, flag, configuration='All'):
  528. build_configs = [b for b in self.objects.values() if b.get('isa') == 'XCBuildConfiguration']
  529. for b in build_configs:
  530. if configuration != "All" and b.get('name') != configuration :
  531. continue
  532. if b.remove_single_valued_flag(flag):
  533. self.modified = True
  534. def add_embed_binaries(self, embed_binaries):
  535. if len(embed_binaries) > 0:
  536. self.modified = True
  537. self.add_single_valued_flag("LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks", "Release")
  538. self.add_single_valued_flag("LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks", "Debug")
  539. for binary in embed_binaries:
  540. self.add_embed_framework(binary)
  541. def add_embed_framework(self,file_name):
  542. file_refs = self.get_files_by_name(os.path.basename(file_name))
  543. if len(file_refs) > 0:
  544. file_ref = file_refs[0]
  545. embedPhase = self.add_embed_framework_build_phase()
  546. if embedPhase == None:
  547. print "AddEmbedFrameworkBuildPhase Failed."
  548. return
  549. buildFile = PBXBuildFile.Create(file_ref)
  550. buildFile.add_code_sign_on_copy()
  551. self.objects[buildFile.id] = buildFile
  552. embedPhase.add_build_file(buildFile)
  553. else:
  554. print "Embed Framework must added already: " + file_name
  555. def add_embed_framework_build_phase(self):
  556. phase = None
  557. naviTarget = self.get_target_by_name("Unity-iPhone")
  558. if naviTarget == None :
  559. print "Not found Correct NativeTarget."
  560. return phase
  561. copyBuildPhase = self.get_copy_file_build_phase()
  562. for buildPhase in copyBuildPhase:
  563. if buildPhase.has_key('name'):
  564. Name = buildPhase.get('name')
  565. if Name == "Embed Frameworks":
  566. return buildPhase
  567. build_action_mask = self.get_build_action_mask()
  568. phase = PBXCopyFilesBuildPhase.Create(build_action_mask)
  569. buildPhases = naviTarget.get("buildPhases")
  570. buildPhases.append(phase.id)
  571. self.objects[phase.id] = phase
  572. return phase
  573. def get_copy_file_build_phase(self):
  574. Ls = [b for b in self.objects.values() if b.get('isa') == 'PBXCopyFilesBuildPhase']
  575. return Ls
  576. def get_build_action_mask(self):
  577. build_action_mask = 0
  578. copy_file = [b for b in self.objects.values() if b.get('isa') == 'PBXCopyFilesBuildPhase']
  579. for obj in copy_file:
  580. build_action_mask = obj.get("buildActionMask")
  581. break
  582. return build_action_mask
  583. def get_obj(self, id):
  584. return self.objects.get(id)
  585. def get_ids(self):
  586. return self.objects.keys()
  587. def get_files_by_os_path(self, os_path, tree='SOURCE_ROOT'):
  588. files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
  589. and f.get('path') == os_path
  590. and f.get('sourceTree') == tree]
  591. return files
  592. def get_files_by_name(self, name, parent=None):
  593. if parent:
  594. files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
  595. and f.get('name') == name
  596. and parent.has_child(f)]
  597. else:
  598. files = [f for f in self.objects.values() if f.get('isa') == 'PBXFileReference'
  599. and f.get('name') == name]
  600. return files
  601. def get_keys_for_files_by_name(self, name):
  602. keys = [key for key in self.objects if self.objects.data[key].get('name') == name
  603. and self.objects.data[key].get('isa') == 'PBXFileReference']
  604. return keys
  605. def get_build_files(self, id):
  606. files = [f for f in self.objects.values() if f.get('isa') == 'PBXBuildFile'
  607. and f.get('fileRef') == id]
  608. return files
  609. def get_groups_by_name(self, name, parent=None):
  610. if parent:
  611. groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
  612. and g.get_name() == name
  613. and parent.has_child(g)]
  614. else:
  615. groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
  616. and g.get_name() == name]
  617. return groups
  618. def get_or_create_group(self, name, path=None, parent=None):
  619. if not name:
  620. return None
  621. if not parent:
  622. parent = self.root_group
  623. elif not isinstance(parent, PBXGroup):
  624. # assume it's an id
  625. parent = self.objects.get(parent, self.root_group)
  626. groups = self.get_groups_by_name(name)
  627. for grp in groups:
  628. if parent.has_child(grp.id):
  629. return grp
  630. grp = PBXGroup.Create(name, path)
  631. parent.add_child(grp)
  632. self.objects[grp.id] = grp
  633. self.modified = True
  634. return grp
  635. def get_groups_by_os_path(self, path):
  636. path = os.path.abspath(path)
  637. groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup'
  638. and os.path.abspath(g.get('path', '/dev/null')) == path]
  639. return groups
  640. def get_build_phases(self, phase_name):
  641. phases = [p for p in self.objects.values() if p.get('isa') == phase_name]
  642. return phases
  643. def get_target_by_name(self, name):
  644. targets = self.get_build_phases('PBXNativeTarget')
  645. target = None
  646. for t in targets:
  647. if t.get("name") == name:
  648. target = t
  649. break
  650. return target
  651. def get_relative_path(self, os_path):
  652. return os.path.relpath(os_path, self.source_root)
  653. def verify_files(self, file_list, parent=None):
  654. # returns list of files not in the current project.
  655. if not file_list:
  656. return []
  657. if parent:
  658. exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list and parent.has_child(f)]
  659. else:
  660. exists_list = [f.get('name') for f in self.objects.values() if f.get('isa') == 'PBXFileReference' and f.get('name') in file_list]
  661. return set(file_list).difference(exists_list)
  662. def add_run_script(self, target, script=None, insert_before_compile=False):
  663. result = []
  664. targets = [t for t in self.get_build_phases('PBXNativeTarget') + self.get_build_phases('PBXAggregateTarget') if t.get('name') == target]
  665. if len(targets) != 0 :
  666. script_phase = PBXShellScriptBuildPhase.Create(script)
  667. for t in targets:
  668. skip = False
  669. for buildPhase in t['buildPhases']:
  670. if self.objects[buildPhase].get('isa') == 'PBXShellScriptBuildPhase' and self.objects[buildPhase].get('shellScript') == script:
  671. skip = True
  672. if not skip:
  673. if insert_before_compile:
  674. t['buildPhases'].insert(0, script_phase.id)
  675. else:
  676. t['buildPhases'].add(script_phase.id)
  677. self.objects[script_phase.id] = script_phase
  678. result.append(script_phase)
  679. return result
  680. def add_run_script_all_targets(self, script=None):
  681. result = []
  682. targets = self.get_build_phases('PBXNativeTarget') + self.get_build_phases('PBXAggregateTarget')
  683. if len(targets) != 0 :
  684. script_phase = PBXShellScriptBuildPhase.Create(script)
  685. for t in targets:
  686. skip = False
  687. for buildPhase in t['buildPhases']:
  688. if self.objects[buildPhase].get('isa') == 'PBXShellScriptBuildPhase' and self.objects[buildPhase].get('shellScript') == script:
  689. skip = True
  690. if not skip:
  691. t['buildPhases'].add(script_phase.id)
  692. self.objects[script_phase.id] = script_phase
  693. result.append(script_phase)
  694. return result
  695. def add_folder(self, os_path, parent=None, excludes=None, recursive=True, create_build_files=True):
  696. if not os.path.isdir(os_path):
  697. return []
  698. if not excludes:
  699. excludes = []
  700. results = []
  701. if not parent:
  702. parent = self.root_group
  703. elif not isinstance(parent, PBXGroup):
  704. # assume it's an id
  705. parent = self.objects.get(parent, self.root_group)
  706. path_dict = {os.path.split(os_path)[0]: parent}
  707. special_list = []
  708. for (grp_path, subdirs, files) in os.walk(os_path):
  709. parent_folder, folder_name = os.path.split(grp_path)
  710. parent = path_dict.get(parent_folder, parent)
  711. if [sp for sp in special_list if parent_folder.startswith(sp)]:
  712. continue
  713. if folder_name.startswith('.'):
  714. special_list.append(grp_path)
  715. continue
  716. if os.path.splitext(grp_path)[1] in XcodeProject.special_folders:
  717. # if this file has a special extension (bundle or framework mainly) treat it as a file
  718. special_list.append(grp_path)
  719. new_files = self.verify_files([folder_name], parent=parent)
  720. # Ignore this file if it is in excludes
  721. if new_files and not [m for m in excludes if re.match(m, grp_path)]:
  722. results.extend(self.add_file(grp_path, parent, create_build_files=create_build_files))
  723. continue
  724. # create group
  725. grp = self.get_or_create_group(folder_name, path=self.get_relative_path(grp_path), parent=parent)
  726. path_dict[grp_path] = grp
  727. results.append(grp)
  728. file_dict = {}
  729. for f in files:
  730. if f[0] == '.' or [m for m in excludes if re.match(m, f)]:
  731. continue
  732. kwds = {
  733. 'create_build_files': create_build_files,
  734. 'parent': grp,
  735. 'name': f
  736. }
  737. f_path = os.path.join(grp_path, f)
  738. file_dict[f_path] = kwds
  739. new_files = self.verify_files([n.get('name') for n in file_dict.values()], parent=grp)
  740. add_files = [(k, v) for k, v in file_dict.items() if v.get('name') in new_files]
  741. for path, kwds in add_files:
  742. kwds.pop('name', None)
  743. self.add_file(path, **kwds)
  744. if not recursive:
  745. break
  746. for r in results:
  747. self.objects[r.id] = r
  748. return results
  749. def path_leaf(self, path):
  750. head, tail = ntpath.split(path)
  751. return tail or ntpath.basename(head)
  752. def add_file_if_doesnt_exist(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=False, target=None):
  753. for obj in self.objects.values():
  754. if 'path' in obj:
  755. if self.path_leaf(f_path) == self.path_leaf(obj.get('path')):
  756. return []
  757. return self.add_file(f_path, parent, tree, create_build_files, weak, ignore_unknown_type=ignore_unknown_type, target=target)
  758. def add_file(self, f_path, parent=None, tree='SOURCE_ROOT', create_build_files=True, weak=False, ignore_unknown_type=False, target=None):
  759. results = []
  760. abs_path = ''
  761. if os.path.isabs(f_path):
  762. abs_path = f_path
  763. if not os.path.exists(f_path):
  764. return results
  765. elif tree == 'SOURCE_ROOT':
  766. f_path = os.path.relpath(f_path, self.source_root)
  767. else:
  768. tree = '<absolute>'
  769. if not parent:
  770. parent = self.root_group
  771. elif not isinstance(parent, PBXGroup):
  772. # assume it's an id
  773. parent = self.objects.get(parent, self.root_group)
  774. file_ref = PBXFileReference.Create(f_path, tree, ignore_unknown_type=ignore_unknown_type)
  775. parent.add_child(file_ref)
  776. results.append(file_ref)
  777. # create a build file for the file ref
  778. if file_ref.build_phase and create_build_files:
  779. phases = self.get_build_phases(file_ref.build_phase)
  780. if target:
  781. target = self.get_target_by_name(target)
  782. for phase in phases:
  783. if (not target) or (phase.id in target.get('buildPhases')):
  784. build_file = PBXBuildFile.Create(file_ref, weak=weak)
  785. phase.add_build_file(build_file)
  786. results.append(build_file)
  787. if abs_path and tree == 'SOURCE_ROOT' \
  788. and os.path.isfile(abs_path) \
  789. and file_ref.build_phase == 'PBXFrameworksBuildPhase':
  790. library_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0])
  791. self.add_library_search_paths([library_path], recursive=False)
  792. if abs_path and tree == 'SOURCE_ROOT' \
  793. and not os.path.isfile(abs_path) \
  794. and file_ref.build_phase == 'PBXFrameworksBuildPhase':
  795. framework_path = os.path.join('$(SRCROOT)', os.path.split(f_path)[0])
  796. self.add_framework_search_paths([framework_path, '$(inherited)'], recursive=False)
  797. for r in results:
  798. self.objects[r.id] = r
  799. if results:
  800. self.modified = True
  801. return results
  802. def check_and_repair_framework(self, base):
  803. name = os.path.basename(base)
  804. if ".framework" in name:
  805. basename = name[:-len(".framework")]
  806. finalHeaders = os.path.join(base, "Headers")
  807. finalCurrent = os.path.join(base, "Versions/Current")
  808. finalLib = os.path.join(base, basename)
  809. srcHeaders = "Versions/A/Headers"
  810. srcCurrent = "A"
  811. srcLib = "Versions/A/" + basename
  812. if not os.path.exists(finalHeaders):
  813. os.symlink(srcHeaders, finalHeaders)
  814. if not os.path.exists(finalCurrent):
  815. os.symlink(srcCurrent, finalCurrent)
  816. if not os.path.exists(finalLib):
  817. os.symlink(srcLib, finalLib)
  818. def get_file_id_by_path(self, f_path):
  819. for k, v in self.objects.iteritems():
  820. if str(v.get('path')) == f_path:
  821. return k
  822. return 0
  823. def remove_file_by_path(self, f_path, recursive=True):
  824. id = self.get_file_id_by_path(f_path)
  825. if id != 0:
  826. self.remove_file(id, recursive=recursive)
  827. return
  828. def remove_file(self, id, recursive=True):
  829. if not PBXType.IsGuid(id):
  830. id = id.id
  831. if id in self.objects:
  832. self.objects.remove(id)
  833. # Remove from PBXResourcesBuildPhase and PBXSourcesBuildPhase if necessary
  834. buildFiles = [f for f in self.objects.values() if f.get('isa') == 'PBXBuildFile']
  835. for buildFile in buildFiles:
  836. if id == buildFile.get('fileRef'):
  837. key = buildFile.id
  838. PBXRBP = [f for f in self.objects.values() if f.get('isa') == 'PBXResourcesBuildPhase']
  839. PBXSBP = [f for f in self.objects.values() if f.get('isa') == 'PBXSourcesBuildPhase']
  840. self.objects.remove(key)
  841. if len(PBXSBP) and PBXSBP[0].has_build_file(key):
  842. PBXSBP[0].remove_build_file(key)
  843. if len(PBXRBP) and PBXRBP[0].has_build_file(key):
  844. PBXRBP[0].remove_build_file(key)
  845. if recursive:
  846. groups = [g for g in self.objects.values() if g.get('isa') == 'PBXGroup']
  847. for group in groups:
  848. if id in group['children']:
  849. group.remove_child(id)
  850. self.modified = True
  851. def remove_group(self, id, recursive = True):
  852. if not PBXType.IsGuid(id):
  853. id = id.id
  854. name = self.objects.get(id).get('path')
  855. children = self.objects.get(id).get('children')
  856. if name is None:
  857. name = id
  858. if id in self.objects:
  859. if recursive:
  860. for childKey in children:
  861. childValue = self.objects.get(childKey)
  862. if childValue.get('isa') == 'PBXGroup':
  863. self.remove_group(childKey, True)
  864. else:
  865. self.remove_file(childKey, False)
  866. self.objects.remove(id);
  867. def remove_group_by_name(self, name, recursive = True):
  868. groups = self.get_groups_by_name(name)
  869. if len(groups):
  870. for group in groups:
  871. self.remove_group(group.id, recursive)
  872. def move_file(self, id, dest_grp=None):
  873. pass
  874. def apply_patch(self, patch_path, xcode_path):
  875. if not os.path.isfile(patch_path) or not os.path.isdir(xcode_path):
  876. print 'ERROR: couldn\'t apply "%s" to "%s"' % (patch_path, xcode_path)
  877. return
  878. print 'applying "%s" to "%s"' % (patch_path, xcode_path)
  879. return subprocess.call(['patch', '-p1', '--forward', '--directory=%s' % xcode_path, '--input=%s' % patch_path])
  880. def apply_mods(self, mod_dict, default_path=None):
  881. if not default_path:
  882. default_path = os.getcwd()
  883. keys = mod_dict.keys()
  884. for k in keys:
  885. v = mod_dict.pop(k)
  886. mod_dict[k.lower()] = v
  887. parent = mod_dict.pop('group', None)
  888. if parent:
  889. parent = self.get_or_create_group(parent)
  890. excludes = mod_dict.pop('excludes', [])
  891. if excludes:
  892. excludes = [re.compile(e) for e in excludes]
  893. compiler_flags = mod_dict.pop('compiler_flags', {})
  894. for k, v in mod_dict.items():
  895. if k == 'patches':
  896. for p in v:
  897. if not os.path.isabs(p):
  898. p = os.path.join(default_path, p)
  899. self.apply_patch(p, self.source_root)
  900. elif k == 'folders':
  901. # get and compile excludes list
  902. # do each folder individually
  903. for folder in v:
  904. kwds = {}
  905. # if path contains ':' remove it and set recursive to False
  906. if ':' in folder:
  907. args = folder.split(':')
  908. kwds['recursive'] = False
  909. folder = args.pop(0)
  910. if os.path.isabs(folder) and os.path.isdir(folder):
  911. pass
  912. else:
  913. folder = os.path.join(default_path, folder)
  914. if not os.path.isdir(folder):
  915. continue
  916. if parent:
  917. kwds['parent'] = parent
  918. if excludes:
  919. kwds['excludes'] = excludes
  920. self.add_folder(folder, **kwds)
  921. elif k == 'headerpaths' or k == 'librarypaths':
  922. paths = []
  923. for p in v:
  924. if p.endswith('/**'):
  925. p = os.path.split(p)[0]
  926. if not os.path.isabs(p):
  927. p = os.path.join(default_path, p)
  928. if not os.path.exists(p):
  929. continue
  930. p = self.get_relative_path(p)
  931. paths.append(os.path.join('$(SRCROOT)', p, "**"))
  932. if k == 'headerpaths':
  933. self.add_header_search_paths(paths)
  934. else:
  935. self.add_library_search_paths(paths)
  936. elif k == 'other_cflags':
  937. self.add_other_cflags(v)
  938. elif k == 'other_ldflags':
  939. self.add_other_ldflags(v)
  940. elif k == 'libs' or k == 'frameworks' or k == 'files':
  941. paths = {}
  942. for p in v:
  943. kwds = {}
  944. if ':' in p:
  945. args = p.split(':')
  946. p = args.pop(0)
  947. if 'weak' in args:
  948. kwds['weak'] = True
  949. file_path = os.path.join(default_path, p)
  950. search_path, file_name = os.path.split(file_path)
  951. if [m for m in excludes if re.match(m, file_name)]:
  952. continue
  953. try:
  954. expr = re.compile(file_name)
  955. except re.error:
  956. expr = None
  957. if expr and os.path.isdir(search_path):
  958. file_list = os.listdir(search_path)
  959. for f in file_list:
  960. if [m for m in excludes if re.match(m, f)]:
  961. continue
  962. if re.search(expr, f):
  963. kwds['name'] = f
  964. paths[os.path.join(search_path, f)] = kwds
  965. p = None
  966. if k == 'libs':
  967. kwds['parent'] = self.get_or_create_group('Libraries', parent=parent)
  968. elif k == 'frameworks':
  969. kwds['parent'] = self.get_or_create_group('Frameworks', parent=parent)
  970. if p:
  971. kwds['name'] = file_name
  972. if k == 'libs':
  973. p = os.path.join('usr', 'lib', p)
  974. kwds['tree'] = 'SDKROOT'
  975. elif k == 'frameworks':
  976. p = os.path.join('System', 'Library', 'Frameworks', p)
  977. kwds['tree'] = 'SDKROOT'
  978. elif k == 'files' and not os.path.exists(file_path):
  979. # don't add non-existent files to the project.
  980. continue
  981. paths[p] = kwds
  982. new_files = self.verify_files([n.get('name') for n in paths.values()])
  983. add_files = [(k, v) for k, v in paths.items() if v.get('name') in new_files]
  984. for path, kwds in add_files:
  985. kwds.pop('name', None)
  986. if 'parent' not in kwds and parent:
  987. kwds['parent'] = parent
  988. self.add_file(path, **kwds)
  989. if compiler_flags:
  990. for k, v in compiler_flags.items():
  991. filerefs = []
  992. for f in v:
  993. filerefs.extend([fr.id for fr in self.objects.values() if fr.get('isa') == 'PBXFileReference'
  994. and fr.get('name') == f])
  995. buildfiles = [bf for bf in self.objects.values() if bf.get('isa') == 'PBXBuildFile'
  996. and bf.get('fileRef') in filerefs]
  997. for bf in buildfiles:
  998. if bf.add_compiler_flag(k):
  999. self.modified = True
  1000. def backup(self, file_name=None, backup_name=None):
  1001. if not file_name:
  1002. file_name = self.pbxproj_path
  1003. if not backup_name:
  1004. backup_name = "%s.%s.backup" % (file_name, datetime.datetime.now().strftime('%d%m%y-%H%M%S'))
  1005. shutil.copy2(file_name, backup_name)
  1006. return backup_name
  1007. def save(self, file_name=None, old_format=False, sort=False):
  1008. if old_format :
  1009. self.save_format_xml(file_name)
  1010. else:
  1011. self.save_new_format(file_name, sort)
  1012. def save_format_xml(self, file_name=None):
  1013. """Saves in old (xml) format"""
  1014. if not file_name:
  1015. file_name = self.pbxproj_path
  1016. # This code is adapted from plistlib.writePlist
  1017. with open(file_name, "w") as f:
  1018. writer = PBXWriter(f)
  1019. writer.writeln("<plist version=\"1.0\">")
  1020. writer.writeValue(self.data)
  1021. writer.writeln("</plist>")
  1022. def save_new_format(self, file_name=None, sort=False):
  1023. """Save in Xcode 3.2 compatible (new) format"""
  1024. if not file_name:
  1025. file_name = self.pbxproj_path
  1026. # process to get the section's info and names
  1027. objs = self.data.get('objects')
  1028. sections = dict()
  1029. uuids = dict()
  1030. for key in objs:
  1031. l = list()
  1032. if objs.get(key).get('isa') in sections:
  1033. l = sections.get(objs.get(key).get('isa'))
  1034. l.append(tuple([key, objs.get(key)]))
  1035. sections[objs.get(key).get('isa')] = l
  1036. if 'name' in objs.get(key):
  1037. uuids[key] = objs.get(key).get('name')
  1038. elif 'path' in objs.get(key):
  1039. uuids[key] = objs.get(key).get('path')
  1040. else:
  1041. if objs.get(key).get('isa') == 'PBXProject':
  1042. uuids[objs.get(key).get('buildConfigurationList')] = 'Build configuration list for PBXProject "Unity-iPhone"'
  1043. elif objs.get(key).get('isa')[0:3] == 'PBX':
  1044. uuids[key] = objs.get(key).get('isa')[3:-10]
  1045. else:
  1046. uuids[key] = 'Build configuration list for PBXNativeTarget "TARGET_NAME"'
  1047. ro = self.data.get('rootObject')
  1048. uuids[ro] = 'Project object'
  1049. for key in objs:
  1050. # transitive references (used in the BuildFile section)
  1051. if 'fileRef' in objs.get(key) and objs.get(key).get('fileRef') in uuids:
  1052. uuids[key] = uuids[objs.get(key).get('fileRef')]
  1053. # transitive reference to the target name (used in the Native target section)
  1054. if objs.get(key).get('isa') == 'PBXNativeTarget':
  1055. uuids[objs.get(key).get('buildConfigurationList')] = uuids[objs.get(key).get('buildConfigurationList')].replace('TARGET_NAME', uuids[key])
  1056. self.uuids = uuids
  1057. self.sections = sections
  1058. out = open(file_name, 'w')
  1059. out.write('// !$*UTF8*$!\n')
  1060. self._printNewXCodeFormat(out, self.data, '', enters=True, sort=sort)
  1061. out.close()
  1062. @classmethod
  1063. def addslashes(cls, s):
  1064. d = {'"': '\\"', "'": "\\'", "\0": "\\\0", "\\": "\\\\", "\n":"\\n", "\t":"\\t"}
  1065. return ''.join(d.get(c, c) for c in s)
  1066. def _printNewXCodeFormat(self, out, root, deep, enters=True, sort=False):
  1067. if isinstance(root, IterableUserDict):
  1068. out.write('{')
  1069. if enters:
  1070. out.write('\n')
  1071. isa = root.pop('isa', '')
  1072. if isa != '': # keep the isa in the first spot
  1073. if enters:
  1074. out.write('\t' + deep)
  1075. out.write('isa = ')
  1076. self._printNewXCodeFormat(out, isa, '\t' + deep, enters=enters)
  1077. out.write(';')
  1078. if enters:
  1079. out.write('\n')
  1080. else:
  1081. out.write(' ')
  1082. for key in sorted(root.iterkeys()): # keep the same order as Apple.
  1083. if enters:
  1084. out.write('\t' + deep)
  1085. if re.match(regex, key).group(0) == key:
  1086. out.write(key.encode("utf-8") + ' = ')
  1087. else:
  1088. out.write('"' + key.encode("utf-8") + '" = ')
  1089. if key == 'objects':
  1090. out.write('{') # open the objects section
  1091. if enters:
  1092. out.write('\n')
  1093. #root.remove('objects') # remove it to avoid problems
  1094. sections = [
  1095. ('PBXBuildFile', False),
  1096. ('PBXCopyFilesBuildPhase', True),
  1097. ('PBXFileReference', False),
  1098. ('PBXFrameworksBuildPhase', True),
  1099. ('PBXGroup', True),
  1100. ('PBXAggregateTarget', True),
  1101. ('PBXNativeTarget', True),
  1102. ('PBXProject', True),
  1103. ('PBXResourcesBuildPhase', True),
  1104. ('PBXShellScriptBuildPhase', True),
  1105. ('PBXSourcesBuildPhase', True),
  1106. ('XCBuildConfiguration', True),
  1107. ('XCConfigurationList', True),
  1108. ('PBXTargetDependency', True),
  1109. ('PBXVariantGroup', True),
  1110. ('PBXReferenceProxy', True),
  1111. ('PBXContainerItemProxy', True),
  1112. ('XCVersionGroup', True),
  1113. ('PBXLegacyTarget', True)]
  1114. for section in sections: # iterate over the sections
  1115. if self.sections.get(section[0]) is None:
  1116. continue
  1117. out.write('\n/* Begin %s section */' % section[0].encode("utf-8"))
  1118. self.sections.get(section[0]).sort(cmp=lambda x, y: cmp(x[0], y[0]))
  1119. if sort and section[0] == 'PBXGroup':
  1120. for entry in self.sections.get(section[0]):
  1121. entry[1]['children'] = sorted(entry[1]['children'],
  1122. key=lambda x: self.uuids[x].encode("utf-8"))
  1123. for pair in self.sections.get(section[0]):
  1124. key = pair[0]
  1125. value = pair[1]
  1126. out.write('\n')
  1127. if enters:
  1128. out.write('\t\t' + deep)
  1129. out.write(key.encode("utf-8"))
  1130. if key in self.uuids:
  1131. out.write(" /* " + self.uuids[key].encode("utf-8") + " */")
  1132. out.write(" = ")
  1133. self._printNewXCodeFormat(out, value, '\t\t' + deep, enters=section[1])
  1134. out.write(';')
  1135. out.write('\n/* End %s section */\n' % section[0].encode("utf-8"))
  1136. out.write(deep + '\t}') # close of the objects section
  1137. else:
  1138. self._printNewXCodeFormat(out, root[key], '\t' + deep, enters=enters)
  1139. out.write(';')
  1140. if enters:
  1141. out.write('\n')
  1142. else:
  1143. out.write(' ')
  1144. root['isa'] = isa # restore the isa for further calls
  1145. if enters:
  1146. out.write(deep)
  1147. out.write('}')
  1148. elif isinstance(root, UserList):
  1149. out.write('(')
  1150. if enters:
  1151. out.write('\n')
  1152. for value in root:
  1153. if enters:
  1154. out.write('\t' + deep)
  1155. self._printNewXCodeFormat(out, value, '\t' + deep, enters=enters)
  1156. out.write(',')
  1157. if enters:
  1158. out.write('\n')
  1159. if enters:
  1160. out.write(deep)
  1161. out.write(')')
  1162. else:
  1163. if len(root) > 0 and re.match(regex, root).group(0) == root:
  1164. out.write(root.encode("utf-8"))
  1165. else:
  1166. out.write('"' + XcodeProject.addslashes(root.encode("utf-8")) + '"')
  1167. if root in self.uuids:
  1168. out.write(" /* " + self.uuids[root].encode("utf-8") + " */")
  1169. @classmethod
  1170. def Load(cls, path, pure_python=False):
  1171. if pure_python:
  1172. import openstep_parser as osp
  1173. tree = osp.OpenStepDecoder.ParseFromFile(open(path, 'r'))
  1174. else:
  1175. cls.plutil_path = os.path.join(os.path.split(__file__)[0], 'plutil')
  1176. if not os.path.isfile(XcodeProject.plutil_path):
  1177. cls.plutil_path = 'plutil'
  1178. # load project by converting to xml and then convert that using plistlib
  1179. p = subprocess.Popen([XcodeProject.plutil_path, '-convert', 'xml1', '-o', '-', path], stdout=subprocess.PIPE)
  1180. stdout, stderr = p.communicate()
  1181. # If the plist was malformed, return code will be non-zero
  1182. if p.returncode != 0:
  1183. print stdout
  1184. return None
  1185. tree = plistlib.readPlistFromString(stdout)
  1186. return XcodeProject(tree, path)
  1187. @classmethod
  1188. def LoadFromXML(cls, path):
  1189. tree = plistlib.readPlist(path)
  1190. return XcodeProject(tree, path)
  1191. # The code below was adapted from plistlib.py.
  1192. class PBXWriter(plistlib.PlistWriter):
  1193. def writeValue(self, value):
  1194. if isinstance(value, (PBXList, PBXDict)):
  1195. plistlib.PlistWriter.writeValue(self, value.data)
  1196. else:
  1197. plistlib.PlistWriter.writeValue(self, value)
  1198. def simpleElement(self, element, value=None):
  1199. """
  1200. We have to override this method to deal with Unicode text correctly.
  1201. Non-ascii characters have to get encoded as character references.
  1202. """
  1203. if value is not None:
  1204. value = _escapeAndEncode(value)
  1205. self.writeln("<%s>%s</%s>" % (element, value, element))
  1206. else:
  1207. self.writeln("<%s/>" % element)
  1208. # Regex to find any control chars, except for \t \n and \r
  1209. _controlCharPat = re.compile(
  1210. r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
  1211. r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
  1212. def _escapeAndEncode(text):
  1213. m = _controlCharPat.search(text)
  1214. if m is not None:
  1215. raise ValueError("strings can't contains control characters; "
  1216. "use plistlib.Data instead")
  1217. text = text.replace("\r\n", "\n") # convert DOS line endings
  1218. text = text.replace("\r", "\n") # convert Mac line endings
  1219. text = text.replace("&", "&amp;") # escape '&'
  1220. text = text.replace("<", "&lt;") # escape '<'
  1221. text = text.replace(">", "&gt;") # escape '>'
  1222. return text.encode("ascii", "xmlcharrefreplace") # encode as ascii with xml character references