Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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) |
Hal Canary | 69802c4 | 2019-01-11 16:27:35 -0500 | [diff] [blame] | 66 | elif os.path.lexists(p): |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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) |
Hal Canary | 69802c4 | 2019-01-11 16:27:35 -0500 | [diff] [blame] | 87 | assert os.path.exists(skia_dir + '/bin/gn') # Did you `tools/git-syc-deps`? |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 88 | assert architectures |
| 89 | assert all(arch in skia_to_android_arch_name_map |
| 90 | for arch in architectures) |
| 91 | |
Hal Canary | 974200c | 2018-11-09 09:30:47 -0500 | [diff] [blame] | 92 | for d in [build_dir, final_output_dir]: |
| 93 | if not os.path.exists(d): |
| 94 | os.makedirs(d) |
| 95 | |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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', |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 104 | apps_dir + '/skqp/src/main/assets/gmkb'] |
| 105 | remove(build_dir + '/libs') |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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 | |
Hal Canary | 69802c4 | 2019-01-11 16:27:35 -0500 | [diff] [blame] | 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 | |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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']) |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 128 | else: |
| 129 | sys.stderr.write( |
| 130 | '\n* * *\n\nNote: SkQP models are missing!!!!\n\n* * *\n\n') |
| 131 | |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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 | |
Hal Canary | 4979e72 | 2018-12-07 09:52:21 -0500 | [diff] [blame] | 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 | |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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)) |
Hal Canary | 974200c | 2018-11-09 09:30:47 -0500 | [diff] [blame] | 211 | sys.stdout.flush() |
Hal Canary | 6fe9d0a | 2018-10-29 16:39:10 -0400 | [diff] [blame] | 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 | |