blob: 8dd67d7bad3e4c23137d5f973527f7de581825e1 [file] [log] [blame]
Hal Canary6fe9d0a2018-10-29 16:39:10 -04001#! /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'''
7This script can be run with no arguments, in which case it will produce an
8APK with native libraries for all four architectures: arm, arm64, x86, and
9x64. You can instead list the architectures you want as arguments to this
10script. For example:
11
12 python make_universal_apk.py arm x86
13
14The environment variables ANDROID_NDK and ANDROID_HOME must be set to the
15locations of the Android NDK and SDK.
16
17Additionally, `ninja` should be in your path.
18
19It assumes that the source tree is in the desired state, e.g. by having
20run 'python tools/git-sync-deps' in the root of the skia checkout.
21
22Also:
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
31import os
32import glob
33import re
34import subprocess
35import sys
36import shutil
37
38def 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
49def check_call(cmd, **kwargs):
50 print_cmd(cmd, sys.stdout)
51 return subprocess.check_call(cmd, **kwargs)
52
53def 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
58def check_ninja():
59 with open(os.devnull, 'w') as devnull:
60 return 0 == subprocess.call(['ninja', '--version'],
61 stdout=devnull, stderr=devnull)
62
63def remove(p):
64 if not os.path.islink(p) and os.path.isdir(p):
65 shutil.rmtree(p)
Hal Canary69802c42019-01-11 16:27:35 -050066 elif os.path.lexists(p):
Hal Canary6fe9d0a2018-10-29 16:39:10 -040067 os.remove(p)
68 assert not os.path.exists(p)
69
70skia_to_android_arch_name_map = {'arm' : 'armeabi-v7a',
71 'arm64': 'arm64-v8a' ,
72 'x86' : 'x86' ,
73 'x64' : 'x86_64' }
74
75def 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 Canary69802c42019-01-11 16:27:35 -050087 assert os.path.exists(skia_dir + '/bin/gn') # Did you `tools/git-syc-deps`?
Hal Canary6fe9d0a2018-10-29 16:39:10 -040088 assert architectures
89 assert all(arch in skia_to_android_arch_name_map
90 for arch in architectures)
91
Hal Canary974200c2018-11-09 09:30:47 -050092 for d in [build_dir, final_output_dir]:
93 if not os.path.exists(d):
94 os.makedirs(d)
95
Hal Canary6fe9d0a2018-10-29 16:39:10 -040096 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 Canary6fe9d0a2018-10-29 16:39:10 -0400104 apps_dir + '/skqp/src/main/assets/gmkb']
105 remove(build_dir + '/libs')
Hal Canary6fe9d0a2018-10-29 16:39:10 -0400106 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 Canary69802c42019-01-11 16:27:35 -0500116 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 Canary6fe9d0a2018-10-29 16:39:10 -0400121 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 Canary6fe9d0a2018-10-29 16:39:10 -0400128 else:
129 sys.stderr.write(
130 '\n* * *\n\nNote: SkQP models are missing!!!!\n\n* * *\n\n')
131
Hal Canary6fe9d0a2018-10-29 16:39:10 -0400132 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 Canary4979e722018-12-07 09:52:21 -0500171 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 Canary6fe9d0a2018-10-29 16:39:10 -0400176 sys.stdout.write('* * * COMPLETE * * *\n\n')
177
178def 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 Canary974200c2018-11-09 09:30:47 -0500211 sys.stdout.flush()
Hal Canary6fe9d0a2018-10-29 16:39:10 -0400212 make_apk(architectures,
213 android_ndk,
214 android_home,
215 build_dir,
216 final_output_dir,
217 debug,
218 skia_dir)
219
220if __name__ == '__main__':
221 main()
222