Søren Gjesse | e6087bf | 2023-01-17 10:49:29 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
| 6 | import argparse |
| 7 | import os |
| 8 | import re |
| 9 | try: |
| 10 | import resource |
| 11 | except ImportError: |
| 12 | # Not a Unix system. Do what Gandalf tells you not to. |
| 13 | pass |
| 14 | import shutil |
| 15 | import subprocess |
| 16 | import sys |
| 17 | |
| 18 | import utils |
| 19 | |
| 20 | ARCHIVE_BUCKET = 'r8-releases' |
| 21 | REPO = 'https://github.com/google/smali' |
| 22 | NO_DRYRUN_OUTPUT = object() |
| 23 | |
| 24 | def checkout(temp): |
| 25 | subprocess.check_call(['git', 'clone', REPO, temp]) |
| 26 | return temp |
| 27 | |
| 28 | |
| 29 | def parse_options(): |
| 30 | result = argparse.ArgumentParser(description='Release Smali') |
| 31 | result.add_argument('--archive-hash', |
| 32 | required=True, |
| 33 | metavar=('<main hash>'), |
| 34 | help='The hash to use for archiving a smali build') |
| 35 | result.add_argument('--version', |
| 36 | required=True, |
| 37 | metavar=('<version>'), |
| 38 | help='The version of smali to archive.') |
| 39 | result.add_argument('--dry-run', '--dry_run', |
| 40 | nargs='?', |
| 41 | help='Build only, no upload.', |
| 42 | metavar='<output directory>', |
| 43 | default=None, |
| 44 | const=NO_DRYRUN_OUTPUT) |
| 45 | result.add_argument('--checkout', |
| 46 | help='Use existing checkout.') |
| 47 | return result.parse_args() |
| 48 | |
| 49 | |
| 50 | def set_rlimit_to_max(): |
| 51 | (soft, hard) = resource.getrlimit(resource.RLIMIT_NOFILE) |
| 52 | resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) |
| 53 | |
| 54 | |
| 55 | def Main(): |
| 56 | options = parse_options() |
| 57 | if not utils.is_bot() and not options.dry_run: |
| 58 | raise Exception('You are not a bot, don\'t archive builds. ' |
| 59 | + 'Use --dry-run to test locally') |
| 60 | if options.checkout and not options.dry_run: |
| 61 | raise Exception('Using local checkout is only allowed with --dry-run') |
| 62 | if not options.checkout and (not options.archive_hash or not options.version): |
| 63 | raise Exception('Both --archive-hash and --version are required') |
| 64 | |
| 65 | if utils.is_bot() and not utils.IsWindows(): |
| 66 | set_rlimit_to_max() |
| 67 | |
| 68 | with utils.TempDir() as temp: |
| 69 | # Resolve dry run location to support relative directories. |
| 70 | dry_run_output = None |
| 71 | if options.dry_run and options.dry_run != NO_DRYRUN_OUTPUT: |
| 72 | if not os.path.isdir(options.dry_run): |
| 73 | os.mkdir(options.dry_run) |
| 74 | dry_run_output = os.path.abspath(options.dry_run) |
| 75 | |
| 76 | checkout_dir = options.checkout if options.checkout else checkout(temp) |
| 77 | with utils.ChangedWorkingDirectory(checkout_dir): |
| 78 | assert options.archive_hash |
| 79 | subprocess.check_call(['git', 'checkout', options.archive_hash]) |
| 80 | |
| 81 | # Find version from `build.gradle`. |
| 82 | for line in open(os.path.join('build.gradle'), 'r'): |
| 83 | result = re.match( |
| 84 | r'^version = \'(\d+)\.(\d+)\.(\d+)\'', line) |
| 85 | if result: |
| 86 | break |
| 87 | version = '%s.%s.%s' % (result.group(1), result.group(2), result.group(3)) |
| 88 | if version != options.version: |
| 89 | raise Exception( |
| 90 | 'Commit % has version %s, expected version %s' |
| 91 | % (options.archive_hash, version, options.version)) |
| 92 | print('Building version: %s' % version) |
| 93 | |
| 94 | # Build release to local Maven repository. |
| 95 | m2 = os.path.join(temp, 'm2') |
| 96 | os.mkdir(m2) |
| 97 | subprocess.check_call( |
| 98 | ['./gradlew', '-Dmaven.repo.local=%s' % m2 , 'release', 'publishToMavenLocal']) |
| 99 | base = os.path.join('com', 'android', 'tools', 'smali') |
| 100 | |
| 101 | # Check that the local maven repository only has the single version directory in |
| 102 | # each artifact directory. |
| 103 | for name in ['smali-util', 'smali-dexlib2', 'smali', 'smali-baksmali']: |
| 104 | dirnames = next(os.walk(os.path.join(m2, base, name)), (None, None, []))[1] |
| 105 | if not dirnames or len(dirnames) != 1 or dirnames[0] != version: |
| 106 | raise Exception('Found unexpected directory %s in %s' % (dirnames, name)) |
| 107 | |
| 108 | # Build an archive with the relevant content of the local maven repository. |
| 109 | m2_filtered = os.path.join(temp, 'm2_filtered') |
| 110 | shutil.copytree(m2, m2_filtered, ignore=shutil.ignore_patterns('maven-metadata-local.xml')) |
| 111 | maven_release_archive = shutil.make_archive( |
| 112 | 'smali-maven-release-%s' % version, 'zip', m2_filtered, base) |
| 113 | |
| 114 | # Collect names of the fat jars. |
| 115 | fat_jars = list(map( |
| 116 | lambda prefix: '%s-%s-fat.jar' % (prefix, version), |
| 117 | ['smali/build/libs/smali', 'baksmali/build/libs/baksmali'])) |
| 118 | |
| 119 | # Copy artifacts. |
| 120 | files = [maven_release_archive] |
| 121 | files.extend(fat_jars) |
| 122 | if options.dry_run: |
| 123 | if dry_run_output: |
| 124 | print('Dry run, not actually uploading. Copying to %s:' % dry_run_output) |
| 125 | for file in files: |
| 126 | destination = os.path.join(dry_run_output, os.path.basename(file)) |
| 127 | shutil.copyfile(file, destination) |
| 128 | print(" %s" % destination) |
| 129 | else: |
| 130 | print('Dry run, not actually uploading. Generated files:') |
| 131 | for file in files: |
| 132 | print(" %s" % os.path.basename(file)) |
| 133 | else: |
| 134 | destination_prefix = 'gs://%s/smali/%s' % (ARCHIVE_BUCKET, version) |
| 135 | if utils.cloud_storage_exists(destination_prefix): |
| 136 | raise Exception('Target archive directory %s already exists' % destination_prefix) |
| 137 | for file in files: |
| 138 | destination = '%s/%s' % (destination_prefix, os.path.basename(file)) |
| 139 | if utils.cloud_storage_exists(destination): |
| 140 | raise Exception('Target %s already exists' % destination) |
| 141 | utils.upload_file_to_cloud_storage(file, destination) |
| 142 | public_url = 'https://storage.googleapis.com/%s/smali/%s' % (ARCHIVE_BUCKET, version) |
| 143 | print('Artifacts available at: %s' % public_url) |
| 144 | |
| 145 | print("Done!") |
| 146 | |
| 147 | if __name__ == '__main__': |
| 148 | sys.exit(Main()) |