diff --git a/android/play_services/update.py b/android/play_services/update.py index d672e2f7d..118279cc9 100755 --- a/android/play_services/update.py +++ b/android/play_services/update.py @@ -24,6 +24,7 @@ from py_utils import tempfile_ext from pylib import constants from pylib.constants import host_paths from pylib.utils import logging_utils +from pylib.utils import maven_downloader sys.path.append(os.path.join(host_paths.DIR_SOURCE_ROOT, 'build')) import find_depot_tools # pylint: disable=import-error,unused-import @@ -50,17 +51,6 @@ ZIP_FILE_NAME = 'google_play_services_library.zip' LICENSE_PATTERN = re.compile(r'^Pkg\.License=(?P.*)$', re.MULTILINE) -# Template for a Maven settings.xml which instructs Maven to download to the -# given directory -MAVEN_SETTINGS_TEMPLATE = '''\ - - {} - -''' - COM_GOOGLE_ANDROID_GMS = os.path.join('com', 'google', 'android', 'gms') EXTRAS_GOOGLE_M2REPOSITORY = os.path.join('extras', 'google', 'm2repository') @@ -262,43 +252,17 @@ def UpdateSdk(args): breakpad.IS_ENABLED = False config = utils.ConfigParser(args.config) + target_repo = os.path.join(args.sdk_root, EXTRAS_GOOGLE_M2REPOSITORY) # Remove the old SDK. - shutil.rmtree(os.path.join(args.sdk_root, EXTRAS_GOOGLE_M2REPOSITORY)) - - with tempfile_ext.NamedTemporaryDirectory() as temp_dir: - # Configure temp_dir as the Maven repository. - settings_path = os.path.join(temp_dir, 'settings.xml') - with open(settings_path, 'w') as settings: - settings.write(MAVEN_SETTINGS_TEMPLATE.format(temp_dir)) - - for client in config.clients: - # Download the client. - artifact = 'com.google.android.gms:{}:{}:aar'.format( - client, config.version_number) - try: - cmd_helper.Call([ - 'mvn', '--global-settings', settings_path, - 'org.apache.maven.plugins:maven-dependency-plugin:2.1:get', - '-DrepoUrl=https://maven.google.com', '-Dartifact=' + artifact]) - except OSError as e: - if e.errno == os.errno.ENOENT: - logging.error('mvn command not found. Please install Maven.') - return -1 - else: - raise - - # Copy the client .aar file from temp_dir into the tree. - src_path = os.path.join( - temp_dir, COM_GOOGLE_ANDROID_GMS, - client, config.version_number, - '{}-{}.aar'.format(client, config.version_number)) - dst_path = os.path.join( - args.sdk_root, EXTRAS_GOOGLE_M2REPOSITORY, COM_GOOGLE_ANDROID_GMS, - client, config.version_number) - _MakeDirIfAbsent(dst_path) - shutil.copy(src_path, dst_path) + # TODO(https://crbug.com/778650) not everything should be deleted. + shutil.rmtree(target_repo, ignore_errors=True) + downloader = maven_downloader.MavenDownloader() + artifact_list = [ + 'com.google.android.gms:{}:{}:aar'.format(client, config.version_number) + for client in config.clients] + downloader.Install(target_repo, artifact_list) return 0 diff --git a/android/pylib/utils/maven_downloader.py b/android/pylib/utils/maven_downloader.py new file mode 100755 index 000000000..c5b0badf2 --- /dev/null +++ b/android/pylib/utils/maven_downloader.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import errno +import logging +import os +import shutil + +from devil.utils import cmd_helper +from devil.utils import parallelizer + + +def _MakeDirsIfAbsent(path): + try: + os.makedirs(path) + except OSError as err: + if err.errno != errno.EEXIST or not os.path.isdir(path): + raise + + +class MavenDownloader(object): + ''' + Downloads and installs the requested artifacts from the Google Maven repo. + The artifacts are expected to be specified in the format + "group_id:artifact_id:version:file_type", as the default file type is JAR + but most Android libraries are provided as AARs, which would otherwise fail + downloading. See Install() + ''' + + # Remote repository to download the artifacts from. The support library and + # Google Play service are only distributed there, but third party libraries + # could use Maven Central or JCenter for example. The default Maven remote + # is Maven Central. + _REMOTE_REPO = 'https://maven.google.com' + + # Default Maven repository. + _DEFAULT_REPO_PATH = os.path.join(os.getenv('HOME'), '.m2', 'repository') + + def __init__(self, debug=False): + self._repo_path = MavenDownloader._DEFAULT_REPO_PATH + self._remote_url = MavenDownloader._REMOTE_REPO + self._debug = debug + + def Install(self, target_repo, artifacts, include_poms=False): + logging.info('Installing %d artifacts...', len(artifacts)) + downloaders = [_SingleArtifactDownloader(self, artifact, target_repo) + for artifact in artifacts] + if self._debug: + for downloader in downloaders: + downloader.Run(include_poms) + else: + parallelizer.SyncParallelizer(downloaders).Run(include_poms) + logging.info('%d artifacts installed to %s', len(artifacts), target_repo) + + @property + def repo_path(self): + return self._repo_path + + @property + def remote_url(self): + return self._remote_url + + @property + def debug(self): + return self._debug + + +class _SingleArtifactDownloader(object): + '''Handles downloading and installing a single Maven artifact.''' + + _POM_FILE_TYPE = 'pom' + + def __init__(self, download_manager, artifact, target_repo): + self._download_manager = download_manager + self._artifact = artifact + self._target_repo = target_repo + + def Run(self, include_pom=False): + parts = self._artifact.split(':') + if len(parts) != 4: + raise Exception('Artifacts expected as ' + '"group_id:artifact_id:version:file_type".') + group_id, artifact_id, version, file_type = parts + self._InstallArtifact(group_id, artifact_id, version, file_type) + + if include_pom and file_type != _SingleArtifactDownloader._POM_FILE_TYPE: + self._InstallArtifact(group_id, artifact_id, version, + _SingleArtifactDownloader._POM_FILE_TYPE) + + def _InstallArtifact(self, group_id, artifact_id, version, file_type): + logging.debug('Processing %s', self._artifact) + + download_relpath = self._DownloadArtifact( + group_id, artifact_id, version, file_type) + logging.debug('Downloaded.') + + install_path = self._ImportArtifact(download_relpath) + logging.debug('Installed %s', os.path.relpath(install_path)) + + def _DownloadArtifact(self, group_id, artifact_id, version, file_type): + ''' + Downloads the specified artifact using maven, to its standard location, see + MavenDownloader._DEFAULT_REPO_PATH. + ''' + cmd = ['mvn', + 'org.apache.maven.plugins:maven-dependency-plugin:RELEASE:get', + '-DremoteRepositories={}'.format(self._download_manager.remote_url), + '-Dartifact={}:{}:{}:{}'.format(group_id, artifact_id, version, + file_type)] + + stdout = None if self._download_manager.debug else open(os.devnull, 'wb') + + try: + ret_code = cmd_helper.Call(cmd, stdout=stdout) + if ret_code != 0: + raise Exception('Command "{}" failed'.format(' '.join(cmd))) + except OSError as e: + if e.errno == os.errno.ENOENT: + raise Exception('mvn command not found. Please install Maven.') + raise + + return os.path.join(os.path.join(*group_id.split('.')), + artifact_id, + version, + '{}-{}.{}'.format(artifact_id, version, file_type)) + + def _ImportArtifact(self, artifact_path): + src_dir = os.path.join(self._download_manager.repo_path, artifact_path) + dst_dir = os.path.join(self._target_repo, os.path.dirname(artifact_path)) + + _MakeDirsIfAbsent(dst_dir) + shutil.copy(src_dir, dst_dir) + + return dst_dir