xref: /aosp_15_r20/external/skia/tools/skqp/create_apk.py (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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