make_universal_apk.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. #! /usr/bin/env python
  2. # Copyright 2018 Google LLC.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. '''
  6. This script can be run with no arguments, in which case it will produce an
  7. APK with native libraries for all four architectures: arm, arm64, x86, and
  8. x64. You can instead list the architectures you want as arguments to this
  9. script. For example:
  10. python make_universal_apk.py arm x86
  11. The environment variables ANDROID_NDK and ANDROID_HOME must be set to the
  12. locations of the Android NDK and SDK.
  13. Additionally, `ninja` should be in your path.
  14. It assumes that the source tree is in the desired state, e.g. by having
  15. run 'python tools/git-sync-deps' in the root of the skia checkout.
  16. Also:
  17. * If the environment variable SKQP_BUILD_DIR is set, many of the
  18. intermediate build objects will be places here.
  19. * If the environment variable SKQP_OUTPUT_DIR is set, the final APK
  20. will be placed in this directory.
  21. * If the environment variable SKQP_DEBUG is set, Skia will be compiled
  22. in debug mode.
  23. '''
  24. import os
  25. import glob
  26. import re
  27. import subprocess
  28. import sys
  29. import shutil
  30. def print_cmd(cmd, o):
  31. m = re.compile('[^A-Za-z0-9_./-]')
  32. o.write('+ ')
  33. for c in cmd:
  34. if m.search(c) is not None:
  35. o.write(repr(c) + ' ')
  36. else:
  37. o.write(c + ' ')
  38. o.write('\n')
  39. o.flush()
  40. def check_call(cmd, **kwargs):
  41. print_cmd(cmd, sys.stdout)
  42. return subprocess.check_call(cmd, **kwargs)
  43. def find_name(searchpath, filename):
  44. for dirpath, _, filenames in os.walk(searchpath):
  45. if filename in filenames:
  46. yield os.path.join(dirpath, filename)
  47. def check_ninja():
  48. with open(os.devnull, 'w') as devnull:
  49. return 0 == subprocess.call(['ninja', '--version'],
  50. stdout=devnull, stderr=devnull)
  51. def remove(p):
  52. if not os.path.islink(p) and os.path.isdir(p):
  53. shutil.rmtree(p)
  54. elif os.path.lexists(p):
  55. os.remove(p)
  56. assert not os.path.exists(p)
  57. skia_to_android_arch_name_map = {'arm' : 'armeabi-v7a',
  58. 'arm64': 'arm64-v8a' ,
  59. 'x86' : 'x86' ,
  60. 'x64' : 'x86_64' }
  61. def make_apk(architectures,
  62. android_ndk,
  63. android_home,
  64. build_dir,
  65. final_output_dir,
  66. debug,
  67. skia_dir):
  68. assert '/' in [os.sep, os.altsep] # 'a/b' over os.path.join('a', 'b')
  69. assert check_ninja()
  70. assert os.path.exists(android_ndk)
  71. assert os.path.exists(android_home)
  72. assert os.path.exists(skia_dir)
  73. assert os.path.exists(skia_dir + '/bin/gn') # Did you `tools/git-syc-deps`?
  74. assert architectures
  75. assert all(arch in skia_to_android_arch_name_map
  76. for arch in architectures)
  77. for d in [build_dir, final_output_dir]:
  78. if not os.path.exists(d):
  79. os.makedirs(d)
  80. os.chdir(skia_dir)
  81. apps_dir = 'platform_tools/android/apps'
  82. # These are the locations in the tree where the gradle needs or will create
  83. # not-checked-in files. Treat them specially to keep the tree clean.
  84. build_paths = [apps_dir + '/.gradle',
  85. apps_dir + '/skqp/build',
  86. apps_dir + '/skqp/src/main/libs',
  87. apps_dir + '/skqp/src/main/assets/gmkb']
  88. remove(build_dir + '/libs')
  89. for path in build_paths:
  90. remove(path)
  91. newdir = os.path.join(build_dir, os.path.basename(path))
  92. if not os.path.exists(newdir):
  93. os.makedirs(newdir)
  94. try:
  95. os.symlink(os.path.relpath(newdir, os.path.dirname(path)), path)
  96. except OSError:
  97. pass
  98. resources_path = apps_dir + '/skqp/src/main/assets/resources'
  99. remove(resources_path)
  100. os.symlink('../../../../../../../resources', resources_path)
  101. build_paths.append(resources_path)
  102. app = 'skqp'
  103. lib = 'libskqp_app.so'
  104. shutil.rmtree(apps_dir + '/%s/src/main/libs' % app, True)
  105. if os.path.exists(apps_dir + '/skqp/src/main/assets/files.checksum'):
  106. check_call([sys.executable, 'tools/skqp/download_model'])
  107. else:
  108. sys.stderr.write(
  109. '\n* * *\n\nNote: SkQP models are missing!!!!\n\n* * *\n\n')
  110. for arch in architectures:
  111. build = os.path.join(build_dir, arch)
  112. gn_args = [android_ndk, '--arch', arch]
  113. if debug:
  114. build += '-debug'
  115. gn_args += ['--debug']
  116. check_call([sys.executable, 'tools/skqp/generate_gn_args', build]
  117. + gn_args)
  118. check_call(['bin/gn', 'gen', build])
  119. check_call(['ninja', '-C', build, lib])
  120. dst = apps_dir + '/%s/src/main/libs/%s' % (
  121. app, skia_to_android_arch_name_map[arch])
  122. if not os.path.isdir(dst):
  123. os.makedirs(dst)
  124. shutil.copy(os.path.join(build, lib), dst)
  125. apk_build_dir = apps_dir + '/%s/build/outputs/apk' % app
  126. shutil.rmtree(apk_build_dir, True) # force rebuild
  127. # Why does gradlew need to be called from this directory?
  128. os.chdir('platform_tools/android')
  129. env_copy = os.environ.copy()
  130. env_copy['ANDROID_HOME'] = android_home
  131. check_call(['apps/gradlew', '-p' 'apps/' + app, '-P', 'suppressNativeBuild',
  132. ':%s:assembleUniversalDebug' % app], env=env_copy)
  133. os.chdir(skia_dir)
  134. apk_name = app + "-universal-debug.apk"
  135. apk_list = list(find_name(apk_build_dir, apk_name))
  136. assert len(apk_list) == 1
  137. out = os.path.join(final_output_dir, apk_name)
  138. shutil.move(apk_list[0], out)
  139. sys.stdout.write(out + '\n')
  140. for path in build_paths:
  141. remove(path)
  142. arches = '_'.join(sorted(architectures))
  143. copy = os.path.join(final_output_dir, "%s-%s-debug.apk" % (app, arches))
  144. shutil.copyfile(out, copy)
  145. sys.stdout.write(copy + '\n')
  146. sys.stdout.write('* * * COMPLETE * * *\n\n')
  147. def main():
  148. def error(s):
  149. sys.stderr.write(s + __doc__)
  150. sys.exit(1)
  151. if not check_ninja():
  152. error('`ninja` is not in the path.\n')
  153. for var in ['ANDROID_NDK', 'ANDROID_HOME']:
  154. if not os.path.exists(os.environ.get(var, '')):
  155. error('Environment variable `%s` is not set.\n' % var)
  156. architectures = sys.argv[1:]
  157. for arg in sys.argv[1:]:
  158. if arg not in skia_to_android_arch_name_map:
  159. error('Argument %r is not in %r\n' %
  160. (arg, skia_to_android_arch_name_map.keys()))
  161. if not architectures:
  162. architectures = skia_to_android_arch_name_map.keys()
  163. skia_dir = os.path.abspath(
  164. os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
  165. default_build = os.path.join(skia_dir, 'out', 'skqp')
  166. build_dir = os.path.abspath(os.environ.get('SKQP_BUILD_DIR', default_build))
  167. final_output_dir = os.path.abspath(
  168. os.environ.get('SKQP_OUTPUT_DIR', default_build))
  169. debug = bool(os.environ.get('SKQP_DEBUG', ''))
  170. android_ndk = os.path.abspath(os.environ['ANDROID_NDK'])
  171. android_home = os.path.abspath(os.environ['ANDROID_HOME'])
  172. for k, v in [('ANDROID_NDK', android_ndk),
  173. ('ANDROID_HOME', android_home),
  174. ('skia root directory', skia_dir),
  175. ('SKQP_OUTPUT_DIR', final_output_dir),
  176. ('SKQP_BUILD_DIR', build_dir),
  177. ('Architectures', architectures)]:
  178. sys.stdout.write('%s = %r\n' % (k, v))
  179. sys.stdout.flush()
  180. make_apk(architectures,
  181. android_ndk,
  182. android_home,
  183. build_dir,
  184. final_output_dir,
  185. debug,
  186. skia_dir)
  187. if __name__ == '__main__':
  188. main()