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
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
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
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
28 | +-- <content of the first AAR file>
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
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:
49 +-- google-play-services.jar
58 +-- res/[.git-keep-directory]
59 +-- src/android/UnusedStub.java
61 Requires the `jar` utility in the path.
75 from datetime
import datetime
76 from devil
.utils
import cmd_helper
77 from pylib
import constants
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>')
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',
96 help='The Google Play services repository location',
99 parser
.add_argument('-o',
101 help='The output directory',
104 parser
.add_argument('-c',
106 help='Config file path',
109 parser
.add_argument('-x',
110 '--is-extracted-repo',
113 help='The provided repository is not made of AAR files.')
115 args
= parser
.parse_args()
117 ProcessGooglePlayServices(args
.repository
,
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
)
133 _ImportFromAars(config
, tmp_paths
, repo
)
135 _GenerateCombinedJar(tmp_paths
)
136 _ProcessResources(config
, tmp_paths
)
138 if is_extracted_repo
:
139 printable_repo
= repo
141 printable_repo
= 'm2repository - ' + config
['lib_version']
142 _BuildOutput(config
, tmp_paths
, out_dir
, printable_repo
)
145 def _SetupTempDir(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'])
158 def _SetupOutputDir(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)
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'],
193 def _ImportFromExtractedRepo(config
, tmp_paths
, repo
):
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'],
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
)
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'],
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
)
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
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
)
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() {}'
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__':