ios_wrapper.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import errno
  2. import json
  3. import os
  4. import shutil
  5. import subprocess
  6. import sys
  7. import tarfile
  8. import plistlib
  9. def ensure_dir(path):
  10. try:
  11. os.makedirs(path)
  12. except OSError as e:
  13. if e.errno != errno.EEXIST or not os.path.isdir(path):
  14. raise
  15. def just_do_it(args):
  16. if not args:
  17. raise Exception('Not enough args!')
  18. parts = [[]]
  19. for arg in args:
  20. if arg == '__DELIM__':
  21. parts.append([])
  22. else:
  23. parts[-1].append(arg)
  24. if len(parts) != 3 or len(parts[0]) != 5:
  25. raise Exception('Bad call')
  26. bin_name, ibtool_path, main_out, app_name, module_dir = parts[0]
  27. bin_name = os.path.basename(bin_name)
  28. inputs, storyboard_user_flags = parts[1:]
  29. plists, storyboards, signs, nibs, resources, signed_resources, plist_jsons, strings = [], [], [], [], [], [], [], []
  30. for i in inputs:
  31. if i.endswith('.plist') or i.endswith('.partial_plist'):
  32. plists.append(i)
  33. elif i.endswith('.compiled_storyboard_tar'):
  34. storyboards.append(i)
  35. elif i.endswith('.xcent'):
  36. signs.append(i)
  37. elif i.endswith('.nib'):
  38. nibs.append(i)
  39. elif i.endswith('.resource_tar'):
  40. resources.append(i)
  41. elif i.endswith('.signed_resource_tar'):
  42. signed_resources.append(i)
  43. elif i.endswith('.plist_json'):
  44. plist_jsons.append(i)
  45. elif i.endswith('.strings_tar'):
  46. strings.append(i)
  47. else:
  48. print >> sys.stderr, 'Unknown input:', i, 'ignoring'
  49. if not plists:
  50. raise Exception("Can't find plist files")
  51. if not plists[0].endswith('.plist'):
  52. print >> sys.stderr, "Main plist file can be defined incorretly"
  53. if not storyboards:
  54. print >> sys.stderr, "Storyboards list are empty"
  55. if len(signs) > 1:
  56. raise Exception("Too many .xcent files")
  57. app_dir = os.path.join(module_dir, app_name + '.app')
  58. ensure_dir(app_dir)
  59. copy_nibs(nibs, module_dir, app_dir)
  60. replaced_parameters = {
  61. 'DEVELOPMENT_LANGUAGE': 'en',
  62. 'EXECUTABLE_NAME': bin_name,
  63. 'PRODUCT_BUNDLE_IDENTIFIER': 'Yandex.' + app_name,
  64. 'PRODUCT_NAME': app_name,
  65. }
  66. replaced_templates = {}
  67. for plist_json in plist_jsons:
  68. with open(plist_json) as jsonfile:
  69. for k, v in json.loads(jsonfile.read()).items():
  70. replaced_parameters[k] = v
  71. for k, v in replaced_parameters.items():
  72. replaced_templates['$(' + k + ')'] = v
  73. replaced_templates['${' + k + '}'] = v
  74. make_main_plist(plists, os.path.join(app_dir, 'Info.plist'), replaced_templates)
  75. link_storyboards(ibtool_path, storyboards, app_name, app_dir, storyboard_user_flags)
  76. if resources:
  77. extract_resources(resources, app_dir)
  78. if signed_resources:
  79. extract_resources(signed_resources, app_dir, sign=True)
  80. if strings:
  81. extract_resources(strings, app_dir, strings=True)
  82. if not signs:
  83. sign_file = os.path.join(module_dir, app_name + '.xcent')
  84. with open(sign_file, 'w') as f:
  85. f.write(
  86. '''<?xml version="1.0" encoding="UTF-8"?>
  87. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  88. <plist version="1.0">
  89. <dict>
  90. <key>com.apple.security.get-task-allow</key>
  91. <true/>
  92. </dict>
  93. </plist>
  94. '''
  95. )
  96. else:
  97. sign_file = signs[0]
  98. sign_application(sign_file, app_dir)
  99. make_archive(app_dir, main_out)
  100. def is_exe(fpath):
  101. return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
  102. def copy_nibs(nibs, module_dir, app_dir):
  103. for nib in nibs:
  104. dst = os.path.join(app_dir, os.path.relpath(nib, module_dir))
  105. ensure_dir(os.path.dirname(dst))
  106. shutil.copyfile(nib, dst)
  107. def make_main_plist(inputs, out, replaced_parameters):
  108. united_data = {}
  109. for i in inputs:
  110. united_data.update(plistlib.readPlist(i))
  111. def scan_n_replace(root):
  112. if not isinstance(root, dict):
  113. raise Exception('Invalid state')
  114. for k in root:
  115. if isinstance(root[k], list):
  116. for i in xrange(len(root[k])):
  117. if isinstance(root[k][i], dict):
  118. scan_n_replace(root[k][i])
  119. elif root[k][i] in replaced_parameters:
  120. root[k][i] = replaced_parameters[root[k][i]]
  121. elif isinstance(root[k], dict):
  122. scan_n_replace(root[k])
  123. else:
  124. if root[k] in replaced_parameters:
  125. root[k] = replaced_parameters[root[k]]
  126. scan_n_replace(united_data)
  127. plistlib.writePlist(united_data, out)
  128. subprocess.check_call(['/usr/bin/plutil', '-convert', 'binary1', out])
  129. def link_storyboards(ibtool, archives, app_name, app_dir, flags):
  130. unpacked = []
  131. for arc in archives:
  132. unpacked.append(os.path.splitext(arc)[0] + 'c')
  133. ensure_dir(unpacked[-1])
  134. with tarfile.open(arc) as a:
  135. a.extractall(path=unpacked[-1])
  136. flags += [
  137. '--module',
  138. app_name,
  139. '--link',
  140. app_dir,
  141. ]
  142. subprocess.check_call(
  143. [ibtool] + flags + ['--errors', '--warnings', '--notices', '--output-format', 'human-readable-text'] + unpacked
  144. )
  145. def sign_application(xcent, app_dir):
  146. subprocess.check_call(
  147. ['/usr/bin/codesign', '--force', '--sign', '-', '--entitlements', xcent, '--timestamp=none', app_dir]
  148. )
  149. def extract_resources(resources, app_dir, strings=False, sign=False):
  150. for res in resources:
  151. with tarfile.open(res) as tf:
  152. for tfinfo in tf:
  153. tf.extract(tfinfo.name, app_dir)
  154. if strings:
  155. subprocess.check_call(
  156. ['/usr/bin/plutil', '-convert', 'binary1', os.path.join(app_dir, tfinfo.name)]
  157. )
  158. if sign:
  159. subprocess.check_call(
  160. ['/usr/bin/codesign', '--force', '--sign', '-', os.path.join(app_dir, tfinfo.name)]
  161. )
  162. def make_archive(app_dir, output):
  163. with tarfile.open(output, "w") as tar_handle:
  164. for root, _, files in os.walk(app_dir):
  165. for f in files:
  166. tar_handle.add(
  167. os.path.join(root, f),
  168. arcname=os.path.join(os.path.basename(app_dir), os.path.relpath(os.path.join(root, f), app_dir)),
  169. )
  170. if __name__ == '__main__':
  171. just_do_it(sys.argv[1:])