Revert of [Android] Don't use a device blacklist if one isn't provided. (RELAND)...
[chromium-blink-merge.git] / build / android / preprocess_google_play_services.py
blobeb106a1628bf9f7c10543379a922c3a9b03d2876
1 #!/usr/bin/env python
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.
7 '''Prepares the Google Play services split client libraries before usage by
8 Chrome's build system.
10 We need to preprocess Google Play services before using it in Chrome
11 builds for 2 main reasons:
13 - Getting rid of unused resources: unsupported languages, unused
14 drawables, etc.
16 - Merging the differents jars so that it can be proguarded more
17 easily. This is necessary since debug and test apks get very close
18 to the dex limit.
20 The script is supposed to be used with the maven repository that can be
21 obtained by downloading the "extra-google-m2repository" from the Android SDK
22 Manager. It also supports importing from already extracted AAR files using the
23 --is-extracted-repo flag. The expected directory structure in that case would
24 look like:
26 REPOSITORY_DIR
27 +-- CLIENT_1
28 | +-- <content of the first AAR file>
29 +-- CLIENT_2
30 +-- etc.
32 The json config (see the -c argument) file should provide the following fields:
34 - lib_version: String. Used when building from the maven repository. It should
35 be the package's version (e.g. "7.3.0"). Unused with extracted repositories.
37 - clients: String array. List of clients to pick. For example, when building
38 from the maven repository, it's the artifactId (e.g. "play-services-base") of
39 each client. With an extracted repository, it's the name of the
40 subdirectories.
42 - locale_whitelist: String array. Locales that should be allowed in the final
43 resources. They are specified the same way they are appended to the `values`
44 directory in android resources (e.g. "us-GB", "it", "fil").
46 The output is a directory with the following structure:
48 OUT_DIR
49 +-- google-play-services.jar
50 +-- res
51 | +-- CLIENT_1
52 | | +-- color
53 | | +-- values
54 | | +-- etc.
55 | +-- CLIENT_2
56 | +-- ...
57 +-- stub
58 +-- res/[.git-keep-directory]
59 +-- src/android/UnusedStub.java
61 Requires the `jar` utility in the path.
63 '''
65 import argparse
66 import glob
67 import itertools
68 import json
69 import os
70 import re
71 import shutil
72 import stat
73 import sys
75 from datetime import datetime
76 from devil.utils import cmd_helper
77 from pylib import constants
79 sys.path.append(
80 os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android', 'gyp'))
81 from util import build_utils # pylint: disable=import-error
84 M2_PKG_PATH = os.path.join('com', 'google', 'android', 'gms')
85 OUTPUT_FORMAT_VERSION = 1
86 VERSION_FILE_NAME = 'version_info.json'
87 VERSION_NUMBER_PATTERN = re.compile(
88 r'<integer name="google_play_services_version">(\d+)<\/integer>')
90 def main():
91 parser = argparse.ArgumentParser(description=("Prepares the Google Play "
92 "services split client libraries before usage by Chrome's build system. "
93 "See the script's documentation for more a detailed help."))
94 parser.add_argument('-r',
95 '--repository',
96 help='The Google Play services repository location',
97 required=True,
98 metavar='FILE')
99 parser.add_argument('-o',
100 '--out-dir',
101 help='The output directory',
102 required=True,
103 metavar='FILE')
104 parser.add_argument('-c',
105 '--config-file',
106 help='Config file path',
107 required=True,
108 metavar='FILE')
109 parser.add_argument('-x',
110 '--is-extracted-repo',
111 action='store_true',
112 default=False,
113 help='The provided repository is not made of AAR files.')
115 args = parser.parse_args()
117 ProcessGooglePlayServices(args.repository,
118 args.out_dir,
119 args.config_file,
120 args.is_extracted_repo)
123 def ProcessGooglePlayServices(repo, out_dir, config_path, is_extracted_repo):
124 with open(config_path, 'r') as json_file:
125 config = json.load(json_file)
127 with build_utils.TempDir() as tmp_root:
128 tmp_paths = _SetupTempDir(tmp_root)
130 if is_extracted_repo:
131 _ImportFromExtractedRepo(config, tmp_paths, repo)
132 else:
133 _ImportFromAars(config, tmp_paths, repo)
135 _GenerateCombinedJar(tmp_paths)
136 _ProcessResources(config, tmp_paths)
138 if is_extracted_repo:
139 printable_repo = repo
140 else:
141 printable_repo = 'm2repository - ' + config['lib_version']
142 _BuildOutput(config, tmp_paths, out_dir, printable_repo)
145 def _SetupTempDir(tmp_root):
146 tmp_paths = {
147 'root': tmp_root,
148 'imported_clients': os.path.join(tmp_root, 'imported_clients'),
149 'extracted_jars': os.path.join(tmp_root, 'jar'),
150 'combined_jar': os.path.join(tmp_root, 'google-play-services.jar'),
152 os.mkdir(tmp_paths['imported_clients'])
153 os.mkdir(tmp_paths['extracted_jars'])
155 return tmp_paths
158 def _SetupOutputDir(out_dir):
159 out_paths = {
160 'root': out_dir,
161 'res': os.path.join(out_dir, 'res'),
162 'jar': os.path.join(out_dir, 'google-play-services.jar'),
163 'stub': os.path.join(out_dir, 'stub'),
166 shutil.rmtree(out_paths['jar'], ignore_errors=True)
167 shutil.rmtree(out_paths['res'], ignore_errors=True)
168 shutil.rmtree(out_paths['stub'], ignore_errors=True)
170 return out_paths
173 def _MakeWritable(dir_path):
174 for root, dirs, files in os.walk(dir_path):
175 for path in itertools.chain(dirs, files):
176 st = os.stat(os.path.join(root, path))
177 os.chmod(os.path.join(root, path), st.st_mode | stat.S_IWUSR)
180 def _ImportFromAars(config, tmp_paths, repo):
181 for client in config['clients']:
182 aar_name = '%s-%s.aar' % (client, config['lib_version'])
183 aar_path = os.path.join(repo, M2_PKG_PATH, client,
184 config['lib_version'], aar_name)
185 aar_out_path = os.path.join(tmp_paths['imported_clients'], client)
186 build_utils.ExtractAll(aar_path, aar_out_path)
188 client_jar_path = os.path.join(aar_out_path, 'classes.jar')
189 build_utils.ExtractAll(client_jar_path, tmp_paths['extracted_jars'],
190 no_clobber=False)
193 def _ImportFromExtractedRepo(config, tmp_paths, repo):
194 # Import the clients
195 try:
196 for client in config['clients']:
197 client_out_dir = os.path.join(tmp_paths['imported_clients'], client)
198 shutil.copytree(os.path.join(repo, client), client_out_dir)
200 client_jar_path = os.path.join(client_out_dir, 'classes.jar')
201 build_utils.ExtractAll(client_jar_path, tmp_paths['extracted_jars'],
202 no_clobber=False)
203 finally:
204 _MakeWritable(tmp_paths['imported_clients'])
207 def _GenerateCombinedJar(tmp_paths):
208 out_file_name = tmp_paths['combined_jar']
209 working_dir = tmp_paths['extracted_jars']
210 cmd_helper.Call(['jar', '-cf', out_file_name, '-C', working_dir, '.'])
213 def _ProcessResources(config, tmp_paths):
214 LOCALIZED_VALUES_BASE_NAME = 'values-'
215 locale_whitelist = set(config['locale_whitelist'])
217 glob_pattern = os.path.join(tmp_paths['imported_clients'], '*', 'res', '*')
218 for res_dir in glob.glob(glob_pattern):
219 dir_name = os.path.basename(res_dir)
221 if dir_name.startswith('drawable'):
222 shutil.rmtree(res_dir)
223 continue
225 if dir_name.startswith(LOCALIZED_VALUES_BASE_NAME):
226 dir_locale = dir_name[len(LOCALIZED_VALUES_BASE_NAME):]
227 if dir_locale not in locale_whitelist:
228 shutil.rmtree(res_dir)
231 def _GetVersionNumber(config, tmp_paths):
232 version_file_path = os.path.join(tmp_paths['imported_clients'],
233 config['base_client'],
234 'res',
235 'values',
236 'version.xml')
238 with open(version_file_path, 'r') as version_file:
239 version_file_content = version_file.read()
241 match = VERSION_NUMBER_PATTERN.search(version_file_content)
242 if not match:
243 raise AttributeError('A value for google_play_services_version was not '
244 'found in ' + version_file_path)
246 return match.group(1)
249 def _BuildOutput(config, tmp_paths, out_dir, printable_repo):
250 generation_date = datetime.utcnow()
251 play_services_full_version = _GetVersionNumber(config, tmp_paths)
253 # Create a version text file to allow quickly checking the version
254 gen_info = {
255 '@Description@': 'Preprocessed Google Play services clients for chrome',
256 'Generator script': os.path.basename(__file__),
257 'Repository source': printable_repo,
258 'Library version': play_services_full_version,
259 'Directory format version': OUTPUT_FORMAT_VERSION,
260 'Generation Date (UTC)': str(generation_date)
262 tmp_version_file_path = os.path.join(tmp_paths['root'], VERSION_FILE_NAME)
263 with open(tmp_version_file_path, 'w') as version_file:
264 json.dump(gen_info, version_file, indent=2, sort_keys=True)
266 out_paths = _SetupOutputDir(out_dir)
268 # Copy the resources to the output dir
269 for client in config['clients']:
270 res_in_tmp_dir = os.path.join(tmp_paths['imported_clients'], client, 'res')
271 if os.path.isdir(res_in_tmp_dir) and os.listdir(res_in_tmp_dir):
272 res_in_final_dir = os.path.join(out_paths['res'], client)
273 shutil.copytree(res_in_tmp_dir, res_in_final_dir)
275 # Copy the jar
276 shutil.copyfile(tmp_paths['combined_jar'], out_paths['jar'])
278 # Write the java dummy stub. Needed for gyp to create the resource jar
279 stub_location = os.path.join(out_paths['stub'], 'src', 'android')
280 os.makedirs(stub_location)
281 with open(os.path.join(stub_location, 'UnusedStub.java'), 'w') as stub:
282 stub.write('package android;'
283 'public final class UnusedStub {'
284 ' private UnusedStub() {}'
285 '}')
287 # Create the main res directory. It is needed by gyp
288 stub_res_location = os.path.join(out_paths['stub'], 'res')
289 os.makedirs(stub_res_location)
290 with open(os.path.join(stub_res_location, '.res-stamp'), 'w') as stamp:
291 content_str = 'google_play_services_version: %s\nutc_date: %s\n'
292 stamp.write(content_str % (play_services_full_version, generation_date))
294 shutil.copyfile(tmp_version_file_path,
295 os.path.join(out_paths['root'], VERSION_FILE_NAME))
298 if __name__ == '__main__':
299 sys.exit(main())