Add archive and release scripts for Google smali fork
The archive script will archive a version built from a specified
hash and archive it in GCS under r8-releases/smali.
The release script will publish the archive from GCS to Google
Maven.
Bug: b/262205084
Change-Id: I48d38d5ef2fd760e3fab24c0e60fbfaa6c5ee489
diff --git a/tools/archive_smali.py b/tools/archive_smali.py
new file mode 100755
index 0000000..fdfb75c
--- /dev/null
+++ b/tools/archive_smali.py
@@ -0,0 +1,148 @@
+#!/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('--archive-hash',
+ required=True,
+ metavar=('<main hash>'),
+ help='The hash to use for archiving a smali build')
+ result.add_argument('--version',
+ required=True,
+ 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.archive_hash or not options.version):
+ raise Exception('Both --archive-hash and --version are required')
+
+ 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):
+ assert options.archive_hash
+ subprocess.check_call(['git', 'checkout', options.archive_hash])
+
+ # 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 version != options.version:
+ raise Exception(
+ 'Commit % has version %s, expected version %s'
+ % (options.archive_hash, version, options.version))
+ 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', '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())