Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / build / android / gyp / package_resources.py
blob195c2a4fba11be3ae617cd06b255a104bd54475e
1 #!/usr/bin/env python
3 # Copyright 2014 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.
7 # pylint: disable=C0301
8 """Package resources into an apk.
10 See https://android.googlesource.com/platform/tools/base/+/master/legacy/ant-tasks/src/main/java/com/android/ant/AaptExecTask.java
11 and
12 https://android.googlesource.com/platform/sdk/+/master/files/ant/build.xml
13 """
14 # pylint: enable=C0301
16 import optparse
17 import os
18 import re
19 import shutil
20 import zipfile
22 from util import build_utils
25 # List is generated from the chrome_apk.apk_intermediates.ap_ via:
26 # unzip -l $FILE_AP_ | cut -c31- | grep res/draw | cut -d'/' -f 2 | sort \
27 # | uniq | grep -- -tvdpi- | cut -c10-
28 # and then manually sorted.
29 # Note that we can't just do a cross-product of dimentions because the filenames
30 # become too big and aapt fails to create the files.
31 # This leaves all default drawables (mdpi) in the main apk. Android gets upset
32 # though if any drawables are missing from the default drawables/ directory.
33 DENSITY_SPLITS = {
34 'hdpi': (
35 'hdpi-v4', # Order matters for output file names.
36 'ldrtl-hdpi-v4',
37 'sw600dp-hdpi-v13',
38 'ldrtl-hdpi-v17',
39 'ldrtl-sw600dp-hdpi-v17',
40 'hdpi-v21',
42 'xhdpi': (
43 'xhdpi-v4',
44 'ldrtl-xhdpi-v4',
45 'sw600dp-xhdpi-v13',
46 'ldrtl-xhdpi-v17',
47 'ldrtl-sw600dp-xhdpi-v17',
48 'xhdpi-v21',
50 'xxhdpi': (
51 'xxhdpi-v4',
52 'ldrtl-xxhdpi-v4',
53 'sw600dp-xxhdpi-v13',
54 'ldrtl-xxhdpi-v17',
55 'ldrtl-sw600dp-xxhdpi-v17',
56 'xxhdpi-v21',
58 'xxxhdpi': (
59 'xxxhdpi-v4',
60 'ldrtl-xxxhdpi-v4',
61 'sw600dp-xxxhdpi-v13',
62 'ldrtl-xxxhdpi-v17',
63 'ldrtl-sw600dp-xxxhdpi-v17',
64 'xxxhdpi-v21',
66 'tvdpi': (
67 'tvdpi-v4',
68 'sw600dp-tvdpi-v13',
69 'ldrtl-sw600dp-tvdpi-v17',
74 def ParseArgs():
75 """Parses command line options.
77 Returns:
78 An options object as from optparse.OptionsParser.parse_args()
79 """
80 parser = optparse.OptionParser()
81 build_utils.AddDepfileOption(parser)
82 parser.add_option('--android-sdk', help='path to the Android SDK folder')
83 parser.add_option('--aapt-path',
84 help='path to the Android aapt tool')
86 parser.add_option('--configuration-name',
87 help='Gyp\'s configuration name (Debug or Release).')
89 parser.add_option('--android-manifest', help='AndroidManifest.xml path')
90 parser.add_option('--version-code', help='Version code for apk.')
91 parser.add_option('--version-name', help='Version name for apk.')
92 parser.add_option(
93 '--shared-resources',
94 action='store_true',
95 help='Make a resource package that can be loaded by a different'
96 'application at runtime to access the package\'s resources.')
97 parser.add_option(
98 '--app-as-shared-lib',
99 action='store_true',
100 help='Make a resource package that can be loaded as shared library')
101 parser.add_option('--resource-zips',
102 help='zip files containing resources to be packaged')
103 parser.add_option('--asset-dir',
104 help='directories containing assets to be packaged')
105 parser.add_option('--no-compress', help='disables compression for the '
106 'given comma separated list of extensions')
107 parser.add_option(
108 '--create-density-splits',
109 action='store_true',
110 help='Enables density splits')
111 parser.add_option('--language-splits',
112 help='GYP list of languages to create splits for')
114 parser.add_option('--apk-path',
115 help='Path to output (partial) apk.')
117 (options, args) = parser.parse_args()
119 if args:
120 parser.error('No positional arguments should be given.')
122 # Check that required options have been provided.
123 required_options = ('android_sdk', 'aapt_path', 'configuration_name',
124 'android_manifest', 'version_code', 'version_name',
125 'apk_path')
127 build_utils.CheckOptions(options, parser, required=required_options)
129 return options
132 def MoveImagesToNonMdpiFolders(res_root):
133 """Move images from drawable-*-mdpi-* folders to drawable-* folders.
135 Why? http://crbug.com/289843
137 for src_dir_name in os.listdir(res_root):
138 src_components = src_dir_name.split('-')
139 if src_components[0] != 'drawable' or 'mdpi' not in src_components:
140 continue
141 src_dir = os.path.join(res_root, src_dir_name)
142 if not os.path.isdir(src_dir):
143 continue
144 dst_components = [c for c in src_components if c != 'mdpi']
145 assert dst_components != src_components
146 dst_dir_name = '-'.join(dst_components)
147 dst_dir = os.path.join(res_root, dst_dir_name)
148 build_utils.MakeDirectory(dst_dir)
149 for src_file_name in os.listdir(src_dir):
150 if not src_file_name.endswith('.png'):
151 continue
152 src_file = os.path.join(src_dir, src_file_name)
153 dst_file = os.path.join(dst_dir, src_file_name)
154 assert not os.path.lexists(dst_file)
155 shutil.move(src_file, dst_file)
158 def PackageArgsForExtractedZip(d):
159 """Returns the aapt args for an extracted resources zip.
161 A resources zip either contains the resources for a single target or for
162 multiple targets. If it is multiple targets merged into one, the actual
163 resource directories will be contained in the subdirectories 0, 1, 2, ...
165 subdirs = [os.path.join(d, s) for s in os.listdir(d)]
166 subdirs = [s for s in subdirs if os.path.isdir(s)]
167 is_multi = '0' in [os.path.basename(s) for s in subdirs]
168 if is_multi:
169 res_dirs = sorted(subdirs, key=lambda p : int(os.path.basename(p)))
170 else:
171 res_dirs = [d]
172 package_command = []
173 for d in res_dirs:
174 MoveImagesToNonMdpiFolders(d)
175 package_command += ['-S', d]
176 return package_command
179 def RenameDensitySplits(apk_path):
180 """Renames all density splits to have shorter / predictable names."""
181 for density, config in DENSITY_SPLITS.iteritems():
182 src_path = '%s_%s' % (apk_path, '_'.join(config))
183 dst_path = '%s_%s' % (apk_path, density)
184 if src_path != dst_path:
185 if os.path.exists(dst_path):
186 os.unlink(dst_path)
187 os.rename(src_path, dst_path)
190 def CheckForMissedConfigs(apk_path, check_density, languages):
191 """Raises an exception if apk_path contains any unexpected configs."""
192 triggers = []
193 if check_density:
194 triggers.extend(re.compile('-%s' % density) for density in DENSITY_SPLITS)
195 if languages:
196 triggers.extend(re.compile(r'-%s\b' % lang) for lang in languages)
197 with zipfile.ZipFile(apk_path) as main_apk_zip:
198 for name in main_apk_zip.namelist():
199 for trigger in triggers:
200 if trigger.search(name) and not 'mipmap-' in name:
201 raise Exception(('Found config in main apk that should have been ' +
202 'put into a split: %s\nYou need to update ' +
203 'package_resources.py to include this new ' +
204 'config (trigger=%s)') % (name, trigger.pattern))
207 def main():
208 options = ParseArgs()
209 android_jar = os.path.join(options.android_sdk, 'android.jar')
210 aapt = options.aapt_path
212 with build_utils.TempDir() as temp_dir:
213 package_command = [aapt,
214 'package',
215 '--version-code', options.version_code,
216 '--version-name', options.version_name,
217 '-M', options.android_manifest,
218 '--no-crunch',
219 '-f',
220 '--auto-add-overlay',
221 '-I', android_jar,
222 '-F', options.apk_path,
223 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN,
226 if options.no_compress:
227 for ext in options.no_compress.split(','):
228 package_command += ['-0', ext]
229 if options.shared_resources:
230 package_command.append('--shared-lib')
231 if options.app_as_shared_lib:
232 package_command.append('--app-as-shared-lib')
234 if options.asset_dir and os.path.exists(options.asset_dir):
235 package_command += ['-A', options.asset_dir]
237 if options.resource_zips:
238 dep_zips = build_utils.ParseGypList(options.resource_zips)
239 for z in dep_zips:
240 subdir = os.path.join(temp_dir, os.path.basename(z))
241 if os.path.exists(subdir):
242 raise Exception('Resource zip name conflict: ' + os.path.basename(z))
243 build_utils.ExtractAll(z, path=subdir)
244 package_command += PackageArgsForExtractedZip(subdir)
246 if options.create_density_splits:
247 for config in DENSITY_SPLITS.itervalues():
248 package_command.extend(('--split', ','.join(config)))
250 language_splits = None
251 if options.language_splits:
252 language_splits = build_utils.ParseGypList(options.language_splits)
253 for lang in language_splits:
254 package_command.extend(('--split', lang))
256 if 'Debug' in options.configuration_name:
257 package_command += ['--debug-mode']
259 build_utils.CheckOutput(
260 package_command, print_stdout=False, print_stderr=False)
262 if options.create_density_splits or language_splits:
263 CheckForMissedConfigs(
264 options.apk_path, options.create_density_splits, language_splits)
266 if options.create_density_splits:
267 RenameDensitySplits(options.apk_path)
269 if options.depfile:
270 build_utils.WriteDepfile(
271 options.depfile,
272 build_utils.GetPythonDependencies())
275 if __name__ == '__main__':
276 main()