| #! /usr/bin/env python |
| # Copyright 2018 Google LLC. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| ''' |
| This script can be run with no arguments, in which case it will produce an |
| APK with native libraries for all four architectures: arm, arm64, x86, and |
| x64. You can instead list the architectures you want as arguments to this |
| script. For example: |
| |
| python make_universal_apk.py arm x86 |
| |
| The environment variables ANDROID_NDK and ANDROID_HOME must be set to the |
| locations of the Android NDK and SDK. |
| |
| Additionally, `ninja` should be in your path. |
| |
| It assumes that the source tree is in the desired state, e.g. by having |
| run 'python tools/git-sync-deps' in the root of the skia checkout. |
| |
| Also: |
| * If the environment variable SKQP_BUILD_DIR is set, many of the |
| intermediate build objects will be places here. |
| * If the environment variable SKQP_OUTPUT_DIR is set, the final APK |
| will be placed in this directory. |
| * If the environment variable SKQP_DEBUG is set, Skia will be compiled |
| in debug mode. |
| ''' |
| |
| import os |
| import glob |
| import re |
| import subprocess |
| import sys |
| import shutil |
| |
| def print_cmd(cmd, o): |
| m = re.compile('[^A-Za-z0-9_./-]') |
| o.write('+ ') |
| for c in cmd: |
| if m.search(c) is not None: |
| o.write(repr(c) + ' ') |
| else: |
| o.write(c + ' ') |
| o.write('\n') |
| o.flush() |
| |
| def check_call(cmd, **kwargs): |
| print_cmd(cmd, sys.stdout) |
| return subprocess.check_call(cmd, **kwargs) |
| |
| def find_name(searchpath, filename): |
| for dirpath, _, filenames in os.walk(searchpath): |
| if filename in filenames: |
| yield os.path.join(dirpath, filename) |
| |
| def check_ninja(): |
| with open(os.devnull, 'w') as devnull: |
| return 0 == subprocess.call(['ninja', '--version'], |
| stdout=devnull, stderr=devnull) |
| |
| def remove(p): |
| if not os.path.islink(p) and os.path.isdir(p): |
| shutil.rmtree(p) |
| elif os.path.lexists(p): |
| os.remove(p) |
| assert not os.path.exists(p) |
| |
| skia_to_android_arch_name_map = {'arm' : 'armeabi-v7a', |
| 'arm64': 'arm64-v8a' , |
| 'x86' : 'x86' , |
| 'x64' : 'x86_64' } |
| |
| def make_apk(architectures, |
| android_ndk, |
| android_home, |
| build_dir, |
| final_output_dir, |
| debug, |
| skia_dir): |
| assert '/' in [os.sep, os.altsep] # 'a/b' over os.path.join('a', 'b') |
| assert check_ninja() |
| assert os.path.exists(android_ndk) |
| assert os.path.exists(android_home) |
| assert os.path.exists(skia_dir) |
| assert os.path.exists(skia_dir + '/bin/gn') # Did you `tools/git-syc-deps`? |
| assert architectures |
| assert all(arch in skia_to_android_arch_name_map |
| for arch in architectures) |
| |
| for d in [build_dir, final_output_dir]: |
| if not os.path.exists(d): |
| os.makedirs(d) |
| |
| os.chdir(skia_dir) |
| apps_dir = 'platform_tools/android/apps' |
| |
| # These are the locations in the tree where the gradle needs or will create |
| # not-checked-in files. Treat them specially to keep the tree clean. |
| build_paths = [apps_dir + '/.gradle', |
| apps_dir + '/skqp/build', |
| apps_dir + '/skqp/src/main/libs', |
| apps_dir + '/skqp/src/main/assets/gmkb'] |
| remove(build_dir + '/libs') |
| for path in build_paths: |
| remove(path) |
| newdir = os.path.join(build_dir, os.path.basename(path)) |
| if not os.path.exists(newdir): |
| os.makedirs(newdir) |
| try: |
| os.symlink(os.path.relpath(newdir, os.path.dirname(path)), path) |
| except OSError: |
| pass |
| |
| resources_path = apps_dir + '/skqp/src/main/assets/resources' |
| remove(resources_path) |
| os.symlink('../../../../../../../resources', resources_path) |
| build_paths.append(resources_path) |
| |
| app = 'skqp' |
| lib = 'libskqp_app.so' |
| |
| shutil.rmtree(apps_dir + '/%s/src/main/libs' % app, True) |
| |
| if os.path.exists(apps_dir + '/skqp/src/main/assets/files.checksum'): |
| check_call([sys.executable, 'tools/skqp/download_model']) |
| else: |
| sys.stderr.write( |
| '\n* * *\n\nNote: SkQP models are missing!!!!\n\n* * *\n\n') |
| |
| for arch in architectures: |
| build = os.path.join(build_dir, arch) |
| gn_args = [android_ndk, '--arch', arch] |
| if debug: |
| build += '-debug' |
| gn_args += ['--debug'] |
| check_call([sys.executable, 'tools/skqp/generate_gn_args', build] |
| + gn_args) |
| check_call(['bin/gn', 'gen', build]) |
| check_call(['ninja', '-C', build, lib]) |
| dst = apps_dir + '/%s/src/main/libs/%s' % ( |
| app, skia_to_android_arch_name_map[arch]) |
| if not os.path.isdir(dst): |
| os.makedirs(dst) |
| shutil.copy(os.path.join(build, lib), dst) |
| |
| apk_build_dir = apps_dir + '/%s/build/outputs/apk' % app |
| shutil.rmtree(apk_build_dir, True) # force rebuild |
| |
| # Why does gradlew need to be called from this directory? |
| os.chdir('platform_tools/android') |
| env_copy = os.environ.copy() |
| env_copy['ANDROID_HOME'] = android_home |
| check_call(['apps/gradlew', '-p' 'apps/' + app, '-P', 'suppressNativeBuild', |
| ':%s:assembleUniversalDebug' % app], env=env_copy) |
| os.chdir(skia_dir) |
| |
| apk_name = app + "-universal-debug.apk" |
| |
| apk_list = list(find_name(apk_build_dir, apk_name)) |
| assert len(apk_list) == 1 |
| |
| out = os.path.join(final_output_dir, apk_name) |
| shutil.move(apk_list[0], out) |
| sys.stdout.write(out + '\n') |
| |
| for path in build_paths: |
| remove(path) |
| |
| arches = '_'.join(sorted(architectures)) |
| copy = os.path.join(final_output_dir, "%s-%s-debug.apk" % (app, arches)) |
| shutil.copyfile(out, copy) |
| sys.stdout.write(copy + '\n') |
| |
| sys.stdout.write('* * * COMPLETE * * *\n\n') |
| |
| def main(): |
| def error(s): |
| sys.stderr.write(s + __doc__) |
| sys.exit(1) |
| if not check_ninja(): |
| error('`ninja` is not in the path.\n') |
| for var in ['ANDROID_NDK', 'ANDROID_HOME']: |
| if not os.path.exists(os.environ.get(var, '')): |
| error('Environment variable `%s` is not set.\n' % var) |
| architectures = sys.argv[1:] |
| for arg in sys.argv[1:]: |
| if arg not in skia_to_android_arch_name_map: |
| error('Argument %r is not in %r\n' % |
| (arg, skia_to_android_arch_name_map.keys())) |
| if not architectures: |
| architectures = skia_to_android_arch_name_map.keys() |
| skia_dir = os.path.abspath( |
| os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) |
| default_build = os.path.join(skia_dir, 'out', 'skqp') |
| build_dir = os.path.abspath(os.environ.get('SKQP_BUILD_DIR', default_build)) |
| final_output_dir = os.path.abspath( |
| os.environ.get('SKQP_OUTPUT_DIR', default_build)) |
| debug = bool(os.environ.get('SKQP_DEBUG', '')) |
| android_ndk = os.path.abspath(os.environ['ANDROID_NDK']) |
| android_home = os.path.abspath(os.environ['ANDROID_HOME']) |
| |
| for k, v in [('ANDROID_NDK', android_ndk), |
| ('ANDROID_HOME', android_home), |
| ('skia root directory', skia_dir), |
| ('SKQP_OUTPUT_DIR', final_output_dir), |
| ('SKQP_BUILD_DIR', build_dir), |
| ('Architectures', architectures)]: |
| sys.stdout.write('%s = %r\n' % (k, v)) |
| sys.stdout.flush() |
| make_apk(architectures, |
| android_ndk, |
| android_home, |
| build_dir, |
| final_output_dir, |
| debug, |
| skia_dir) |
| |
| if __name__ == '__main__': |
| main() |
| |