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
21 from util
import build_utils
24 # List is generated from the chrome_apk.apk_intermediates.ap_ via:
25 # unzip -l $FILE_AP_ | cut -c31- | grep res/draw | cut -d'/' -f 2 | sort \
26 # | uniq | grep -- -tvdpi- | cut -c10-
27 # and then manually sorted.
28 # Note that we can't just do a cross-product of dimentions because the filenames
29 # become too big and aapt fails to create the files.
30 # This leaves all default drawables (mdpi) in the main apk. Android gets upset
31 # though if any drawables are missing from the default drawables/ directory.
34 'hdpi-v4', # Order matters for output file names.
38 'ldrtl-sw600dp-hdpi-v17',
46 'ldrtl-sw600dp-xhdpi-v17',
54 'ldrtl-sw600dp-xxhdpi-v17',
60 'ldrtl-sw600dp-tvdpi-v17',
66 """Parses command line options.
69 An options object as from optparse.OptionsParser.parse_args()
71 parser
= optparse
.OptionParser()
72 build_utils
.AddDepfileOption(parser
)
73 parser
.add_option('--android-sdk', help='path to the Android SDK folder')
74 parser
.add_option('--android-sdk-tools',
75 help='path to the Android SDK build tools folder')
77 parser
.add_option('--configuration-name',
78 help='Gyp\'s configuration name (Debug or Release).')
80 parser
.add_option('--android-manifest', help='AndroidManifest.xml path')
81 parser
.add_option('--version-code', help='Version code for apk.')
82 parser
.add_option('--version-name', help='Version name for apk.')
86 help='Make a resource package that can be loaded by a different'
87 'application at runtime to access the package\'s resources.')
88 parser
.add_option('--resource-zips',
89 help='zip files containing resources to be packaged')
90 parser
.add_option('--asset-dir',
91 help='directories containing assets to be packaged')
92 parser
.add_option('--no-compress', help='disables compression for the '
93 'given comma separated list of extensions')
95 '--create-density-splits',
97 help='Enables density splits')
99 parser
.add_option('--apk-path',
100 help='Path to output (partial) apk.')
102 (options
, args
) = parser
.parse_args()
105 parser
.error('No positional arguments should be given.')
107 # Check that required options have been provided.
108 required_options
= ('android_sdk', 'android_sdk_tools', 'configuration_name',
109 'android_manifest', 'version_code', 'version_name',
112 build_utils
.CheckOptions(options
, parser
, required
=required_options
)
117 def MoveImagesToNonMdpiFolders(res_root
):
118 """Move images from drawable-*-mdpi-* folders to drawable-* folders.
120 Why? http://crbug.com/289843
122 for src_dir_name
in os
.listdir(res_root
):
123 src_components
= src_dir_name
.split('-')
124 if src_components
[0] != 'drawable' or 'mdpi' not in src_components
:
126 src_dir
= os
.path
.join(res_root
, src_dir_name
)
127 if not os
.path
.isdir(src_dir
):
129 dst_components
= [c
for c
in src_components
if c
!= 'mdpi']
130 assert dst_components
!= src_components
131 dst_dir_name
= '-'.join(dst_components
)
132 dst_dir
= os
.path
.join(res_root
, dst_dir_name
)
133 build_utils
.MakeDirectory(dst_dir
)
134 for src_file_name
in os
.listdir(src_dir
):
135 if not src_file_name
.endswith('.png'):
137 src_file
= os
.path
.join(src_dir
, src_file_name
)
138 dst_file
= os
.path
.join(dst_dir
, src_file_name
)
139 assert not os
.path
.lexists(dst_file
)
140 shutil
.move(src_file
, dst_file
)
143 def PackageArgsForExtractedZip(d
):
144 """Returns the aapt args for an extracted resources zip.
146 A resources zip either contains the resources for a single target or for
147 multiple targets. If it is multiple targets merged into one, the actual
148 resource directories will be contained in the subdirectories 0, 1, 2, ...
150 subdirs
= [os
.path
.join(d
, s
) for s
in os
.listdir(d
)]
151 subdirs
= [s
for s
in subdirs
if os
.path
.isdir(s
)]
152 is_multi
= '0' in [os
.path
.basename(s
) for s
in subdirs
]
154 res_dirs
= sorted(subdirs
, key
=lambda p
: int(os
.path
.basename(p
)))
159 MoveImagesToNonMdpiFolders(d
)
160 package_command
+= ['-S', d
]
161 return package_command
164 def RenameDensitySplits(apk_path
):
165 """Renames all density splits to have shorter / predictable names."""
166 for density
, config
in DENSITY_SPLITS
.iteritems():
167 src_path
= '%s_%s' % (apk_path
, '_'.join(config
))
168 dst_path
= '%s-%s' % (apk_path
, density
)
169 if os
.path
.exists(dst_path
):
171 os
.rename(src_path
, dst_path
)
174 def CheckDensityMissedConfigs(apk_path
):
175 """Raises an exception if apk_path contains any density-specifc files."""
176 triggers
= ['-%s' % density
for density
in DENSITY_SPLITS
]
177 with zipfile
.ZipFile(apk_path
) as main_apk_zip
:
178 for name
in main_apk_zip
.namelist():
179 for trigger
in triggers
:
180 if trigger
in name
and not 'mipmap-' in name
:
181 raise Exception(('Found density in main apk that should have been ' +
182 'put into a split: %s\nYou need to update ' +
183 'package_resources.py to include this new ' +
188 options
= ParseArgs()
189 android_jar
= os
.path
.join(options
.android_sdk
, 'android.jar')
190 aapt
= os
.path
.join(options
.android_sdk_tools
, 'aapt')
192 with build_utils
.TempDir() as temp_dir
:
193 package_command
= [aapt
,
195 '--version-code', options
.version_code
,
196 '--version-name', options
.version_name
,
197 '-M', options
.android_manifest
,
200 '--auto-add-overlay',
202 '-F', options
.apk_path
,
203 '--ignore-assets', build_utils
.AAPT_IGNORE_PATTERN
,
206 if options
.no_compress
:
207 for ext
in options
.no_compress
.split(','):
208 package_command
+= ['-0', ext
]
209 if options
.shared_resources
:
210 package_command
.append('--shared-lib')
212 if options
.asset_dir
and os
.path
.exists(options
.asset_dir
):
213 package_command
+= ['-A', options
.asset_dir
]
215 if options
.resource_zips
:
216 dep_zips
= build_utils
.ParseGypList(options
.resource_zips
)
218 subdir
= os
.path
.join(temp_dir
, os
.path
.basename(z
))
219 if os
.path
.exists(subdir
):
220 raise Exception('Resource zip name conflict: ' + os
.path
.basename(z
))
221 build_utils
.ExtractAll(z
, path
=subdir
)
222 package_command
+= PackageArgsForExtractedZip(subdir
)
224 if options
.create_density_splits
:
225 for config
in DENSITY_SPLITS
.itervalues():
226 package_command
.extend(('--split', ','.join(config
)))
228 if 'Debug' in options
.configuration_name
:
229 package_command
+= ['--debug-mode']
231 build_utils
.CheckOutput(
232 package_command
, print_stdout
=False, print_stderr
=False)
234 if options
.create_density_splits
:
235 CheckDensityMissedConfigs(options
.apk_path
)
236 RenameDensitySplits(options
.apk_path
)
239 build_utils
.WriteDepfile(
241 build_utils
.GetPythonDependencies())
244 if __name__
== '__main__':