Add version creation to release script.

Change-Id: Ia5c133fec09ef2fcc7ede869bc28ee40b84f7f31
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 1d3cfbb..5e070ea 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -10,11 +10,124 @@
 import shutil
 import subprocess
 import sys
-import tempfile
-import update_prebuilds_in_android
 import urllib
+
+import update_prebuilds_in_android
 import utils
 
+R8_DEV_BRANCH = '1.7'
+R8_VERSION_FILE = os.path.join(
+    'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
+
+
+def prepare_release(args):
+  if args.version:
+    print "Cannot manually specify version when making a dev release."
+    sys.exit(1)
+
+  def make_release(args):
+    commithash = args.dev_release
+
+    with utils.TempDir() as temp:
+      subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
+      with utils.ChangedWorkingDirectory(temp):
+        subprocess.check_call([
+          'git',
+          'new-branch',
+          '--upstream',
+          'origin/%s' % R8_DEV_BRANCH,
+          'dev-release'])
+
+        # Compute the current and new version on the branch.
+        result = None
+        for line in open(R8_VERSION_FILE, 'r'):
+          result = re.match(
+              r'.*LABEL = "%s\.(\d+)\-dev";' % R8_DEV_BRANCH, line)
+          if result:
+            break
+        if not result or not result.group(1):
+          print 'Failed to find version label matching %s(\d+)-dev'\
+                % R8_DEV_BRANCH
+          sys.exit(1)
+        try:
+          patch_version = int(result.group(1))
+        except ValueError:
+          print 'Failed to convert version to integer: %s' % result.group(1)
+
+        old_version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version)
+        version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version + 1)
+
+        # Verify that the merge point from master is not empty.
+        merge_diff_output = subprocess.check_output([
+          'git', 'diff', 'HEAD..%s' % commithash])
+        other_diff = version_change_diff(
+            merge_diff_output, old_version, "master")
+        if not other_diff:
+          print 'Merge point from master (%s)' % commithash, \
+            'is the same as exiting release (%s).' % old_version
+          sys.exit(1)
+
+        # Merge the desired commit from master on to the branch.
+        subprocess.check_call([
+          'git', 'merge', '--no-ff', '--no-edit', commithash])
+
+        # Rewrite the version, commit and validate.
+        sed(old_version, version, R8_VERSION_FILE)
+
+        subprocess.check_call([
+          'git', 'commit', '-a', '-m', '"Version %s"' % version])
+
+        version_diff_output = subprocess.check_output([
+          'git', 'diff', '%s..HEAD' % commithash])
+
+        invalid = version_change_diff(version_diff_output, "master", version)
+        if invalid:
+          print "Unexpected diff content for line:"
+          print invalid
+          sys.exit(1)
+
+        # Double check that we want to push the release.
+        if not args.dry_run:
+          input = raw_input('Publish dev release version %s [y/N]:' % version)
+          if input != 'y':
+            print 'Aborting dev release for %s' % version
+            sys.exit(1)
+
+        maybe_check_call(args, [
+          'git', 'push', 'origin', 'HEAD:%s' % R8_DEV_BRANCH])
+        maybe_check_call(args, [
+          'git', 'tag', '-a', version, '-m', '"%s"' % version])
+        maybe_check_call(args, [
+          'git', 'push', 'origin', 'refs/tags/%s' % version])
+
+        return "%s dev version %s from hash %s" % (
+          'DryRun: omitted publish of' if args.dry_run else 'Published',
+          version,
+          commithash)
+
+  return make_release
+
+
+def version_change_diff(diff, old_version, new_version):
+  invalid_line = None
+  for line in diff.splitlines():
+    if line.startswith('-  ') and \
+        line != '-  public static final String LABEL = "%s";' % old_version:
+      invalid_line = line
+    elif line.startswith('+  ') and \
+        line != '+  public static final String LABEL = "%s";' % new_version:
+      invalid_line = line
+  return invalid_line
+
+
+def maybe_check_call(args, cmd):
+  if args.dry_run:
+    print 'DryRun:', ' '.join(cmd)
+  else:
+    print ' '.join(cmd)
+    return subprocess.check_call(cmd)
+
+
 def update_prebuilds(version, checkout):
   update_prebuilds_in_android.main_download('', True, 'lib', checkout, version)
 
