diff --git a/tools_webrtc/android/release_aar.py b/tools_webrtc/android/release_aar.py index f4be14f770..c2ae06516a 100644 --- a/tools_webrtc/android/release_aar.py +++ b/tools_webrtc/android/release_aar.py @@ -15,6 +15,7 @@ this script for authentication. """ import argparse +import json import logging import os import re @@ -37,20 +38,35 @@ from android.build_aar import BuildAar ARCHS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'] -REPOSITORY_API = 'https://api.bintray.com/content/google/webrtc/google-webrtc' +MAVEN_REPOSITORY = 'https://google.bintray.com/webrtc' +API = 'https://api.bintray.com' +PACKAGE_PATH = 'google/webrtc/google-webrtc' +CONTENT_API = API + '/content/' + PACKAGE_PATH +PACKAGES_API = API + '/packages/' + PACKAGE_PATH GROUP_ID = 'org/webrtc' ARTIFACT_ID = 'google-webrtc' COMMIT_POSITION_REGEX = r'^Cr-Commit-Position: refs/heads/master@{#(\d+)}$' -UPLOAD_TIMEOUT_SECONDS = 10.0 +API_TIMEOUT_SECONDS = 10.0 UPLOAD_TRIES = 3 # The sleep time is increased exponentially. UPLOAD_RETRY_BASE_SLEEP_SECONDS = 2.0 +GRADLEW_BIN = os.path.join(CHECKOUT_ROOT, + 'examples/androidtests/third_party/gradle/gradlew') +AAR_PROJECT_DIR = os.path.join(CHECKOUT_ROOT, 'examples/aarproject') +AAR_PROJECT_GRADLE = os.path.join(AAR_PROJECT_DIR, 'build.gradle') +AAR_PROJECT_APP_GRADLE = os.path.join(AAR_PROJECT_DIR, 'app', 'build.gradle') +AAR_PROJECT_DEPENDENCY = "implementation 'org.webrtc:google-webrtc:1.0.+'" +AAR_PROJECT_VERSION_DEPENDENCY = "implementation 'org.webrtc:google-webrtc:%s'" def _ParseArgs(): parser = argparse.ArgumentParser(description='Releases WebRTC on Bintray.') parser.add_argument('--use-goma', action='store_true', default=False, help='Use goma.') + parser.add_argument('--skip-tests', action='store_true', default=False, + help='Skips running the tests.') + parser.add_argument('--publish', action='store_true', default=False, + help='Automatically publishes the library if the tests pass.') parser.add_argument('--verbose', action='store_true', default=False, help='Debug logging.') return parser.parse_args() @@ -82,7 +98,7 @@ def _UploadFile(user, password, filename, version, target_file): target_dir = version + '/' + GROUP_ID + '/' + ARTIFACT_ID + '/' + version target_path = target_dir + '/' + target_file - url = REPOSITORY_API + '/' + target_path + url = CONTENT_API + '/' + target_path logging.info('Uploading %s to %s', filename, url) with open(filename) as fh: @@ -91,7 +107,7 @@ def _UploadFile(user, password, filename, version, target_file): for attempt in xrange(UPLOAD_TRIES): try: response = requests.put(url, data=file_data, auth=(user, password), - timeout=UPLOAD_TIMEOUT_SECONDS) + timeout=API_TIMEOUT_SECONDS) break except requests.exceptions.Timeout as e: logging.warning('Timeout while uploading: %s', e) @@ -114,7 +130,96 @@ def _GeneratePom(target_file, version, commit): fh.write(pom) -def ReleaseAar(use_goma): +def _TestAAR(tmp_dir, username, password, version): + """Runs AppRTCMobile tests using the AAR. Returns true if the tests pass.""" + logging.info('Testing library.') + env = jinja2.Environment( + loader=jinja2.PackageLoader('release_aar'), + ) + + gradle_backup = os.path.join(tmp_dir, 'build.gradle.backup') + app_gradle_backup = os.path.join(tmp_dir, 'app-build.gradle.backup') + + # Make backup copies of the project files before modifying them. + shutil.copy2(AAR_PROJECT_GRADLE, gradle_backup) + shutil.copy2(AAR_PROJECT_APP_GRADLE, app_gradle_backup) + + try: + maven_repository_template = env.get_template('maven-repository.jinja') + maven_repository = maven_repository_template.render( + url=MAVEN_REPOSITORY, username=username, password=password) + + # Append Maven repository to build file to download unpublished files. + with open(AAR_PROJECT_GRADLE, 'a') as gradle_file: + gradle_file.write(maven_repository) + + # Read app build file. + with open(AAR_PROJECT_APP_GRADLE, 'r') as gradle_app_file: + gradle_app = gradle_app_file.read() + + if AAR_PROJECT_DEPENDENCY not in gradle_app: + raise Exception( + '%s not found in the build file.' % AAR_PROJECT_DEPENDENCY) + # Set version to the version to be tested. + target_dependency = AAR_PROJECT_VERSION_DEPENDENCY % version + gradle_app = gradle_app.replace(AAR_PROJECT_DEPENDENCY, target_dependency) + + # Write back. + with open(AAR_PROJECT_APP_GRADLE, 'w') as gradle_app_file: + gradle_app_file.write(gradle_app) + + # Run tests. + try: + # First clean the project. + subprocess.check_call([GRADLEW_BIN, 'clean'], cwd=AAR_PROJECT_DIR) + # Then run the tests. + subprocess.check_call([GRADLEW_BIN, 'connectedDebugAndroidTest'], + cwd=AAR_PROJECT_DIR) + except subprocess.CalledProcessError: + logging.exception('Test failure.') + return False # Clean or tests failed + + return True # Tests pass + finally: + # Restore backups. + shutil.copy2(gradle_backup, AAR_PROJECT_GRADLE) + shutil.copy2(app_gradle_backup, AAR_PROJECT_APP_GRADLE) + + +def _PublishAAR(user, password, version, additional_args): + args = { + 'publish_wait_for_secs': 0 # Publish asynchronously. + } + args.update(additional_args) + + url = CONTENT_API + '/' + version + '/publish' + response = requests.post(url, data=json.dumps(args), auth=(user, password), + timeout=API_TIMEOUT_SECONDS) + + if not response.ok: + raise Exception('Failed to publish. Response: %s' % response) + + +def _DeleteUnpublishedVersion(user, password, version): + url = PACKAGES_API + '/versions/' + version + response = requests.get(url, auth=(user, password), + timeout=API_TIMEOUT_SECONDS) + if not response.ok: + raise Exception('Failed to get version info. Response: %s' % response) + + version_info = json.loads(response.content) + if version_info['published']: + logging.info('Version has already been published, not deleting.') + return + + logging.info('Deleting unpublished version.') + response = requests.delete(url, auth=(user, password), + timeout=API_TIMEOUT_SECONDS) + if not response.ok: + raise Exception('Failed to delete version. Response: %s' % response) + + +def ReleaseAar(use_goma, skip_tests, publish): version = '1.0.' + _GetCommitPos() commit = _GetCommitHash() logging.info('Releasing AAR version %s with hash %s', version, commit) @@ -143,17 +248,28 @@ def ReleaseAar(use_goma): _UploadFile(user, api_key, third_party_licenses_file, version, 'THIRD_PARTY_LICENSES.md') _UploadFile(user, api_key, pom_file, version, base_name + '.pom') + + tests_pass = skip_tests or _TestAAR(tmp_dir, user, api_key, version) + if not tests_pass: + logging.info('Discarding library.') + _PublishAAR(user, api_key, version, {'discard': True}) + _DeleteUnpublishedVersion(user, api_key, version) + raise Exception('Test failure. Discarded library.') + + if publish: + logging.info('Publishing library.') + _PublishAAR(user, api_key, version, {}) + else: + logging.info('Note: The library has not not been published automatically.' + ' Please do so manually if desired.') finally: shutil.rmtree(tmp_dir, True) - logging.info('Library successfully uploaded. Please test and publish it on ' - 'Bintray.') - def main(): args = _ParseArgs() logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) - ReleaseAar(args.use_goma) + ReleaseAar(args.use_goma, args.skip_tests, args.publish) if __name__ == '__main__': diff --git a/tools_webrtc/android/templates/maven-repository.jinja b/tools_webrtc/android/templates/maven-repository.jinja new file mode 100644 index 0000000000..10a37f40d3 --- /dev/null +++ b/tools_webrtc/android/templates/maven-repository.jinja @@ -0,0 +1,11 @@ +allprojects { + repositories { + maven { + url '{{ url }}' + credentials { + username '{{ username }}' + password '{{ password }}' + } + } + } +}