• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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'''
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)
66    elif os.path.lexists(p):
67        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)
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    aosp_mode = os.path.exists('MODULE_LICENSE_BSD')
102    build_paths = [apps_dir + '/.gradle',
103                   apps_dir + '/skqp/build',
104                   apps_dir + '/skqp/src/main/libs']
105    if not aosp_mode:
106        build_paths.append(apps_dir + '/skqp/src/main/assets/gmkb')
107    remove(build_dir + '/libs')
108    for path in build_paths:
109        remove(path)
110        newdir = os.path.join(build_dir, os.path.basename(path))
111        if not os.path.exists(newdir):
112            os.makedirs(newdir)
113        try:
114            os.symlink(os.path.relpath(newdir, os.path.dirname(path)), path)
115        except OSError:
116            pass
117
118    if not aosp_mode:
119        resources_path = apps_dir + '/skqp/src/main/assets/resources'
120        remove(resources_path)
121        os.symlink('../../../../../../../resources', resources_path)
122        build_paths.append(resources_path)
123
124    app = 'skqp'
125    lib = 'libskqp_app.so'
126
127    shutil.rmtree(apps_dir + '/%s/src/main/libs' % app, True)
128
129    if not aosp_mode:
130        if os.path.exists(apps_dir + '/skqp/src/main/assets/files.checksum'):
131            check_call([sys.executable, 'tools/skqp/download_model'])
132        else:
133            sys.stderr.write(
134                    '\n* * *\n\nNote: SkQP models are missing!!!!\n\n* * *\n\n')
135    if aosp_mode:
136        with open('include/config/SkUserConfig.h') as f:
137            user_config = f.readlines()
138        with open('include/config/SkUserConfig.h', 'w') as o:
139            for line in user_config:
140                m = re.match(r'^#define\s+([A-Za-z0-9_]+)(|\s.*)$', line)
141                if m:
142                    o.write('#ifndef %s\n%s\n#endif\n' % (m.group(1), m.group(0).strip()))
143                else:
144                    o.write(line)
145
146    for arch in architectures:
147        build = os.path.join(build_dir, arch)
148        gn_args = [android_ndk, '--arch', arch]
149        if debug:
150            build += '-debug'
151            gn_args += ['--debug']
152        check_call([sys.executable, 'tools/skqp/generate_gn_args', build]
153                   + gn_args)
154        check_call(['bin/gn', 'gen', build])
155        check_call(['ninja', '-C', build, lib])
156        dst = apps_dir + '/%s/src/main/libs/%s' % (
157                app, skia_to_android_arch_name_map[arch])
158        if not os.path.isdir(dst):
159            os.makedirs(dst)
160        shutil.copy(os.path.join(build, lib), dst)
161
162    if aosp_mode:
163        subprocess.call('git', 'checkout', 'HEAD', 'include/config/SkUserConfig.h')
164
165    apk_build_dir = apps_dir + '/%s/build/outputs/apk' % app
166    shutil.rmtree(apk_build_dir, True)  # force rebuild
167
168    # Why does gradlew need to be called from this directory?
169    os.chdir('platform_tools/android')
170    env_copy = os.environ.copy()
171    env_copy['ANDROID_HOME'] = android_home
172    check_call(['apps/gradlew', '-p' 'apps/' + app, '-P', 'suppressNativeBuild',
173                ':%s:assembleUniversalDebug' % app], env=env_copy)
174    os.chdir(skia_dir)
175
176    apk_name = app + "-universal-debug.apk"
177
178    apk_list = list(find_name(apk_build_dir, apk_name))
179    assert len(apk_list) == 1
180
181    out = os.path.join(final_output_dir, apk_name)
182    shutil.move(apk_list[0], out)
183    sys.stdout.write(out + '\n')
184
185    for path in build_paths:
186        remove(path)
187
188    arches = '_'.join(sorted(architectures))
189    copy = os.path.join(final_output_dir, "%s-%s-debug.apk" % (app, arches))
190    shutil.copyfile(out, copy)
191    sys.stdout.write(copy + '\n')
192
193    sys.stdout.write('* * * COMPLETE * * *\n\n')
194
195def main():
196    def error(s):
197        sys.stderr.write(s + __doc__)
198        sys.exit(1)
199    if not check_ninja():
200        error('`ninja` is not in the path.\n')
201    for var in ['ANDROID_NDK', 'ANDROID_HOME']:
202        if not os.path.exists(os.environ.get(var, '')):
203            error('Environment variable `%s` is not set.\n' % var)
204    architectures = sys.argv[1:]
205    for arg in sys.argv[1:]:
206        if arg not in skia_to_android_arch_name_map:
207            error('Argument %r is not in %r\n' %
208                  (arg, skia_to_android_arch_name_map.keys()))
209    if not architectures:
210        architectures = skia_to_android_arch_name_map.keys()
211    skia_dir = os.path.abspath(
212            os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
213    default_build = os.path.join(skia_dir, 'out', 'skqp')
214    build_dir = os.path.abspath(os.environ.get('SKQP_BUILD_DIR', default_build))
215    final_output_dir = os.path.abspath(
216            os.environ.get('SKQP_OUTPUT_DIR', default_build))
217    debug = bool(os.environ.get('SKQP_DEBUG', ''))
218    android_ndk = os.path.abspath(os.environ['ANDROID_NDK'])
219    android_home = os.path.abspath(os.environ['ANDROID_HOME'])
220
221    for k, v in [('ANDROID_NDK', android_ndk),
222                 ('ANDROID_HOME', android_home),
223                 ('skia root directory', skia_dir),
224                 ('SKQP_OUTPUT_DIR', final_output_dir),
225                 ('SKQP_BUILD_DIR', build_dir),
226                 ('Architectures', architectures)]:
227        sys.stdout.write('%s = %r\n' % (k, v))
228    sys.stdout.flush()
229    make_apk(architectures,
230             android_ndk,
231             android_home,
232             build_dir,
233             final_output_dir,
234             debug,
235             skia_dir)
236
237if __name__ == '__main__':
238    main()
239
240