1cb93a386Sopenharmony_ci#! /usr/bin/env python 2cb93a386Sopenharmony_ci# Copyright 2019 Google LLC. 3cb93a386Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be 4cb93a386Sopenharmony_ci# found in the LICENSE file. 5cb93a386Sopenharmony_ci 6cb93a386Sopenharmony_ci''' 7cb93a386Sopenharmony_ciThis script can be run with no arguments, in which case it will produce an 8cb93a386Sopenharmony_ciAPK with native libraries for all four architectures: arm, arm64, x86, and 9cb93a386Sopenharmony_cix64. You can instead list the architectures you want as arguments to this 10cb93a386Sopenharmony_ciscript. For example: 11cb93a386Sopenharmony_ci 12cb93a386Sopenharmony_ci python create_apk.py arm x86 13cb93a386Sopenharmony_ci 14cb93a386Sopenharmony_ciThe environment variables ANDROID_NDK and ANDROID_HOME must be set to the 15cb93a386Sopenharmony_cilocations of the Android NDK and SDK. 16cb93a386Sopenharmony_ci 17cb93a386Sopenharmony_ciAdditionally, `ninja` should be in your path. 18cb93a386Sopenharmony_ci 19cb93a386Sopenharmony_ciIt assumes that the source tree is in the desired state, e.g. by having 20cb93a386Sopenharmony_cirun 'python tools/git-sync-deps' in the root of the skia checkout. 21cb93a386Sopenharmony_ci 22cb93a386Sopenharmony_ciWe also assume that the 'resources' directory has been copied to 23cb93a386Sopenharmony_ci'platform_tools/android/apps/skqp/src/main/assets', and the 24cb93a386Sopenharmony_ci'tools/skqp/download_model' script has been run. 25cb93a386Sopenharmony_ci 26cb93a386Sopenharmony_ciAlso: 27cb93a386Sopenharmony_ci * If the environment variable SKQP_BUILD_DIR is set, many of the 28cb93a386Sopenharmony_ci intermediate build objects will be placed here. 29cb93a386Sopenharmony_ci * If the environment variable SKQP_OUTPUT_DIR is set, the final APK 30cb93a386Sopenharmony_ci will be placed in this directory. 31cb93a386Sopenharmony_ci * If the environment variable SKQP_DEBUG is set, Skia will be compiled 32cb93a386Sopenharmony_ci in debug mode. 33cb93a386Sopenharmony_ci''' 34cb93a386Sopenharmony_ci 35cb93a386Sopenharmony_ciimport os 36cb93a386Sopenharmony_ciimport re 37cb93a386Sopenharmony_ciimport subprocess 38cb93a386Sopenharmony_ciimport sys 39cb93a386Sopenharmony_ciimport shutil 40cb93a386Sopenharmony_ciimport time 41cb93a386Sopenharmony_ci 42cb93a386Sopenharmony_ciimport skqp_gn_args 43cb93a386Sopenharmony_ci 44cb93a386Sopenharmony_cidef print_cmd(cmd, o): 45cb93a386Sopenharmony_ci m = re.compile('[^A-Za-z0-9_./-]') 46cb93a386Sopenharmony_ci o.write('+ ') 47cb93a386Sopenharmony_ci for c in cmd: 48cb93a386Sopenharmony_ci if m.search(c) is not None: 49cb93a386Sopenharmony_ci o.write(repr(c) + ' ') 50cb93a386Sopenharmony_ci else: 51cb93a386Sopenharmony_ci o.write(c + ' ') 52cb93a386Sopenharmony_ci o.write('\n') 53cb93a386Sopenharmony_ci o.flush() 54cb93a386Sopenharmony_ci 55cb93a386Sopenharmony_cidef check_call(cmd, **kwargs): 56cb93a386Sopenharmony_ci print_cmd(cmd, sys.stdout) 57cb93a386Sopenharmony_ci return subprocess.check_call(cmd, **kwargs) 58cb93a386Sopenharmony_ci 59cb93a386Sopenharmony_cidef find_name(searchpath, filename): 60cb93a386Sopenharmony_ci for dirpath, _, filenames in os.walk(searchpath): 61cb93a386Sopenharmony_ci if filename in filenames: 62cb93a386Sopenharmony_ci yield os.path.join(dirpath, filename) 63cb93a386Sopenharmony_ci 64cb93a386Sopenharmony_cidef check_ninja(): 65cb93a386Sopenharmony_ci with open(os.devnull, 'w') as devnull: 66cb93a386Sopenharmony_ci return subprocess.call(['ninja', '--version'], 67cb93a386Sopenharmony_ci stdout=devnull, stderr=devnull) == 0 68cb93a386Sopenharmony_ci 69cb93a386Sopenharmony_cidef remove(p): 70cb93a386Sopenharmony_ci if not os.path.islink(p) and os.path.isdir(p): 71cb93a386Sopenharmony_ci shutil.rmtree(p) 72cb93a386Sopenharmony_ci elif os.path.lexists(p): 73cb93a386Sopenharmony_ci os.remove(p) 74cb93a386Sopenharmony_ci assert not os.path.exists(p) 75cb93a386Sopenharmony_ci 76cb93a386Sopenharmony_cidef makedirs(dst): 77cb93a386Sopenharmony_ci if not os.path.exists(dst): 78cb93a386Sopenharmony_ci os.makedirs(dst) 79cb93a386Sopenharmony_ci 80cb93a386Sopenharmony_ciclass RemoveFiles(object): 81cb93a386Sopenharmony_ci def __init__(self, *args): 82cb93a386Sopenharmony_ci self.args = args 83cb93a386Sopenharmony_ci def __enter__(self): 84cb93a386Sopenharmony_ci pass 85cb93a386Sopenharmony_ci def __exit__(self, a, b, c): 86cb93a386Sopenharmony_ci for arg in self.args: 87cb93a386Sopenharmony_ci remove(arg) 88cb93a386Sopenharmony_ci 89cb93a386Sopenharmony_ciclass ChDir(object): 90cb93a386Sopenharmony_ci def __init__(self, d): 91cb93a386Sopenharmony_ci self.orig = os.getcwd() 92cb93a386Sopenharmony_ci os.chdir(d) 93cb93a386Sopenharmony_ci def __enter__(self): 94cb93a386Sopenharmony_ci pass 95cb93a386Sopenharmony_ci def __exit__(self, a, b, c): 96cb93a386Sopenharmony_ci os.chdir(self.orig) 97cb93a386Sopenharmony_ci 98cb93a386Sopenharmony_cidef make_symlinked_subdir(target, working_dir): 99cb93a386Sopenharmony_ci newdir = os.path.join(working_dir, os.path.basename(target)) 100cb93a386Sopenharmony_ci makedirs(newdir) 101cb93a386Sopenharmony_ci os.symlink(os.path.relpath(newdir, os.path.dirname(target)), target) 102cb93a386Sopenharmony_ci 103cb93a386Sopenharmony_cidef accept_android_license(android_home): 104cb93a386Sopenharmony_ci proc = subprocess.Popen( 105cb93a386Sopenharmony_ci [android_home + '/tools/bin/sdkmanager', '--licenses'], 106cb93a386Sopenharmony_ci stdin=subprocess.PIPE) 107cb93a386Sopenharmony_ci while proc.poll() is None: 108cb93a386Sopenharmony_ci proc.stdin.write('y\n') 109cb93a386Sopenharmony_ci time.sleep(1) 110cb93a386Sopenharmony_ci 111cb93a386Sopenharmony_ci# pylint: disable=bad-whitespace 112cb93a386Sopenharmony_ciskia_to_android_arch_name_map = {'arm' : 'armeabi-v7a', 113cb93a386Sopenharmony_ci 'arm64': 'arm64-v8a' , 114cb93a386Sopenharmony_ci 'x86' : 'x86' , 115cb93a386Sopenharmony_ci 'x64' : 'x86_64' } 116cb93a386Sopenharmony_ci 117cb93a386Sopenharmony_cidef create_apk_impl(opts): 118cb93a386Sopenharmony_ci build_dir, final_output_dir = opts.build_dir, opts.final_output_dir 119cb93a386Sopenharmony_ci 120cb93a386Sopenharmony_ci assert os.path.exists('bin/gn') # Did you `tools/git-syc-deps`? 121cb93a386Sopenharmony_ci 122cb93a386Sopenharmony_ci for d in [build_dir, final_output_dir]: 123cb93a386Sopenharmony_ci makedirs(d) 124cb93a386Sopenharmony_ci 125cb93a386Sopenharmony_ci apps_dir = 'platform_tools/android/apps' 126cb93a386Sopenharmony_ci app = 'skqp' 127cb93a386Sopenharmony_ci lib = 'lib%s_app.so' % app 128cb93a386Sopenharmony_ci 129cb93a386Sopenharmony_ci # These are the locations in the tree where the gradle needs or will create 130cb93a386Sopenharmony_ci # not-checked-in files. Treat them specially to keep the tree clean. 131cb93a386Sopenharmony_ci remove(build_dir + '/libs') 132cb93a386Sopenharmony_ci build_paths = [apps_dir + '/.gradle', 133cb93a386Sopenharmony_ci apps_dir + '/' + app + '/build', 134cb93a386Sopenharmony_ci apps_dir + '/' + app + '/src/main/libs'] 135cb93a386Sopenharmony_ci for path in build_paths: 136cb93a386Sopenharmony_ci remove(path) 137cb93a386Sopenharmony_ci try: 138cb93a386Sopenharmony_ci make_symlinked_subdir(path, build_dir) 139cb93a386Sopenharmony_ci except OSError: 140cb93a386Sopenharmony_ci sys.stderr.write('failed to create symlink "%s"\n' % path) 141cb93a386Sopenharmony_ci 142cb93a386Sopenharmony_ci lib_dir = '%s/%s/src/main/libs' % (apps_dir, app) 143cb93a386Sopenharmony_ci apk_build_dir = '%s/%s/build/outputs/apk' % (apps_dir, app) 144cb93a386Sopenharmony_ci for d in [lib_dir, apk_build_dir]: 145cb93a386Sopenharmony_ci shutil.rmtree(d, True) # force rebuild 146cb93a386Sopenharmony_ci 147cb93a386Sopenharmony_ci with RemoveFiles(*build_paths): 148cb93a386Sopenharmony_ci for arch in opts.architectures: 149cb93a386Sopenharmony_ci build = os.path.join(build_dir, arch) 150cb93a386Sopenharmony_ci gn_args = opts.gn_args(arch) 151cb93a386Sopenharmony_ci args = ' '.join('%s=%s' % (k, v) for k, v in gn_args.items()) 152cb93a386Sopenharmony_ci check_call(['bin/gn', 'gen', build, '--args=' + args]) 153cb93a386Sopenharmony_ci try: 154cb93a386Sopenharmony_ci check_call(['ninja', '-C', build, lib]) 155cb93a386Sopenharmony_ci except subprocess.CalledProcessError: 156cb93a386Sopenharmony_ci check_call(['ninja', '-C', build, '-t', 'clean']) 157cb93a386Sopenharmony_ci check_call(['ninja', '-C', build, lib]) 158cb93a386Sopenharmony_ci dst = '%s/%s' % (lib_dir, skia_to_android_arch_name_map[arch]) 159cb93a386Sopenharmony_ci makedirs(dst) 160cb93a386Sopenharmony_ci shutil.copy(os.path.join(build, lib), dst) 161cb93a386Sopenharmony_ci 162cb93a386Sopenharmony_ci accept_android_license(opts.android_home) 163cb93a386Sopenharmony_ci env_copy = os.environ.copy() 164cb93a386Sopenharmony_ci env_copy['ANDROID_HOME'] = opts.android_home 165cb93a386Sopenharmony_ci env_copy['ANDROID_NDK_HOME'] = opts.android_ndk 166cb93a386Sopenharmony_ci # Why does gradlew need to be called from this directory? 167cb93a386Sopenharmony_ci check_call(['apps/gradlew', '-p' 'apps/' + app, 168cb93a386Sopenharmony_ci '-P', 'suppressNativeBuild', 169cb93a386Sopenharmony_ci ':%s:assembleUniversalDebug' % app], 170cb93a386Sopenharmony_ci env=env_copy, cwd='platform_tools/android') 171cb93a386Sopenharmony_ci 172cb93a386Sopenharmony_ci apk_name = app + "-universal-debug.apk" 173cb93a386Sopenharmony_ci 174cb93a386Sopenharmony_ci apk_list = list(find_name(apk_build_dir, apk_name)) 175cb93a386Sopenharmony_ci assert len(apk_list) == 1 176cb93a386Sopenharmony_ci 177cb93a386Sopenharmony_ci out = os.path.join(final_output_dir, apk_name) 178cb93a386Sopenharmony_ci shutil.move(apk_list[0], out) 179cb93a386Sopenharmony_ci sys.stdout.write(out + '\n') 180cb93a386Sopenharmony_ci 181cb93a386Sopenharmony_ci arches = '_'.join(sorted(opts.architectures)) 182cb93a386Sopenharmony_ci copy = os.path.join(final_output_dir, "%s-%s-debug.apk" % (app, arches)) 183cb93a386Sopenharmony_ci shutil.copyfile(out, copy) 184cb93a386Sopenharmony_ci sys.stdout.write(copy + '\n') 185cb93a386Sopenharmony_ci 186cb93a386Sopenharmony_ci sys.stdout.write('* * * COMPLETE * * *\n\n') 187cb93a386Sopenharmony_ci 188cb93a386Sopenharmony_ci 189cb93a386Sopenharmony_cidef create_apk(opts): 190cb93a386Sopenharmony_ci skia_dir = os.path.abspath(os.path.dirname(__file__) + '/../..') 191cb93a386Sopenharmony_ci assert os.path.exists(skia_dir) 192cb93a386Sopenharmony_ci with ChDir(skia_dir): 193cb93a386Sopenharmony_ci create_apk_impl(opts) 194cb93a386Sopenharmony_ci 195cb93a386Sopenharmony_ciclass SkQP_Build_Options(object): 196cb93a386Sopenharmony_ci def __init__(self): 197cb93a386Sopenharmony_ci assert '/' in [os.sep, os.altsep] # 'a/b' over os.path.join('a', 'b') 198cb93a386Sopenharmony_ci self.error = '' 199cb93a386Sopenharmony_ci if not check_ninja(): 200cb93a386Sopenharmony_ci self.error += '`ninja` is not in the path.\n' 201cb93a386Sopenharmony_ci for var in ['ANDROID_NDK', 'ANDROID_HOME']: 202cb93a386Sopenharmony_ci if not os.path.exists(os.environ.get(var, '')): 203cb93a386Sopenharmony_ci self.error += 'Environment variable `%s` is not set.\n' % var 204cb93a386Sopenharmony_ci self.android_ndk = os.path.abspath(os.environ['ANDROID_NDK']) 205cb93a386Sopenharmony_ci self.android_home = os.path.abspath(os.environ['ANDROID_HOME']) 206cb93a386Sopenharmony_ci args = sys.argv[1:] 207cb93a386Sopenharmony_ci for arg in args: 208cb93a386Sopenharmony_ci if arg not in skia_to_android_arch_name_map: 209cb93a386Sopenharmony_ci self.error += ('Argument %r is not in %r\n' % 210cb93a386Sopenharmony_ci (arg, skia_to_android_arch_name_map.keys())) 211cb93a386Sopenharmony_ci self.architectures = args if args else skia_to_android_arch_name_map.keys() 212cb93a386Sopenharmony_ci default_build = os.path.dirname(__file__) + '/../../out/skqp' 213cb93a386Sopenharmony_ci self.build_dir = os.path.abspath(os.environ.get('SKQP_BUILD_DIR', default_build)) 214cb93a386Sopenharmony_ci self.final_output_dir = os.path.abspath(os.environ.get('SKQP_OUTPUT_DIR', default_build)) 215cb93a386Sopenharmony_ci self.debug = bool(os.environ.get('SKQP_DEBUG', '')) 216cb93a386Sopenharmony_ci 217cb93a386Sopenharmony_ci def gn_args(self, arch): 218cb93a386Sopenharmony_ci return skqp_gn_args.GetGNArgs(arch, self.android_ndk, self.debug, 26) 219cb93a386Sopenharmony_ci 220cb93a386Sopenharmony_ci def write(self, o): 221cb93a386Sopenharmony_ci for k, v in [('ANDROID_NDK', self.android_ndk), 222cb93a386Sopenharmony_ci ('ANDROID_HOME', self.android_home), 223cb93a386Sopenharmony_ci ('SKQP_OUTPUT_DIR', self.final_output_dir), 224cb93a386Sopenharmony_ci ('SKQP_BUILD_DIR', self.build_dir), 225cb93a386Sopenharmony_ci ('SKQP_DEBUG', self.debug), 226cb93a386Sopenharmony_ci ('Architectures', self.architectures)]: 227cb93a386Sopenharmony_ci o.write('%s = %r\n' % (k, v)) 228cb93a386Sopenharmony_ci o.flush() 229cb93a386Sopenharmony_ci 230cb93a386Sopenharmony_cidef main(): 231cb93a386Sopenharmony_ci options = SkQP_Build_Options() 232cb93a386Sopenharmony_ci if options.error: 233cb93a386Sopenharmony_ci sys.stderr.write(options.error + __doc__) 234cb93a386Sopenharmony_ci sys.exit(1) 235cb93a386Sopenharmony_ci options.write(sys.stdout) 236cb93a386Sopenharmony_ci create_apk(options) 237cb93a386Sopenharmony_ci 238cb93a386Sopenharmony_ciif __name__ == '__main__': 239cb93a386Sopenharmony_ci main() 240