1#!/usr/bin/env python 2# 3# Copyright 2015 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'''Prepares the Google Play services split client libraries before usage by 8Chrome's build system. 9 10We need to preprocess Google Play services before using it in Chrome 11builds for 2 main reasons: 12 13- Getting rid of unused resources: unsupported languages, unused 14drawables, etc. 15 16- Merging the differents jars so that it can be proguarded more 17easily. This is necessary since debug and test apks get very close 18to the dex limit. 19 20The script is supposed to be used with the maven repository that can be 21obtained by downloading the "extra-google-m2repository" from the Android SDK 22Manager. It also supports importing from already extracted AAR files using the 23--is-extracted-repo flag. The expected directory structure in that case would 24look like: 25 26 REPOSITORY_DIR 27 +-- CLIENT_1 28 | +-- <content of the first AAR file> 29 +-- CLIENT_2 30 +-- etc. 31 32The output is a directory with the following structure: 33 34 OUT_DIR 35 +-- google-play-services.jar 36 +-- res 37 | +-- CLIENT_1 38 | | +-- color 39 | | +-- values 40 | | +-- etc. 41 | +-- CLIENT_2 42 | +-- ... 43 +-- stub 44 +-- res/[.git-keep-directory] 45 +-- src/android/UnusedStub.java 46 47Requires the `jar` utility in the path. 48 49''' 50 51import argparse 52import glob 53import itertools 54import os 55import shutil 56import stat 57import sys 58import tempfile 59import zipfile 60 61from datetime import datetime 62 63sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) 64import devil_chromium 65from devil.utils import cmd_helper 66from play_services import utils 67from pylib.utils import argparse_utils 68 69 70M2_PKG_PATH = os.path.join('com', 'google', 'android', 'gms') 71 72 73def main(): 74 parser = argparse.ArgumentParser(description=( 75 "Prepares the Google Play services split client libraries before usage " 76 "by Chrome's build system. See the script's documentation for more a " 77 "detailed help.")) 78 argparse_utils.CustomHelpAction.EnableFor(parser) 79 required_args = parser.add_argument_group('required named arguments') 80 required_args.add_argument('-r', 81 '--repository', 82 help=('the Google Play services repository ' 83 'location'), 84 required=True, 85 metavar='FILE') 86 required_args.add_argument('-o', 87 '--out-dir', 88 help='the output directory', 89 required=True, 90 metavar='FILE') 91 required_args.add_argument('-c', 92 '--config-file', 93 help='the config file path', 94 required=True, 95 metavar='FILE') 96 parser.add_argument('-x', 97 '--is-extracted-repo', 98 action='store_true', 99 help='the provided repository is not made of AAR files') 100 parser.add_argument('--config-help', 101 action='custom_help', 102 custom_help_text=utils.ConfigParser.__doc__, 103 help='show the configuration file format help') 104 105 args = parser.parse_args() 106 107 devil_chromium.Initialize() 108 109 return ProcessGooglePlayServices(args.repository, 110 args.out_dir, 111 args.config_file, 112 args.is_extracted_repo) 113 114 115def ProcessGooglePlayServices(repo, out_dir, config_path, is_extracted_repo): 116 config = utils.ConfigParser(config_path) 117 118 tmp_root = tempfile.mkdtemp() 119 try: 120 tmp_paths = _SetupTempDir(tmp_root) 121 122 if is_extracted_repo: 123 _ImportFromExtractedRepo(config, tmp_paths, repo) 124 else: 125 _ImportFromAars(config, tmp_paths, repo) 126 127 _GenerateCombinedJar(tmp_paths) 128 _ProcessResources(config, tmp_paths, repo) 129 _BuildOutput(config, tmp_paths, out_dir) 130 finally: 131 shutil.rmtree(tmp_root) 132 133 return 0 134 135 136def _SetupTempDir(tmp_root): 137 tmp_paths = { 138 'root': tmp_root, 139 'imported_clients': os.path.join(tmp_root, 'imported_clients'), 140 'extracted_jars': os.path.join(tmp_root, 'jar'), 141 'combined_jar': os.path.join(tmp_root, 'google-play-services.jar'), 142 } 143 os.mkdir(tmp_paths['imported_clients']) 144 os.mkdir(tmp_paths['extracted_jars']) 145 146 return tmp_paths 147 148 149def _SetupOutputDir(out_dir): 150 out_paths = { 151 'root': out_dir, 152 'res': os.path.join(out_dir, 'res'), 153 'jar': os.path.join(out_dir, 'google-play-services.jar'), 154 'stub': os.path.join(out_dir, 'stub'), 155 } 156 157 shutil.rmtree(out_paths['jar'], ignore_errors=True) 158 shutil.rmtree(out_paths['res'], ignore_errors=True) 159 shutil.rmtree(out_paths['stub'], ignore_errors=True) 160 161 return out_paths 162 163 164def _MakeWritable(dir_path): 165 for root, dirs, files in os.walk(dir_path): 166 for path in itertools.chain(dirs, files): 167 st = os.stat(os.path.join(root, path)) 168 os.chmod(os.path.join(root, path), st.st_mode | stat.S_IWUSR) 169 170 171def _ImportFromAars(config, tmp_paths, repo): 172 for client in config.clients: 173 aar_name = '%s-%s.aar' % (client, config.sdk_version) 174 aar_path = os.path.join(repo, M2_PKG_PATH, client, 175 config.sdk_version, aar_name) 176 aar_out_path = os.path.join(tmp_paths['imported_clients'], client) 177 _ExtractAll(aar_path, aar_out_path) 178 179 client_jar_path = os.path.join(aar_out_path, 'classes.jar') 180 _ExtractAll(client_jar_path, tmp_paths['extracted_jars']) 181 182 183def _ImportFromExtractedRepo(config, tmp_paths, repo): 184 # Import the clients 185 try: 186 for client in config.clients: 187 client_out_dir = os.path.join(tmp_paths['imported_clients'], client) 188 shutil.copytree(os.path.join(repo, client), client_out_dir) 189 190 client_jar_path = os.path.join(client_out_dir, 'classes.jar') 191 _ExtractAll(client_jar_path, tmp_paths['extracted_jars']) 192 finally: 193 _MakeWritable(tmp_paths['imported_clients']) 194 195 196def _GenerateCombinedJar(tmp_paths): 197 out_file_name = tmp_paths['combined_jar'] 198 working_dir = tmp_paths['extracted_jars'] 199 cmd_helper.Call(['jar', '-cf', out_file_name, '-C', working_dir, '.']) 200 201 202def _ProcessResources(config, tmp_paths, repo): 203 LOCALIZED_VALUES_BASE_NAME = 'values-' 204 locale_whitelist = set(config.locale_whitelist) 205 206 glob_pattern = os.path.join(tmp_paths['imported_clients'], '*', 'res', '*') 207 for res_dir in glob.glob(glob_pattern): 208 dir_name = os.path.basename(res_dir) 209 210 if dir_name.startswith('drawable'): 211 shutil.rmtree(res_dir) 212 continue 213 214 if dir_name.startswith(LOCALIZED_VALUES_BASE_NAME): 215 dir_locale = dir_name[len(LOCALIZED_VALUES_BASE_NAME):] 216 if dir_locale not in locale_whitelist: 217 shutil.rmtree(res_dir) 218 219 # Reimport files from the whitelist. 220 for res_path in config.resource_whitelist: 221 for whitelisted_file in glob.glob(os.path.join(repo, res_path)): 222 resolved_file = os.path.relpath(whitelisted_file, repo) 223 rebased_res = os.path.join(tmp_paths['imported_clients'], resolved_file) 224 225 if not os.path.exists(os.path.dirname(rebased_res)): 226 os.makedirs(os.path.dirname(rebased_res)) 227 228 shutil.copy(os.path.join(repo, whitelisted_file), rebased_res) 229 230 231def _BuildOutput(config, tmp_paths, out_dir): 232 generation_date = datetime.utcnow() 233 version_xml_path = os.path.join(tmp_paths['imported_clients'], 234 config.version_xml_path) 235 play_services_full_version = utils.GetVersionNumberFromLibraryResources( 236 version_xml_path) 237 238 out_paths = _SetupOutputDir(out_dir) 239 240 # Copy the resources to the output dir 241 for client in config.clients: 242 res_in_tmp_dir = os.path.join(tmp_paths['imported_clients'], client, 'res') 243 if os.path.isdir(res_in_tmp_dir) and os.listdir(res_in_tmp_dir): 244 res_in_final_dir = os.path.join(out_paths['res'], client) 245 shutil.copytree(res_in_tmp_dir, res_in_final_dir) 246 247 # Copy the jar 248 shutil.copyfile(tmp_paths['combined_jar'], out_paths['jar']) 249 250 # Write the java dummy stub. Needed for gyp to create the resource jar 251 stub_location = os.path.join(out_paths['stub'], 'src', 'android') 252 os.makedirs(stub_location) 253 with open(os.path.join(stub_location, 'UnusedStub.java'), 'w') as stub: 254 stub.write('package android;' 255 'public final class UnusedStub {' 256 ' private UnusedStub() {}' 257 '}') 258 259 # Create the main res directory. It is needed by gyp 260 stub_res_location = os.path.join(out_paths['stub'], 'res') 261 os.makedirs(stub_res_location) 262 with open(os.path.join(stub_res_location, '.res-stamp'), 'w') as stamp: 263 content_str = 'google_play_services_version: %s\nutc_date: %s\n' 264 stamp.write(content_str % (play_services_full_version, generation_date)) 265 266 config.UpdateVersionNumber(play_services_full_version) 267 268 269def _ExtractAll(zip_path, out_path): 270 with zipfile.ZipFile(zip_path, 'r') as zip_file: 271 zip_file.extractall(out_path) 272 273if __name__ == '__main__': 274 sys.exit(main()) 275