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