| #!/usr/bin/env python3 | 
 | # Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file | 
 | # for details. All rights reserved. Use of this source code is governed by a | 
 | # BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | # This script is designed to run on a buildbot to build from the source | 
 | # of https://github.com/google/desugar_jdk_libs and publish to the | 
 | # r8-release Cloud Storage Bucket. | 
 | # | 
 | # These files are uploaded: | 
 | # | 
 | #   raw/desugar_jdk_libs/<VERSION>/desugar_jdk_libs.jar | 
 | #   raw/desugar_jdk_libs/<VERSION>/desugar_jdk_libs.zip | 
 | #   raw/com/android/tools/desugar_jdk_libs/<VERSION>/desugar_jdk_libs-<VERSION>.jar | 
 | # | 
 | # The first two are the raw jar file and the maven compatible zip file. The | 
 | # third is the raw jar file placed and named so that the URL | 
 | # https://storage.googleapis.com/r8-releases/raw can be treated as a maven | 
 | # repository to fetch the artifact com.android.tools:desugar_jdk_libs:1.0.0 | 
 |  | 
 | import archive | 
 | import defines | 
 | import git_utils | 
 | import gradle | 
 | import hashlib | 
 | import jdk | 
 | import optparse | 
 | import os | 
 | import re | 
 | import shutil | 
 | import subprocess | 
 | import sys | 
 | import utils | 
 | import zipfile | 
 |  | 
 | VERSION_FILE_JDK8 = 'VERSION.txt' | 
 | VERSION_FILE_JDK11_LEGACY = 'VERSION_JDK11_LEGACY.txt' | 
 | VERSION_FILE_JDK11_MINIMAL = 'VERSION_JDK11_MINIMAL.txt' | 
 | VERSION_FILE_JDK11 = 'VERSION_JDK11.txt' | 
 | VERSION_FILE_JDK11_NIO = 'VERSION_JDK11_NIO.txt' | 
 |  | 
 | VERSION_MAP = { | 
 |     'jdk8': VERSION_FILE_JDK8, | 
 |     'jdk11_legacy': VERSION_FILE_JDK11_LEGACY, | 
 |     'jdk11_minimal': VERSION_FILE_JDK11_MINIMAL, | 
 |     'jdk11': VERSION_FILE_JDK11, | 
 |     'jdk11_nio': VERSION_FILE_JDK11_NIO | 
 | } | 
 |  | 
 | GITHUB_REPRO = 'desugar_jdk_libs' | 
 |  | 
 | BASE_LIBRARY_NAME = 'desugar_jdk_libs' | 
 |  | 
 | LIBRARY_NAME_MAP = { | 
 |     'jdk8': BASE_LIBRARY_NAME, | 
 |     'jdk11_legacy': BASE_LIBRARY_NAME, | 
 |     'jdk11_minimal': BASE_LIBRARY_NAME + '_minimal', | 
 |     'jdk11': BASE_LIBRARY_NAME, | 
 |     'jdk11_nio': BASE_LIBRARY_NAME + '_nio' | 
 | } | 
 |  | 
 | MAVEN_RELEASE_TARGET_MAP = { | 
 |     'jdk8': 'maven_release', | 
 |     'jdk11_legacy': 'maven_release_jdk11_legacy', | 
 |     'jdk11_minimal': 'maven_release_jdk11_minimal', | 
 |     'jdk11': 'maven_release_jdk11', | 
 |     'jdk11_nio': 'maven_release_jdk11_nio' | 
 | } | 
 |  | 
 | MAVEN_RELEASE_ZIP = { | 
 |     'jdk8': BASE_LIBRARY_NAME + '.zip', | 
 |     'jdk11_legacy': BASE_LIBRARY_NAME + '_jdk11_legacy.zip', | 
 |     'jdk11_minimal': BASE_LIBRARY_NAME + '_jdk11_minimal.zip', | 
 |     'jdk11': BASE_LIBRARY_NAME + '_jdk11.zip', | 
 |     'jdk11_nio': BASE_LIBRARY_NAME + '_jdk11_nio.zip' | 
 | } | 
 |  | 
 | DESUGAR_JDK_LIBS_HASH_FILE = os.path.join(defines.THIRD_PARTY, 'openjdk', | 
 |                                           'desugar_jdk_libs_11', | 
 |                                           'desugar_jdk_libs_hash') | 
 |  | 
 |  | 
 | def ParseOptions(argv): | 
 |     result = optparse.OptionParser() | 
 |     result.add_option( | 
 |         '--variant', | 
 |         help="Variant(s) to build", | 
 |         metavar=('<variants(s)>'), | 
 |         choices=['jdk8', 'jdk11_legacy', 'jdk11_minimal', 'jdk11', 'jdk11_nio'], | 
 |         default=[], | 
 |         action='append') | 
 |     result.add_option('--dry-run', | 
 |                       '--dry_run', | 
 |                       help='Running on bot, use third_party dependency.', | 
 |                       default=False, | 
 |                       action='store_true') | 
 |     result.add_option('--dry-run-output', | 
 |                       '--dry_run_output', | 
 |                       help='Output directory for dry run.', | 
 |                       type="string", | 
 |                       action="store") | 
 |     result.add_option('--github-account', | 
 |                       '--github_account', | 
 |                       help='GitHub account to clone from.', | 
 |                       default="google", | 
 |                       type="string", | 
 |                       action="store") | 
 |     result.add_option('--build_only', | 
 |                       '--build-only', | 
 |                       help='Build desugared library without archiving.', | 
 |                       type="string", | 
 |                       action="store") | 
 |     (options, args) = result.parse_args(argv) | 
 |     return (options, args) | 
 |  | 
 |  | 
 | def GetVersion(version_file_name): | 
 |     with open(version_file_name, 'r') as version_file: | 
 |         lines = [line.strip() for line in version_file.readlines()] | 
 |         lines = [line for line in lines if not line.startswith('#')] | 
 |         if len(lines) != 1: | 
 |             raise Exception('Version file ' + version_file + | 
 |                             ' is expected to have exactly one line') | 
 |         version = lines[0].strip() | 
 |         utils.check_basic_semver_version(version, | 
 |                                          'in version file ' + version_file_name, | 
 |                                          allowPrerelease=True) | 
 |         return version | 
 |  | 
 |  | 
 | def Upload(options, file_name, storage_path, destination, is_main): | 
 |     print('Uploading %s to %s' % (file_name, destination)) | 
 |     if options.dry_run: | 
 |         if options.dry_run_output: | 
 |             dry_run_destination = \ | 
 |                 os.path.join(options.dry_run_output, os.path.basename(file_name)) | 
 |             print('Dry run, not actually uploading. Copying to ' + | 
 |                   dry_run_destination) | 
 |             shutil.copyfile(file_name, dry_run_destination) | 
 |         else: | 
 |             print('Dry run, not actually uploading') | 
 |     else: | 
 |         utils.upload_file_to_cloud_storage(file_name, destination) | 
 |         print( | 
 |             'File available at: %s' % | 
 |             destination.replace('gs://', 'https://storage.googleapis.com/', 1)) | 
 |  | 
 |  | 
 | def CloneDesugaredLibrary(github_account, checkout_dir, desugar_jdk_libs_hash): | 
 |     git_utils.GitClone( | 
 |         'https://github.com/' + github_account + '/' + GITHUB_REPRO, | 
 |         checkout_dir) | 
 |     git_utils.GitCheckout(desugar_jdk_libs_hash, checkout_dir) | 
 |  | 
 |  | 
 | def GetJavaEnv(androidHomeTemp): | 
 |     java_env = dict(os.environ, JAVA_HOME=jdk.GetJdk11Home()) | 
 |     java_env['PATH'] = java_env['PATH'] + os.pathsep + os.path.join( | 
 |         jdk.GetJdk11Home(), 'bin') | 
 |     java_env['GRADLE_OPTS'] = '-Xmx1g' | 
 |     java_env['ANDROID_HOME'] = androidHomeTemp | 
 |     return java_env | 
 |  | 
 |  | 
 | def setUpFakeAndroidHome(androidHomeTemp): | 
 |     # Bazel will check if 30 is present then extract android.jar from 32. | 
 |     # We copy android.jar from third_party to mimic repository structure. | 
 |     subpath = os.path.join(androidHomeTemp, "build-tools") | 
 |     cmd = ["mkdir", subpath] | 
 |     subprocess.check_call(cmd) | 
 |     subpath = os.path.join(subpath, "32.0.0") | 
 |     cmd = ["mkdir", subpath] | 
 |     subprocess.check_call(cmd) | 
 |     subpath = os.path.join(androidHomeTemp, "platforms") | 
 |     cmd = ["mkdir", subpath] | 
 |     subprocess.check_call(cmd) | 
 |     subpath30 = os.path.join(subpath, "android-30") | 
 |     cmd = ["mkdir", subpath30] | 
 |     subprocess.check_call(cmd) | 
 |     subpath = os.path.join(subpath, "android-32") | 
 |     cmd = ["mkdir", subpath] | 
 |     subprocess.check_call(cmd) | 
 |     dest = os.path.join(subpath, "android.jar") | 
 |     sha = os.path.join(utils.THIRD_PARTY, "android_jar", "lib-v32.tar.gz.sha1") | 
 |     utils.DownloadFromGoogleCloudStorage(sha) | 
 |     src = os.path.join(utils.THIRD_PARTY, "android_jar", "lib-v32", | 
 |                        "android.jar") | 
 |     cmd = ["cp", src, dest] | 
 |     subprocess.check_call(cmd) | 
 |  | 
 |  | 
 | def BuildDesugaredLibrary(checkout_dir, variant, version=None): | 
 |     if not variant in MAVEN_RELEASE_TARGET_MAP: | 
 |         raise Exception('Variant ' + variant + ' is not supported') | 
 |     if variant != 'jdk8' and variant != 'jdk11_legacy' and version is None: | 
 |         raise Exception('Variant ' + variant + | 
 |                         ' require version for undesugaring') | 
 |     if variant != 'jdk8': | 
 |         # Hack to workaround b/256723819. | 
 |         os.remove( | 
 |             os.path.join(checkout_dir, "jdk11", "src", "java.base", "share", | 
 |                          "classes", "java", "time", "format", | 
 |                          "DesugarDateTimeFormatterBuilder.java")) | 
 |     with utils.ChangedWorkingDirectory(checkout_dir): | 
 |         with utils.TempDir() as androidHomeTemp: | 
 |             setUpFakeAndroidHome(androidHomeTemp) | 
 |             javaEnv = GetJavaEnv(androidHomeTemp) | 
 |             bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', | 
 |                                  'bazel') | 
 |             cmd = [ | 
 |                 bazel, '--bazelrc=/dev/null', 'build', '--spawn_strategy=local', | 
 |                 '--verbose_failures', MAVEN_RELEASE_TARGET_MAP[variant] | 
 |             ] | 
 |             utils.PrintCmd(cmd) | 
 |             subprocess.check_call(cmd, env=javaEnv) | 
 |             cmd = [bazel, 'shutdown'] | 
 |             utils.PrintCmd(cmd) | 
 |             subprocess.check_call(cmd, env=javaEnv) | 
 |  | 
 |         # Locate the library jar and the maven zip with the jar from the | 
 |         # bazel build. | 
 |         if variant == 'jdk8': | 
 |             library_jar = os.path.join(checkout_dir, 'bazel-bin', 'src', | 
 |                                        'share', 'classes', 'java', | 
 |                                        'libjava.jar') | 
 |         else: | 
 |             # All JDK11 variants use the same library code. | 
 |             library_jar = os.path.join(checkout_dir, 'bazel-bin', 'jdk11', | 
 |                                        'src', | 
 |                                        'd8_java_base_selected_with_addon.jar') | 
 |         maven_zip = os.path.join(checkout_dir, 'bazel-bin', | 
 |                                  MAVEN_RELEASE_ZIP[variant]) | 
 |  | 
 |         if variant != 'jdk8' and variant != 'jdk11_legacy': | 
 |             # The undesugaring is temporary... | 
 |             undesugared_maven_zip = os.path.join(checkout_dir, | 
 |                                                  'undesugared_maven') | 
 |             Undesugar(variant, maven_zip, version, undesugared_maven_zip) | 
 |             undesugared_maven_zip = os.path.join(checkout_dir, | 
 |                                                  'undesugared_maven.zip') | 
 |             return (library_jar, undesugared_maven_zip) | 
 |         else: | 
 |             return (library_jar, maven_zip) | 
 |  | 
 |  | 
 | def hash_for(file, hash): | 
 |     with open(file, 'rb') as f: | 
 |         while True: | 
 |             # Read chunks of 1MB | 
 |             chunk = f.read(2**20) | 
 |             if not chunk: | 
 |                 break | 
 |             hash.update(chunk) | 
 |     return hash.hexdigest() | 
 |  | 
 |  | 
 | def write_md5_for(file): | 
 |     hexdigest = hash_for(file, hashlib.md5()) | 
 |     with (open(file + '.md5', 'w')) as file: | 
 |         file.write(hexdigest) | 
 |  | 
 |  | 
 | def write_sha1_for(file): | 
 |     hexdigest = hash_for(file, hashlib.sha1()) | 
 |     with (open(file + '.sha1', 'w')) as file: | 
 |         file.write(hexdigest) | 
 |  | 
 |  | 
 | def Undesugar(variant, maven_zip, version, undesugared_maven_zip): | 
 |     gradle.RunGradle([ | 
 |         utils.GRADLE_TASK_R8, utils.GRADLE_TASK_TEST_JAR, | 
 |         utils.GRADLE_TASK_TEST_DEPS_JAR, '-Pno_internal' | 
 |     ]) | 
 |     with utils.TempDir() as tmp: | 
 |         with zipfile.ZipFile(maven_zip, 'r') as zip_ref: | 
 |             zip_ref.extractall(tmp) | 
 |         desugar_jdk_libs_jar = os.path.join( | 
 |             tmp, 'com', 'android', 'tools', LIBRARY_NAME_MAP[variant], version, | 
 |             '%s-%s.jar' % (LIBRARY_NAME_MAP[variant], version)) | 
 |         print(desugar_jdk_libs_jar) | 
 |         undesugared_jar = os.path.join(tmp, 'undesugared.jar') | 
 |         buildLibs = os.path.join(defines.REPO_ROOT, 'build', 'libs') | 
 |         cmd = [ | 
 |             jdk.GetJavaExecutable(), '-cp', | 
 |             '%s:%s:%s' % | 
 |             (utils.R8_JAR, utils.R8_TESTS_JAR, utils.R8_TESTS_DEPS_JAR), | 
 |             'com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer', | 
 |             desugar_jdk_libs_jar, undesugared_jar | 
 |         ] | 
 |         print(cmd) | 
 |         try: | 
 |             output = subprocess.check_output( | 
 |                 cmd, stderr=subprocess.STDOUT).decode('utf-8') | 
 |         except subprocess.CalledProcessError as e: | 
 |             print(e) | 
 |             print(e.output) | 
 |             raise e | 
 |         print(output) | 
 |         # Copy the undesugared jar into place and update the checksums. | 
 |         shutil.copyfile(undesugared_jar, desugar_jdk_libs_jar) | 
 |         write_md5_for(desugar_jdk_libs_jar) | 
 |         write_sha1_for(desugar_jdk_libs_jar) | 
 |         shutil.make_archive(undesugared_maven_zip, 'zip', tmp) | 
 |         print(undesugared_maven_zip) | 
 |         output = subprocess.check_output( | 
 |             ['ls', '-l', os.path.dirname(undesugared_maven_zip)], | 
 |             stderr=subprocess.STDOUT).decode('utf-8') | 
 |         print(output) | 
 |  | 
 |  | 
 | def MustBeExistingDirectory(path): | 
 |     if (not os.path.exists(path) or not os.path.isdir(path)): | 
 |         raise Exception(path + ' does not exist or is not a directory') | 
 |  | 
 |  | 
 | def BuildAndUpload(options, variant): | 
 |     desugar_jdk_libs_hash = '' | 
 |     with open(DESUGAR_JDK_LIBS_HASH_FILE, 'r') as input_hash: | 
 |         desugar_jdk_libs_hash = input_hash.readline() | 
 |     if options.build_only: | 
 |         with utils.TempDir() as checkout_dir: | 
 |             CloneDesugaredLibrary(options.github_account, checkout_dir, | 
 |                                   desugar_jdk_libs_hash) | 
 |             (library_jar, | 
 |              maven_zip) = BuildDesugaredLibrary(checkout_dir, variant, | 
 |                                                 desugar_jdk_libs_hash) | 
 |             shutil.copyfile( | 
 |                 library_jar, | 
 |                 os.path.join(options.build_only, os.path.basename(library_jar))) | 
 |             shutil.copyfile( | 
 |                 maven_zip, | 
 |                 os.path.join(options.build_only, os.path.basename(maven_zip))) | 
 |             return | 
 |  | 
 |     # Only handling versioned desugar_jdk_libs. | 
 |     is_main = False | 
 |  | 
 |     with utils.TempDir() as checkout_dir: | 
 |         CloneDesugaredLibrary(options.github_account, checkout_dir, | 
 |                               desugar_jdk_libs_hash) | 
 |         version = GetVersion(os.path.join(checkout_dir, VERSION_MAP[variant])) | 
 |  | 
 |         destination = archive.GetVersionDestination( | 
 |             'gs://', LIBRARY_NAME_MAP[variant] + '/' + version, is_main) | 
 |         if utils.cloud_storage_exists(destination) and not options.dry_run: | 
 |             raise Exception('Target archive directory %s already exists' % | 
 |                             destination) | 
 |  | 
 |         (library_jar, | 
 |          maven_zip) = BuildDesugaredLibrary(checkout_dir, variant, version) | 
 |  | 
 |         storage_path = LIBRARY_NAME_MAP[variant] + '/' + version | 
 |         # Upload the jar file with the library. | 
 |         destination = archive.GetUploadDestination( | 
 |             storage_path, LIBRARY_NAME_MAP[variant] + '.jar', is_main) | 
 |         Upload(options, library_jar, storage_path, destination, is_main) | 
 |  | 
 |         # Upload the maven zip file with the library. | 
 |         destination = archive.GetUploadDestination(storage_path, | 
 |                                                    MAVEN_RELEASE_ZIP[variant], | 
 |                                                    is_main) | 
 |         Upload(options, maven_zip, storage_path, destination, is_main) | 
 |  | 
 |         # Upload the jar file for accessing GCS as a maven repro. | 
 |         maven_destination = archive.GetUploadDestination( | 
 |             utils.get_maven_path(LIBRARY_NAME_MAP[variant], version), | 
 |             '%s-%s.jar' % (LIBRARY_NAME_MAP[variant], version), is_main) | 
 |         if options.dry_run: | 
 |             print('Dry run, not actually creating maven repo') | 
 |         else: | 
 |             utils.upload_file_to_cloud_storage(library_jar, maven_destination) | 
 |             print('Maven repo root available at: %s' % | 
 |                   archive.GetMavenUrl(is_main)) | 
 |  | 
 |  | 
 | def Main(argv): | 
 |     (options, args) = ParseOptions(argv) | 
 |     if (len(args) > 0): | 
 |         raise Exception('Unsupported arguments') | 
 |     if not utils.is_bot() and not (options.dry_run or options.build_only): | 
 |         raise Exception('You are not a bot, don\'t archive builds. ' + | 
 |                         'Use --dry-run or --build-only to test locally') | 
 |     if options.dry_run_output: | 
 |         MustBeExistingDirectory(options.dry_run_output) | 
 |     if options.build_only: | 
 |         MustBeExistingDirectory(options.build_only) | 
 |     if utils.is_bot(): | 
 |         archive.SetRLimitToMax() | 
 |  | 
 |     # Make sure bazel is extracted in third_party. | 
 |     utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE) | 
 |     utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE) | 
 |     utils.DownloadFromGoogleCloudStorage(utils.JAVA11_SHA_FILE) | 
 |     utils.DownloadFromGoogleCloudStorage(utils.DESUGAR_JDK_LIBS_11_SHA_FILE) | 
 |  | 
 |     for v in options.variant: | 
 |         BuildAndUpload(options, v) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     sys.exit(Main(sys.argv[1:])) |