@@ -40,10 +153,14 @@
 
 
 def prepare_aosp(args):
+  assert args.version
   assert os.path.exists(args.aosp), "Could not find AOSP path %s" % args.aosp
 
   def release_aosp(options):
     print "Releasing for AOSP"
+    if options.dry_run:
+      return 'DryRun: omitting AOSP release for %s' % options.version
+
     git_message = ("""Update D8 and R8 to %s
 
 Version: master %s
@@ -80,11 +197,15 @@
 
 
 def prepare_studio(args):
+  assert args.version
   assert os.path.exists(args.studio), ("Could not find STUDIO path %s"
                                        % args.studio)
 
   def release_studio(options):
     print "Releasing for STUDIO"
+    if options.dry_run:
+      return 'DryRun: omitting studio release for %s' % options.version
+
     git_message = (git_message_dev(options.version)
                    if 'dev' in options.version
                    else git_message_release(options.version, options.bug))
@@ -107,7 +228,8 @@
 
 def g4_change(version, r8version):
   return subprocess.check_output(
-      'g4 change --desc "Update R8 to version %s %s"' % (version, r8version), shell=True)
+      'g4 change --desc "Update R8 to version %s %s"' % (version, r8version),
+      shell=True)
 
 
 def sed(pattern, replace, path):
@@ -129,7 +251,8 @@
       'blaze run %s' % target, shell=True, stderr=subprocess.STDOUT)
 
 
-def prepare_google3():
+def prepare_google3(args):
+  assert args.version
   # Check if an existing client exists.
   if ':update-r8:' in subprocess.check_output('g4 myclients', shell=True):
     print "Remove the existing 'update-r8' client before continuing."
@@ -137,6 +260,8 @@
 
   def release_google3(options):
     print "Releasing for Google 3"
+    if options.dry_run:
+      return 'DryRun: omitting g3 release for %s' % options.version
 
     google3_base = subprocess.check_output(
         ['p4', 'g4d', '-f', 'update-r8']).rstrip()
@@ -212,8 +337,10 @@
 
 def parse_options():
   result = argparse.ArgumentParser(description='Release r8')
-  result.add_argument('--version',
-                      required=True,
+  group = result.add_mutually_exclusive_group()
+  group.add_argument('--dev-release',
+                      help='The hash to use for the new dev version of R8')
+  group.add_argument('--version',
                       help='The new version of R8 (e.g., 1.4.51)')
   result.add_argument('--no-sync', '--no_sync',
                       default=False,
@@ -233,8 +360,12 @@
                       default=False,
                       action='store_true',
                       help='Release for google 3')
+  result.add_argument('--dry-run',
+                      default=False,
+                      action='store_true',
+                      help='Only perform non-commiting tasks and print others.')
   args = result.parse_args()
-  if not 'dev' in args.version and args.bug == []:
+  if args.version and not 'dev' in args.version and args.bug == []:
     print "When releasing a release version add the list of bugs by using '--bug'"
     sys.exit(1)
 
@@ -244,11 +375,18 @@
 def main():
   args = parse_options()
   targets_to_run = []
+
+  if args.dev_release:
+    if args.google3 or args.studio or args.aosp:
+      print 'Cannot create a dev release and roll at the same time.'
+      sys.exit(1)
+    targets_to_run.append(prepare_release(args))
+
   if args.google3 or args.studio:
     utils.check_prodacces()
 
   if args.google3:
-    targets_to_run.append(prepare_google3())
+    targets_to_run.append(prepare_google3(args))
   if args.studio:
     targets_to_run.append(prepare_studio(args))
   if args.aosp:
diff --git a/tools/utils.py b/tools/utils.py
index fb937e6..cede4c7 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -34,6 +34,7 @@
 GENERATED_LICENSE_DIR = os.path.join(BUILD, 'generatedLicense')
 SRC_ROOT = os.path.join(REPO_ROOT, 'src', 'main', 'java')
 TEST_ROOT = os.path.join(REPO_ROOT, 'src', 'test', 'java')
+REPO_SOURCE = 'https://r8.googlesource.com/r8'
 
 D8 = 'd8'
 R8 = 'r8'