Home | History | Annotate | Download | only in gyp
      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