ios_wrapper.py 6.5 KB

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