1 #!/usr/bin/env python 2 # 3 # Copyright (c) 2012 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 """Process Android resources to generate R.java, and prepare for packaging. 8 9 This will crunch images and generate v14 compatible resources 10 (see generate_v14_compatible_resources.py). 11 """ 12 13 import optparse 14 import os 15 import re 16 import zipfile 17 18 import generate_v14_compatible_resources 19 20 from util import build_utils 21 22 def ParseArgs(): 23 """Parses command line options. 24 25 Returns: 26 An options object as from optparse.OptionsParser.parse_args() 27 """ 28 parser = optparse.OptionParser() 29 30 parser.add_option('--android-sdk', help='path to the Android SDK folder') 31 parser.add_option('--android-sdk-tools', 32 help='path to the Android SDK build tools folder') 33 parser.add_option('--non-constant-id', action='store_true') 34 35 parser.add_option('--android-manifest', help='AndroidManifest.xml path') 36 parser.add_option('--custom-package', help='Java package for R.java') 37 38 parser.add_option('--resource-dirs', 39 help='Directories containing resources of this target.') 40 parser.add_option('--dependencies-res-zips', 41 help='Resources from dependents.') 42 43 parser.add_option('--R-dir', help='directory to hold generated R.java') 44 parser.add_option('--resource-zip-out', 45 help='Path for output zipped resources.') 46 47 parser.add_option('--proguard-file', 48 help='Path to proguard.txt generated file') 49 50 parser.add_option( 51 '--v14-verify-only', 52 action='store_true', 53 help='Do not generate v14 resources. Instead, just verify that the ' 54 'resources are already compatible with v14, i.e. they don\'t use ' 55 'attributes that cause crashes on certain devices.') 56 57 parser.add_option( 58 '--extra-res-packages', 59 help='Additional package names to generate R.java files for') 60 parser.add_option( 61 '--extra-r-text-files', 62 help='For each additional package, the R.txt file should contain a ' 63 'list of resources to be included in the R.java file in the format ' 64 'generated by aapt') 65 66 parser.add_option('--stamp', help='File to touch on success') 67 68 (options, args) = parser.parse_args() 69 70 if args: 71 parser.error('No positional arguments should be given.') 72 73 # Check that required options have been provided. 74 required_options = ( 75 'android_sdk', 76 'android_sdk_tools', 77 'android_manifest', 78 'dependencies_res_zips', 79 'resource_dirs', 80 'resource_zip_out', 81 'R_dir', 82 ) 83 build_utils.CheckOptions(options, parser, required=required_options) 84 85 return options 86 87 88 def CreateExtraRJavaFiles( 89 r_dir, extra_packages, extra_r_text_files): 90 if len(extra_packages) != len(extra_r_text_files): 91 raise Exception('--extra-res-packages and --extra-r-text-files' 92 'should have the same length') 93 94 java_files = build_utils.FindInDirectory(r_dir, "R.java") 95 if len(java_files) != 1: 96 return 97 r_java_file = java_files[0] 98 r_java_contents = open(r_java_file).read() 99 100 for package in extra_packages: 101 package_r_java_dir = os.path.join(r_dir, *package.split('.')) 102 build_utils.MakeDirectory(package_r_java_dir) 103 package_r_java_path = os.path.join(package_r_java_dir, 'R.java') 104 open(package_r_java_path, 'w').write( 105 re.sub(r'package [.\w]*;', 'package %s;' % package, r_java_contents)) 106 # TODO(cjhopman): These extra package's R.java files should be filtered to 107 # only contain the resources listed in their R.txt files. At this point, we 108 # have already compiled those other libraries, so doing this would only 109 # affect how the code in this .apk target could refer to the resources. 110 111 112 113 114 115 116 def DidCrunchFail(returncode, stderr): 117 """Determines whether aapt crunch failed from its return code and output. 118 119 Because aapt's return code cannot be trusted, any output to stderr is 120 an indication that aapt has failed (http://crbug.com/314885), except 121 lines that contain "libpng warning", which is a known non-error condition 122 (http://crbug.com/364355). 123 """ 124 if returncode != 0: 125 return True 126 for line in stderr.splitlines(): 127 if line and not 'libpng warning' in line: 128 return True 129 return False 130 131 132 def main(): 133 options = ParseArgs() 134 android_jar = os.path.join(options.android_sdk, 'android.jar') 135 aapt = os.path.join(options.android_sdk_tools, 'aapt') 136 137 build_utils.DeleteDirectory(options.R_dir) 138 build_utils.MakeDirectory(options.R_dir) 139 140 with build_utils.TempDir() as temp_dir: 141 deps_dir = os.path.join(temp_dir, 'deps') 142 build_utils.MakeDirectory(deps_dir) 143 v14_dir = os.path.join(temp_dir, 'v14') 144 build_utils.MakeDirectory(v14_dir) 145 146 input_resource_dirs = build_utils.ParseGypList(options.resource_dirs) 147 148 for resource_dir in input_resource_dirs: 149 generate_v14_compatible_resources.GenerateV14Resources( 150 resource_dir, 151 v14_dir, 152 options.v14_verify_only) 153 154 # Generate R.java. This R.java contains non-final constants and is used only 155 # while compiling the library jar (e.g. chromium_content.jar). When building 156 # an apk, a new R.java file with the correct resource -> ID mappings will be 157 # generated by merging the resources from all libraries and the main apk 158 # project. 159 package_command = [aapt, 160 'package', 161 '-m', 162 '-M', options.android_manifest, 163 '--auto-add-overlay', 164 '-I', android_jar, 165 '--output-text-symbols', options.R_dir, 166 '-J', options.R_dir] 167 168 for d in input_resource_dirs: 169 package_command += ['-S', d] 170 171 dep_zips = build_utils.ParseGypList(options.dependencies_res_zips) 172 for z in dep_zips: 173 subdir = os.path.join(deps_dir, os.path.basename(z)) 174 if os.path.exists(subdir): 175 raise Exception('Resource zip name conflict: ' + os.path.basename(z)) 176 build_utils.ExtractAll(z, path=subdir) 177 package_command += ['-S', subdir] 178 179 if options.non_constant_id: 180 package_command.append('--non-constant-id') 181 if options.custom_package: 182 package_command += ['--custom-package', options.custom_package] 183 if options.proguard_file: 184 package_command += ['-G', options.proguard_file] 185 build_utils.CheckOutput(package_command, print_stderr=False) 186 187 if options.extra_res_packages: 188 CreateExtraRJavaFiles( 189 options.R_dir, 190 build_utils.ParseGypList(options.extra_res_packages), 191 build_utils.ParseGypList(options.extra_r_text_files)) 192 193 # This is the list of directories with resources to put in the final .zip 194 # file. The order of these is important so that crunched/v14 resources 195 # override the normal ones. 196 zip_resource_dirs = input_resource_dirs + [v14_dir] 197 198 base_crunch_dir = os.path.join(temp_dir, 'crunch') 199 200 # Crunch image resources. This shrinks png files and is necessary for 201 # 9-patch images to display correctly. 'aapt crunch' accepts only a single 202 # directory at a time and deletes everything in the output directory. 203 for idx, d in enumerate(input_resource_dirs): 204 crunch_dir = os.path.join(base_crunch_dir, str(idx)) 205 build_utils.MakeDirectory(crunch_dir) 206 zip_resource_dirs.append(crunch_dir) 207 aapt_cmd = [aapt, 208 'crunch', 209 '-C', crunch_dir, 210 '-S', d] 211 build_utils.CheckOutput(aapt_cmd, fail_func=DidCrunchFail) 212 213 # Python zipfile does not provide a way to replace a file (it just writes 214 # another file with the same name). So, first collect all the files to put 215 # in the zip (with proper overriding), and then zip them. 216 files_to_zip = dict() 217 for d in zip_resource_dirs: 218 for root, _, files in os.walk(d): 219 for f in files: 220 archive_path = os.path.join(os.path.relpath(root, d), f) 221 path = os.path.join(root, f) 222 files_to_zip[archive_path] = path 223 with zipfile.ZipFile(options.resource_zip_out, 'w') as outzip: 224 for archive_path, path in files_to_zip.iteritems(): 225 outzip.write(path, archive_path) 226 227 if options.stamp: 228 build_utils.Touch(options.stamp) 229 230 231 if __name__ == '__main__': 232 main() 233