1#!/usr/bin/env python
2#
3# Copyright 2017 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Creates an Android .aar file."""
8
9import argparse
10import os
11import posixpath
12import shutil
13import sys
14import tempfile
15import zipfile
16
17import filter_zip
18from util import build_utils
19
20
21_ANDROID_BUILD_DIR = os.path.dirname(os.path.dirname(__file__))
22
23
24def _MergeRTxt(r_paths, include_globs):
25  """Merging the given R.txt files and returns them as a string."""
26  all_lines = set()
27  for r_path in r_paths:
28    if include_globs and not build_utils.MatchesGlob(r_path, include_globs):
29      continue
30    with open(r_path) as f:
31      all_lines.update(f.readlines())
32  return ''.join(sorted(all_lines))
33
34
35def _MergeProguardConfigs(proguard_configs):
36  """Merging the given proguard config files and returns them as a string."""
37  ret = []
38  for config in proguard_configs:
39    ret.append('# FROM: {}'.format(config))
40    with open(config) as f:
41      ret.append(f.read())
42  return '\n'.join(ret)
43
44
45def _AddResources(aar_zip, resource_zips, include_globs):
46  """Adds all resource zips to the given aar_zip.
47
48  Ensures all res/values/* files have unique names by prefixing them.
49  """
50  for i, path in enumerate(resource_zips):
51    if include_globs and not build_utils.MatchesGlob(path, include_globs):
52      continue
53    with zipfile.ZipFile(path) as res_zip:
54      for info in res_zip.infolist():
55        data = res_zip.read(info)
56        dirname, basename = posixpath.split(info.filename)
57        if 'values' in dirname:
58          root, ext = os.path.splitext(basename)
59          basename = '{}_{}{}'.format(root, i, ext)
60          info.filename = posixpath.join(dirname, basename)
61        info.filename = posixpath.join('res', info.filename)
62        aar_zip.writestr(info, data)
63
64
65def main(args):
66  args = build_utils.ExpandFileArgs(args)
67  parser = argparse.ArgumentParser()
68  build_utils.AddDepfileOption(parser)
69  parser.add_argument('--output', required=True, help='Path to output aar.')
70  parser.add_argument('--jars', required=True, help='GN list of jar inputs.')
71  parser.add_argument('--dependencies-res-zips', required=True,
72                      help='GN list of resource zips')
73  parser.add_argument('--r-text-files', required=True,
74                      help='GN list of R.txt files to merge')
75  parser.add_argument('--proguard-configs', required=True,
76                      help='GN list of ProGuard flag files to merge.')
77  parser.add_argument(
78      '--android-manifest',
79      help='Path to AndroidManifest.xml to include.',
80      default=os.path.join(_ANDROID_BUILD_DIR, 'AndroidManifest.xml'))
81  parser.add_argument('--native-libraries', default='',
82                      help='GN list of native libraries. If non-empty then '
83                      'ABI must be specified.')
84  parser.add_argument('--abi',
85                      help='ABI (e.g. armeabi-v7a) for native libraries.')
86  parser.add_argument(
87      '--jar-excluded-globs',
88      help='GN-list of globs for paths to exclude in jar.')
89  parser.add_argument(
90      '--jar-included-globs',
91      help='GN-list of globs for paths to include in jar.')
92  parser.add_argument(
93      '--resource-included-globs',
94      help='GN-list of globs for paths to include in R.txt and resources zips.')
95
96  options = parser.parse_args(args)
97
98  if options.native_libraries and not options.abi:
99    parser.error('You must provide --abi if you have native libs')
100
101  options.jars = build_utils.ParseGnList(options.jars)
102  options.dependencies_res_zips = build_utils.ParseGnList(
103      options.dependencies_res_zips)
104  options.r_text_files = build_utils.ParseGnList(options.r_text_files)
105  options.proguard_configs = build_utils.ParseGnList(options.proguard_configs)
106  options.native_libraries = build_utils.ParseGnList(options.native_libraries)
107  options.jar_excluded_globs = build_utils.ParseGnList(
108      options.jar_excluded_globs)
109  options.jar_included_globs = build_utils.ParseGnList(
110      options.jar_included_globs)
111  options.resource_included_globs = build_utils.ParseGnList(
112      options.resource_included_globs)
113
114  with tempfile.NamedTemporaryFile(delete=False) as staging_file:
115    try:
116      with zipfile.ZipFile(staging_file.name, 'w') as z:
117        build_utils.AddToZipHermetic(
118            z, 'AndroidManifest.xml', src_path=options.android_manifest)
119
120        path_transform = filter_zip.CreatePathTransform(
121            options.jar_excluded_globs, options.jar_included_globs, [])
122        with tempfile.NamedTemporaryFile() as jar_file:
123          build_utils.MergeZips(
124              jar_file.name, options.jars, path_transform=path_transform)
125          build_utils.AddToZipHermetic(z, 'classes.jar', src_path=jar_file.name)
126
127        build_utils.AddToZipHermetic(
128            z,
129            'R.txt',
130            data=_MergeRTxt(options.r_text_files,
131                            options.resource_included_globs))
132        build_utils.AddToZipHermetic(z, 'public.txt', data='')
133
134        if options.proguard_configs:
135          build_utils.AddToZipHermetic(
136              z, 'proguard.txt',
137              data=_MergeProguardConfigs(options.proguard_configs))
138
139        _AddResources(z, options.dependencies_res_zips,
140                      options.resource_included_globs)
141
142        for native_library in options.native_libraries:
143          libname = os.path.basename(native_library)
144          build_utils.AddToZipHermetic(
145              z, os.path.join('jni', options.abi, libname),
146              src_path=native_library)
147    except:
148      os.unlink(staging_file.name)
149      raise
150    shutil.move(staging_file.name, options.output)
151
152  if options.depfile:
153    all_inputs = (options.jars + options.dependencies_res_zips +
154                  options.r_text_files + options.proguard_configs)
155    build_utils.WriteDepfile(options.depfile, options.output, all_inputs)
156
157
158if __name__ == '__main__':
159  main(sys.argv[1:])
160