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