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
12 https://android.googlesource.com/platform/sdk/+/master/files/ant/build.xml
14 # pylint: enable=C0301
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.
35 'hdpi-v4', # Order matters for output file names.
39 'ldrtl-sw600dp-hdpi-v17',
47 'ldrtl-sw600dp-xhdpi-v17',
55 'ldrtl-sw600dp-xxhdpi-v17',
61 'sw600dp-xxxhdpi-v13',
63 'ldrtl-sw600dp-xxxhdpi-v17',
69 'ldrtl-sw600dp-tvdpi-v17',
75 """Parses command line options.
78 An options object as from optparse.OptionsParser.parse_args()
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.')
95 help='Make a resource package that can be loaded by a different'
96 'application at runtime to access the package\'s resources.')
98 '--app-as-shared-lib',
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')
108 '--create-density-splits',
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()
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',
127 build_utils
.CheckOptions(options
, parser
, required
=required_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
:
141 src_dir
= os
.path
.join(res_root
, src_dir_name
)
142 if not os
.path
.isdir(src_dir
):
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'):
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
]
169 res_dirs
= sorted(subdirs
, key
=lambda p
: int(os
.path
.basename(p
)))
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
):
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."""
194 triggers
.extend(re
.compile('-%s' % density
) for density
in DENSITY_SPLITS
)
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
))
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
,
215 '--version-code', options
.version_code
,
216 '--version-name', options
.version_name
,
217 '-M', options
.android_manifest
,
220 '--auto-add-overlay',
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
)
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
)
270 build_utils
.WriteDepfile(
272 build_utils
.GetPythonDependencies())
275 if __name__
== '__main__':