ios_wrapper.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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('''<?xml version="1.0" encoding="UTF-8"?>
  86. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  87. <plist version="1.0">
  88. <dict>
  89. <key>com.apple.security.get-task-allow</key>
  90. <true/>
  91. </dict>
  92. </plist>
  93. ''')
  94. else:
  95. sign_file = signs[0]
  96. sign_application(sign_file, app_dir)
  97. make_archive(app_dir, main_out)
  98. def is_exe(fpath):
  99. return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
  100. def copy_nibs(nibs, module_dir, app_dir):
  101. for nib in nibs:
  102. dst = os.path.join(app_dir, os.path.relpath(nib, module_dir))
  103. ensure_dir(os.path.dirname(dst))
  104. shutil.copyfile(nib, dst)
  105. def make_main_plist(inputs, out, replaced_parameters):
  106. united_data = {}
  107. for i in inputs:
  108. united_data.update(plistlib.readPlist(i))
  109. def scan_n_replace(root):
  110. if not isinstance(root, dict):
  111. raise Exception('Invalid state')
  112. for k in root:
  113. if isinstance(root[k], list):
  114. for i in xrange(len(root[k])):
  115. if isinstance(root[k][i], dict):
  116. scan_n_replace(root[k][i])
  117. elif root[k][i] in replaced_parameters:
  118. root[k][i] = replaced_parameters[root[k][i]]
  119. elif isinstance(root[k], dict):
  120. scan_n_replace(root[k])
  121. else:
  122. if root[k] in replaced_parameters:
  123. root[k] = replaced_parameters[root[k]]
  124. scan_n_replace(united_data)
  125. plistlib.writePlist(united_data, out)
  126. subprocess.check_call(['/usr/bin/plutil', '-convert', 'binary1', out])
  127. def link_storyboards(ibtool, archives, app_name, app_dir, flags):
  128. unpacked = []
  129. for arc in archives:
  130. unpacked.append(os.path.splitext(arc)[0] + 'c')
  131. ensure_dir(unpacked[-1])
  132. with tarfile.open(arc) as a:
  133. a.extractall(path=unpacked[-1])
  134. flags += [
  135. '--module', app_name,
  136. '--link', app_dir,
  137. ]
  138. subprocess.check_call([ibtool] + flags +
  139. ['--errors', '--warnings', '--notices', '--output-format', 'human-readable-text'] +
  140. unpacked)
  141. def sign_application(xcent, app_dir):
  142. subprocess.check_call(['/usr/bin/codesign', '--force', '--sign', '-', '--entitlements', xcent, '--timestamp=none', app_dir])
  143. def extract_resources(resources, app_dir, strings=False, sign=False):
  144. for res in resources:
  145. with tarfile.open(res) as tf:
  146. for tfinfo in tf:
  147. tf.extract(tfinfo.name, app_dir)
  148. if strings:
  149. subprocess.check_call(['/usr/bin/plutil', '-convert', 'binary1', os.path.join(app_dir, tfinfo.name)])
  150. if sign:
  151. subprocess.check_call(['/usr/bin/codesign', '--force', '--sign', '-', os.path.join(app_dir, tfinfo.name)])
  152. def make_archive(app_dir, output):
  153. with tarfile.open(output, "w") as tar_handle:
  154. for root, _, files in os.walk(app_dir):
  155. for f in files:
  156. tar_handle.add(os.path.join(root, f), arcname=os.path.join(os.path.basename(app_dir),
  157. os.path.relpath(os.path.join(root, f), app_dir)))
  158. if __name__ == '__main__':
  159. just_do_it(sys.argv[1:])