Home | History | Annotate | Download | only in skqp
      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