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