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())
diff --git a/tools/gmaven.py b/tools/gmaven.py
new file mode 100644
index 0000000..1ad96c0
--- /dev/null
+++ b/tools/gmaven.py
@@ -0,0 +1,93 @@
+#!/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 re
+import subprocess
+
+GMAVEN_PUBLISHER = '/google/bin/releases/android-devtools/gmaven/publisher/gmaven-publisher'
+GMAVEN_PUBLISH_STAGE_RELEASE_ID_PATTERN = re.compile('Release ID = ([0-9a-f\-]+)')
+
+
+def publisher_stage(gfiles, dry_run = False):
+ if dry_run:
+ print('Dry-run, would have staged %s' % gfiles)
+ return 'dry-run-release-id'
+
+ print("Staging: %s" % ', '.join(gfiles))
+ print("")
+
+ cmd = [GMAVEN_PUBLISHER, 'stage', '--gfile', ','.join(gfiles)]
+ output = subprocess.check_output(cmd)
+
+ # Expect output to contain:
+ # [INFO] 06/19/2020 09:35:12 CEST: >>>>>>>>>> Staged
+ # [INFO] 06/19/2020 09:35:12 CEST: Release ID = 9171d015-18f6-4a90-9984-1c362589dc1b
+ # [INFO] 06/19/2020 09:35:12 CEST: Stage Path = /bigstore/studio_staging/maven2/sgjesse/9171d015-18f6-4a90-9984-1c362589dc1b
+
+ matches = GMAVEN_PUBLISH_STAGE_RELEASE_ID_PATTERN.findall(output.decode("utf-8"))
+ if matches == None or len(matches) > 1:
+ print("Could not determine the release ID from the gmaven_publisher " +
+ "output. Expected a line with 'Release ID = <release id>'.")
+ print("Output was:")
+ print(output)
+ sys.exit(1)
+
+ print(output)
+
+ release_id = matches[0]
+ return release_id
+
+
+def publisher_stage_redir_test_info(release_id, artifact, dst):
+
+ redir_command = ("/google/data/ro/teams/android-devtools-infra/tools/redir "
+ + "--alsologtostderr "
+ + "--gcs_bucket_path=/bigstore/gmaven-staging/${USER}/%s "
+ + "--port=1480") % release_id
+
+ get_command = ("mvn org.apache.maven.plugins:maven-dependency-plugin:2.4:get "
+ + "-Dmaven.repo.local=/tmp/maven_repo_local "
+ + "-DremoteRepositories=http://localhost:1480 "
+ + "-Dartifact=%s "
+ + "-Ddest=%s") % (artifact, dst)
+
+ print("""To test the staged content with 'redir' run:
+
+%s
+
+Then add the following repository to settings.gradle to search the 'redir'
+repository:
+
+dependencyResolutionManagement {
+ repositories {
+ maven {
+ url 'http://localhost:1480'
+ allowInsecureProtocol true
+ }
+ }
+}
+
+and add the following repository to gradle.build for for the staged version:
+
+dependencies {
+ implementation('%s') {
+ changing = true
+ }
+}
+
+Use this commands to get artifact from 'redir':
+
+rm -rf /tmp/maven_repo_local
+%s
+""" % (redir_command, artifact, get_command))
+
+
+def publisher_publish(release_id, dry_run = False):
+ if dry_run:
+ print('Dry-run, would have published %s' % release_id)
+ return
+
+ cmd = [GMAVEN_PUBLISHER, 'publish', release_id]
+ output = subprocess.check_output(cmd)
diff --git a/tools/release_smali.py b/tools/release_smali.py
new file mode 100755
index 0000000..12a9389
--- /dev/null
+++ b/tools/release_smali.py
@@ -0,0 +1,50 @@
+#!/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 sys
+
+import gmaven
+
+ARCHIVE_BUCKET = 'r8-releases'
+REPO = 'https://github.com/google/smali'
+
+def parse_options():
+ result = argparse.ArgumentParser(description='Release Smali')
+ result.add_argument('--version',
+ required=True,
+ metavar=('<version>'),
+ help='The version of smali to release.')
+ result.add_argument('--dry-run',
+ default=False,
+ action='store_true',
+ help='Only perform non-commiting tasks and print others.')
+ return result.parse_args()
+
+
+def Main():
+ options = parse_options()
+ gfile = ('/bigstore/r8-releases/smali/%s/smali-maven-release-%s.zip'
+ % (options.version, options.version))
+ release_id = gmaven.publisher_stage([gfile], options.dry_run)
+
+ print('Staged Release ID %s.\n' % release_id)
+ gmaven.publisher_stage_redir_test_info(
+ release_id, 'com.android.tools.smali:smali:%s' % options.version, 'smali.jar')
+
+ print()
+ answer = input('Continue with publishing [y/N]:')
+
+ if answer != 'y':
+ print('Aborting release to Google maven')
+ sys.exit(1)
+
+ gmaven.publisher_publish(release_id, options.dry_run)
+
+ print()
+ print('Published. Use the email workflow for approval.')
+
+if __name__ == '__main__':
+ sys.exit(Main())