1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5'''
6Script to produce an Android package (.apk) for Fennec.
7'''
8
9from __future__ import absolute_import, print_function
10
11import argparse
12import buildconfig
13import os
14import subprocess
15import sys
16
17from mozpack.copier import Jarrer
18from mozpack.files import (
19    DeflatedFile,
20    File,
21    FileFinder,
22)
23from mozpack.mozjar import JarReader
24import mozpack.path as mozpath
25
26
27def package_fennec_apk(inputs=[], omni_ja=None, classes_dex=None,
28                       lib_dirs=[],
29                       assets_dirs=[],
30                       features_dirs=[],
31                       root_files=[],
32                       verbose=False):
33    jarrer = Jarrer(optimize=False)
34
35    # First, take input files.  The contents of the later files overwrites the
36    # content of earlier files.
37    for input in inputs:
38        jar = JarReader(input)
39        for file in jar:
40            path = file.filename
41            if jarrer.contains(path):
42                jarrer.remove(path)
43            jarrer.add(path, DeflatedFile(file), compress=file.compressed)
44
45    def add(path, file, compress=None):
46        abspath = os.path.abspath(file.path)
47        if verbose:
48            print('Packaging %s from %s' % (path, file.path))
49        if not os.path.exists(abspath):
50            raise ValueError('File %s not found (looked for %s)' % \
51                             (file.path, abspath))
52        if jarrer.contains(path):
53            jarrer.remove(path)
54        jarrer.add(path, file, compress=compress)
55
56    for features_dir in features_dirs:
57        finder = FileFinder(features_dir, find_executables=False)
58        for p, f in finder.find('**'):
59            add(mozpath.join('assets', 'features', p), f, False)
60
61    for assets_dir in assets_dirs:
62        finder = FileFinder(assets_dir, find_executables=False)
63        for p, f in finder.find('**'):
64            compress = None  # Take default from Jarrer.
65            if p.endswith('.so'):
66                # Asset libraries are special.
67                if f.open().read(5)[1:] == '7zXZ':
68                    print('%s is already compressed' % p)
69                    # We need to store (rather than deflate) compressed libraries
70                    # (even if we don't compress them ourselves).
71                    compress = False
72                elif buildconfig.substs.get('XZ'):
73                    cmd = [buildconfig.substs.get('XZ'), '-zkf',
74                           mozpath.join(finder.base, p)]
75
76                    bcj = None
77                    if buildconfig.substs.get('MOZ_THUMB2'):
78                        bcj = '--armthumb'
79                    elif buildconfig.substs.get('CPU_ARCH') == 'arm':
80                        bcj = '--arm'
81                    elif buildconfig.substs.get('CPU_ARCH') == 'x86':
82                        bcj = '--x86'
83
84                    if bcj:
85                        cmd.extend([bcj, '--lzma2'])
86                    print('xz-compressing %s with %s' % (p, ' '.join(cmd)))
87                    subprocess.check_output(cmd)
88                    os.rename(f.path + '.xz', f.path)
89                    compress = False
90
91            add(mozpath.join('assets', p), f, compress=compress)
92
93    for lib_dir in lib_dirs:
94        finder = FileFinder(lib_dir, find_executables=False)
95        for p, f in finder.find('**'):
96            add(mozpath.join('lib', p), f)
97
98    for root_file in root_files:
99        add(os.path.basename(root_file), File(root_file))
100
101    if omni_ja:
102        add(mozpath.join('assets', 'omni.ja'), File(omni_ja), compress=False)
103
104    if classes_dex:
105        add('classes.dex', File(classes_dex))
106
107    return jarrer
108
109
110def main(args):
111    parser = argparse.ArgumentParser()
112    parser.add_argument('--verbose', '-v', default=False, action='store_true',
113                        help='be verbose')
114    parser.add_argument('--inputs', nargs='+',
115                        help='Input skeleton AP_ or APK file(s).')
116    parser.add_argument('-o', '--output',
117                        help='Output APK file.')
118    parser.add_argument('--omnijar', default=None,
119                        help='Optional omni.ja to pack into APK file.')
120    parser.add_argument('--classes-dex', default=None,
121                        help='Optional classes.dex to pack into APK file.')
122    parser.add_argument('--lib-dirs', nargs='*', default=[],
123                        help='Optional lib/ dirs to pack into APK file.')
124    parser.add_argument('--assets-dirs', nargs='*', default=[],
125                        help='Optional assets/ dirs to pack into APK file.')
126    parser.add_argument('--features-dirs', nargs='*', default=[],
127                        help='Optional features/ dirs to pack into APK file.')
128    parser.add_argument('--root-files', nargs='*', default=[],
129                        help='Optional files to pack into APK file root.')
130    args = parser.parse_args(args)
131
132    if buildconfig.substs.get('OMNIJAR_NAME') != 'assets/omni.ja':
133        raise ValueError("Don't know how package Fennec APKs when "
134                         " OMNIJAR_NAME is not 'assets/omni.jar'.")
135
136    jarrer = package_fennec_apk(inputs=args.inputs,
137                                omni_ja=args.omnijar,
138                                classes_dex=args.classes_dex,
139                                lib_dirs=args.lib_dirs,
140                                assets_dirs=args.assets_dirs,
141                                features_dirs=args.features_dirs,
142                                root_files=args.root_files,
143                                verbose=args.verbose)
144    jarrer.copy(args.output)
145
146    return 0
147
148
149if __name__ == '__main__':
150    sys.exit(main(sys.argv[1:]))
151