| #!/usr/bin/env python3 |
| # Copyright (c) 2023, 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. |
| |
| import argparse |
| import os |
| import re |
| try: |
| import resource |
| except ImportError: |
| # Not a Unix system. Do what Gandalf tells you not to. |
| pass |
| import shutil |
| import subprocess |
| import sys |
| |
| import utils |
| |
| ARCHIVE_BUCKET = 'r8-releases' |
| REPO = 'https://github.com/google/smali' |
| NO_DRYRUN_OUTPUT = object() |
| |
| def checkout(temp): |
| subprocess.check_call(['git', 'clone', REPO, temp]) |
| return temp |
| |
| def parse_options(): |
| result = argparse.ArgumentParser(description='Release Smali') |
| result.add_argument('--version', |
| metavar=('<version>'), |
| help='The version of smali to archive.') |
| result.add_argument('--dry-run', '--dry_run', |
| nargs='?', |
| help='Build only, no upload.', |
| metavar='<output directory>', |
| default=None, |
| const=NO_DRYRUN_OUTPUT) |
| result.add_argument('--checkout', |
| help='Use existing checkout.') |
| return result.parse_args() |
| |
| |
| def set_rlimit_to_max(): |
| (soft, hard) = resource.getrlimit(resource.RLIMIT_NOFILE) |
| resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) |
| |
| |
| def Main(): |
| options = parse_options() |
| if not utils.is_bot() and not options.dry_run: |
| raise Exception('You are not a bot, don\'t archive builds. ' |
| + 'Use --dry-run to test locally') |
| if options.checkout and not options.dry_run: |
| raise Exception('Using local checkout is only allowed with --dry-run') |
| if not options.checkout and not options.version: |
| raise Exception('Option --version is required (when not using local checkout)') |
| |
| if utils.is_bot() and not utils.IsWindows(): |
| set_rlimit_to_max() |
| |
| with utils.TempDir() as temp: |
| # Resolve dry run location to support relative directories. |
| dry_run_output = None |
| if options.dry_run and options.dry_run != NO_DRYRUN_OUTPUT: |
| if not os.path.isdir(options.dry_run): |
| os.mkdir(options.dry_run) |
| dry_run_output = os.path.abspath(options.dry_run) |
| |
| checkout_dir = options.checkout if options.checkout else checkout(temp) |
| with utils.ChangedWorkingDirectory(checkout_dir): |
| if options.version: |
| output = subprocess.check_output(['git', 'tag', '-l', options.version]) |
| if len(output) == 0: |
| raise Exception( |
| 'Repository does not have a release tag for version %s' % options.version) |
| subprocess.check_call(['git', 'checkout', options.version]) |
| |
| # Find version from `build.gradle`. |
| for line in open(os.path.join('build.gradle'), 'r'): |
| result = re.match( |
| r'^version = \'(\d+)\.(\d+)\.(\d+)\'', line) |
| if result: |
| break |
| version = '%s.%s.%s' % (result.group(1), result.group(2), result.group(3)) |
| if options.version and version != options.version: |
| message = 'version %s, expected version %s' % (version, options.version) |
| if (options.checkout): |
| raise Exception('Checkout %s has %s' % (options.checkout, message)) |
| else: |
| raise Exception('Tag % has %s' % (options.version, message)) |
| |
| print('Building version: %s' % version) |
| |
| # Build release to local Maven repository. |
| m2 = os.path.join(temp, 'm2') |
| os.mkdir(m2) |
| subprocess.check_call( |
| ['./gradlew', '-Dmaven.repo.local=%s' % m2 , 'release', 'test', 'publishToMavenLocal']) |
| base = os.path.join('com', 'android', 'tools', 'smali') |
| |
| # Check that the local maven repository only has the single version directory in |
| # each artifact directory. |
| for name in ['smali-util', 'smali-dexlib2', 'smali', 'smali-baksmali']: |
| dirnames = next(os.walk(os.path.join(m2, base, name)), (None, None, []))[1] |
| if not dirnames or len(dirnames) != 1 or dirnames[0] != version: |
| raise Exception('Found unexpected directory %s in %s' % (dirnames, name)) |
| |
| # Build an archive with the relevant content of the local maven repository. |
| m2_filtered = os.path.join(temp, 'm2_filtered') |
| shutil.copytree(m2, m2_filtered, ignore=shutil.ignore_patterns('maven-metadata-local.xml')) |
| maven_release_archive = shutil.make_archive( |
| 'smali-maven-release-%s' % version, 'zip', m2_filtered, base) |
| |
| # Collect names of the fat jars. |
| fat_jars = list(map( |
| lambda prefix: '%s-%s-fat.jar' % (prefix, version), |
| ['smali/build/libs/smali', 'baksmali/build/libs/baksmali'])) |
| |
| # Copy artifacts. |
| files = [maven_release_archive] |
| files.extend(fat_jars) |
| if options.dry_run: |
| if dry_run_output: |
| print('Dry run, not actually uploading. Copying to %s:' % dry_run_output) |
| for file in files: |
| destination = os.path.join(dry_run_output, os.path.basename(file)) |
| shutil.copyfile(file, destination) |
| print(" %s" % destination) |
| else: |
| print('Dry run, not actually uploading. Generated files:') |
| for file in files: |
| print(" %s" % os.path.basename(file)) |
| else: |
| destination_prefix = 'gs://%s/smali/%s' % (ARCHIVE_BUCKET, version) |
| if utils.cloud_storage_exists(destination_prefix): |
| raise Exception('Target archive directory %s already exists' % destination_prefix) |
| for file in files: |
| destination = '%s/%s' % (destination_prefix, os.path.basename(file)) |
| if utils.cloud_storage_exists(destination): |
| raise Exception('Target %s already exists' % destination) |
| utils.upload_file_to_cloud_storage(file, destination) |
| public_url = 'https://storage.googleapis.com/%s/smali/%s' % (ARCHIVE_BUCKET, version) |
| print('Artifacts available at: %s' % public_url) |
| |
| print("Done!") |
| |
| if __name__ == '__main__': |
| sys.exit(Main()) |