• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
4#
5# Use of this source code is governed by a BSD-style license
6# that can be found in the LICENSE file in the root of the source
7# tree. An additional intellectual property rights grant can be found
8# in the file PATENTS.  All contributing project authors may
9# be found in the AUTHORS file in the root of the source tree.
10
11"""Script for publishing WebRTC AAR on Bintray.
12
13Set BINTRAY_USER and BINTRAY_API_KEY environment variables before running
14this script for authentication.
15"""
16
17import argparse
18import json
19import logging
20import os
21import re
22import shutil
23import subprocess
24import sys
25import tempfile
26import time
27
28
29SCRIPT_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
30CHECKOUT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir))
31
32sys.path.append(os.path.join(CHECKOUT_ROOT, 'third_party'))
33import requests
34import jinja2
35
36sys.path.append(os.path.join(CHECKOUT_ROOT, 'tools_webrtc'))
37from android.build_aar import BuildAar
38
39
40ARCHS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64']
41MAVEN_REPOSITORY = 'https://google.bintray.com/webrtc'
42API = 'https://api.bintray.com'
43PACKAGE_PATH = 'google/webrtc/google-webrtc'
44CONTENT_API = API + '/content/' + PACKAGE_PATH
45PACKAGES_API = API + '/packages/' + PACKAGE_PATH
46GROUP_ID = 'org/webrtc'
47ARTIFACT_ID = 'google-webrtc'
48COMMIT_POSITION_REGEX = r'^Cr-Commit-Position: refs/heads/master@{#(\d+)}$'
49API_TIMEOUT_SECONDS = 10.0
50UPLOAD_TRIES = 3
51# The sleep time is increased exponentially.
52UPLOAD_RETRY_BASE_SLEEP_SECONDS = 2.0
53GRADLEW_BIN = os.path.join(CHECKOUT_ROOT,
54                           'examples/androidtests/third_party/gradle/gradlew')
55ADB_BIN = os.path.join(CHECKOUT_ROOT,
56                       'third_party/android_sdk/public/platform-tools/adb')
57AAR_PROJECT_DIR = os.path.join(CHECKOUT_ROOT, 'examples/aarproject')
58AAR_PROJECT_GRADLE = os.path.join(AAR_PROJECT_DIR, 'build.gradle')
59AAR_PROJECT_APP_GRADLE = os.path.join(AAR_PROJECT_DIR, 'app', 'build.gradle')
60AAR_PROJECT_DEPENDENCY = "implementation 'org.webrtc:google-webrtc:1.0.+'"
61AAR_PROJECT_VERSION_DEPENDENCY = "implementation 'org.webrtc:google-webrtc:%s'"
62
63
64def _ParseArgs():
65  parser = argparse.ArgumentParser(description='Releases WebRTC on Bintray.')
66  parser.add_argument('--use-goma', action='store_true', default=False,
67      help='Use goma.')
68  parser.add_argument('--skip-tests', action='store_true', default=False,
69      help='Skips running the tests.')
70  parser.add_argument('--publish', action='store_true', default=False,
71      help='Automatically publishes the library if the tests pass.')
72  parser.add_argument('--build-dir', default=None,
73      help='Temporary directory to store the build files. If not specified, '
74           'a new directory will be created.')
75  parser.add_argument('--verbose', action='store_true', default=False,
76      help='Debug logging.')
77  return parser.parse_args()
78
79
80def _GetCommitHash():
81  commit_hash = subprocess.check_output(
82    ['git', 'rev-parse', 'HEAD'], cwd=CHECKOUT_ROOT).strip()
83  return commit_hash
84
85
86def _GetCommitPos():
87  commit_message = subprocess.check_output(
88      ['git', 'rev-list', '--format=%B', '--max-count=1', 'HEAD'],
89      cwd=CHECKOUT_ROOT)
90  commit_pos_match = re.search(
91      COMMIT_POSITION_REGEX, commit_message, re.MULTILINE)
92  if not commit_pos_match:
93    raise Exception('Commit position not found in the commit message: %s'
94                      % commit_message)
95  return commit_pos_match.group(1)
96
97
98def _UploadFile(user, password, filename, version, target_file):
99# URL is of format:
100  # <repository_api>/<version>/<group_id>/<artifact_id>/<version>/<target_file>
101  # Example:
102  # https://api.bintray.com/content/google/webrtc/google-webrtc/1.0.19742/org/webrtc/google-webrtc/1.0.19742/google-webrtc-1.0.19742.aar
103
104  target_dir = version + '/' + GROUP_ID + '/' + ARTIFACT_ID + '/' + version
105  target_path = target_dir + '/' + target_file
106  url = CONTENT_API + '/' + target_path
107
108  logging.info('Uploading %s to %s', filename, url)
109  with open(filename) as fh:
110    file_data = fh.read()
111
112  for attempt in xrange(UPLOAD_TRIES):
113    try:
114      response = requests.put(url, data=file_data, auth=(user, password),
115                              timeout=API_TIMEOUT_SECONDS)
116      break
117    except requests.exceptions.Timeout as e:
118      logging.warning('Timeout while uploading: %s', e)
119      time.sleep(UPLOAD_RETRY_BASE_SLEEP_SECONDS ** attempt)
120  else:
121    raise Exception('Failed to upload %s' % filename)
122
123  if not response.ok:
124    raise Exception('Failed to upload %s. Response: %s' % (filename, response))
125  logging.info('Uploaded %s: %s', filename, response)
126
127
128def _GeneratePom(target_file, version, commit):
129  env = jinja2.Environment(
130    loader=jinja2.PackageLoader('release_aar'),
131  )
132  template = env.get_template('pom.jinja')
133  pom = template.render(version=version, commit=commit)
134  with open(target_file, 'w') as fh:
135    fh.write(pom)
136
137
138def _TestAAR(tmp_dir, username, password, version):
139  """Runs AppRTCMobile tests using the AAR. Returns true if the tests pass."""
140  logging.info('Testing library.')
141  env = jinja2.Environment(
142    loader=jinja2.PackageLoader('release_aar'),
143  )
144
145  gradle_backup = os.path.join(tmp_dir, 'build.gradle.backup')
146  app_gradle_backup = os.path.join(tmp_dir, 'app-build.gradle.backup')
147
148  # Make backup copies of the project files before modifying them.
149  shutil.copy2(AAR_PROJECT_GRADLE, gradle_backup)
150  shutil.copy2(AAR_PROJECT_APP_GRADLE, app_gradle_backup)
151
152  try:
153    maven_repository_template = env.get_template('maven-repository.jinja')
154    maven_repository = maven_repository_template.render(
155        url=MAVEN_REPOSITORY, username=username, password=password)
156
157    # Append Maven repository to build file to download unpublished files.
158    with open(AAR_PROJECT_GRADLE, 'a') as gradle_file:
159      gradle_file.write(maven_repository)
160
161    # Read app build file.
162    with open(AAR_PROJECT_APP_GRADLE, 'r') as gradle_app_file:
163      gradle_app = gradle_app_file.read()
164
165    if AAR_PROJECT_DEPENDENCY not in gradle_app:
166      raise Exception(
167          '%s not found in the build file.' % AAR_PROJECT_DEPENDENCY)
168    # Set version to the version to be tested.
169    target_dependency = AAR_PROJECT_VERSION_DEPENDENCY % version
170    gradle_app = gradle_app.replace(AAR_PROJECT_DEPENDENCY, target_dependency)
171
172    # Write back.
173    with open(AAR_PROJECT_APP_GRADLE, 'w') as gradle_app_file:
174      gradle_app_file.write(gradle_app)
175
176    # Uninstall any existing version of AppRTCMobile.
177    logging.info('Uninstalling previous AppRTCMobile versions. It is okay for '
178                 'these commands to fail if AppRTCMobile is not installed.')
179    subprocess.call([ADB_BIN, 'uninstall', 'org.appspot.apprtc'])
180    subprocess.call([ADB_BIN, 'uninstall', 'org.appspot.apprtc.test'])
181
182    # Run tests.
183    try:
184      # First clean the project.
185      subprocess.check_call([GRADLEW_BIN, 'clean'], cwd=AAR_PROJECT_DIR)
186      # Then run the tests.
187      subprocess.check_call([GRADLEW_BIN, 'connectedDebugAndroidTest'],
188                            cwd=AAR_PROJECT_DIR)
189    except subprocess.CalledProcessError:
190      logging.exception('Test failure.')
191      return False  # Clean or tests failed
192
193    return True  # Tests pass
194  finally:
195    # Restore backups.
196    shutil.copy2(gradle_backup, AAR_PROJECT_GRADLE)
197    shutil.copy2(app_gradle_backup, AAR_PROJECT_APP_GRADLE)
198
199
200def _PublishAAR(user, password, version, additional_args):
201  args = {
202    'publish_wait_for_secs': 0  # Publish asynchronously.
203  }
204  args.update(additional_args)
205
206  url = CONTENT_API + '/' + version + '/publish'
207  response = requests.post(url, data=json.dumps(args), auth=(user, password),
208                           timeout=API_TIMEOUT_SECONDS)
209
210  if not response.ok:
211    raise Exception('Failed to publish. Response: %s' % response)
212
213
214def _DeleteUnpublishedVersion(user, password, version):
215  url = PACKAGES_API + '/versions/' + version
216  response = requests.get(url, auth=(user, password),
217                          timeout=API_TIMEOUT_SECONDS)
218  if not response.ok:
219    raise Exception('Failed to get version info. Response: %s' % response)
220
221  version_info = json.loads(response.content)
222  if version_info['published']:
223    logging.info('Version has already been published, not deleting.')
224    return
225
226  logging.info('Deleting unpublished version.')
227  response = requests.delete(url, auth=(user, password),
228                             timeout=API_TIMEOUT_SECONDS)
229  if not response.ok:
230    raise Exception('Failed to delete version. Response: %s' % response)
231
232
233def ReleaseAar(use_goma, skip_tests, publish, build_dir):
234  version = '1.0.' + _GetCommitPos()
235  commit = _GetCommitHash()
236  logging.info('Releasing AAR version %s with hash %s', version, commit)
237
238  user = os.environ.get('BINTRAY_USER', None)
239  api_key = os.environ.get('BINTRAY_API_KEY', None)
240  if not user or not api_key:
241    raise Exception('Environment variables BINTRAY_USER and BINTRAY_API_KEY '
242                    'must be defined.')
243
244  # If build directory is not specified, create a temporary directory.
245  use_tmp_dir = not build_dir
246  if use_tmp_dir:
247    build_dir = tempfile.mkdtemp()
248
249  try:
250    base_name = ARTIFACT_ID + '-' + version
251    aar_file = os.path.join(build_dir, base_name + '.aar')
252    third_party_licenses_file = os.path.join(build_dir, 'LICENSE.md')
253    pom_file = os.path.join(build_dir, base_name + '.pom')
254
255    logging.info('Building at %s', build_dir)
256    BuildAar(ARCHS, aar_file,
257             use_goma=use_goma,
258             ext_build_dir=os.path.join(build_dir, 'aar-build'))
259    _GeneratePom(pom_file, version, commit)
260
261    _UploadFile(user, api_key, aar_file, version, base_name + '.aar')
262    _UploadFile(user, api_key, third_party_licenses_file, version,
263                'THIRD_PARTY_LICENSES.md')
264    _UploadFile(user, api_key, pom_file, version, base_name + '.pom')
265
266    tests_pass = skip_tests or _TestAAR(build_dir, user, api_key, version)
267    if not tests_pass:
268      logging.info('Discarding library.')
269      _PublishAAR(user, api_key, version, {'discard': True})
270      _DeleteUnpublishedVersion(user, api_key, version)
271      raise Exception('Test failure. Discarded library.')
272
273    if publish:
274      logging.info('Publishing library.')
275      _PublishAAR(user, api_key, version, {})
276    else:
277      logging.info('Note: The library has not not been published automatically.'
278                   ' Please do so manually if desired.')
279  finally:
280    if use_tmp_dir:
281      shutil.rmtree(build_dir, True)
282
283
284def main():
285  args = _ParseArgs()
286  logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
287  ReleaseAar(args.use_goma, args.skip_tests, args.publish, args.build_dir)
288
289
290if __name__ == '__main__':
291  sys.exit(main())
292