roll_dev.dart 6.47 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Rolls the dev channel.
// Only tested on Linux.
//
// See: https://github.com/flutter/flutter/wiki/Release-process

import 'dart:io';

import 'package:args/args.dart';

const String kIncrement = 'increment';
const String kX = 'x';
const String kY = 'y';
const String kZ = 'z';
18
const String kCommit = 'commit';
19
const String kOrigin = 'origin';
20
const String kJustPrint = 'just-print';
21
const String kYes = 'yes';
22 23
const String kHelp = 'help';

24
const String kUpstreamRemote = 'git@github.com:flutter/flutter.git';
25

26
void main(List<String> args) {
27
  final ArgParser argParser = ArgParser(allowTrailingOptions: false);
28 29 30 31 32 33 34 35 36 37 38
  argParser.addOption(
    kIncrement,
    help: 'Specifies which part of the x.y.z version number to increment. Required.',
    valueHelp: 'level',
    allowed: <String>[kX, kY, kZ],
    allowedHelp: <String, String>{
      kX: 'Indicates a major development, e.g. typically changed after a big press event.',
      kY: 'Indicates a minor development, e.g. typically changed after a beta release.',
      kZ: 'Indicates the least notable level of change. You normally want this.',
    },
  );
39 40 41 42 43 44
  argParser.addOption(
    kCommit,
    help: 'Specifies which git commit to roll to the dev branch.',
    valueHelp: 'hash',
    defaultsTo: 'upstream/master',
  );
45 46 47 48 49 50
  argParser.addOption(
    kOrigin,
    help: 'Specifies the name of the upstream repository',
    valueHelp: 'repository',
    defaultsTo: 'upstream',
  );
51 52 53 54 55 56 57
  argParser.addFlag(
    kJustPrint,
    negatable: false,
    help:
        'Don\'t actually roll the dev channel; '
        'just print the would-be version and quit.',
  );
58
  argParser.addFlag(kYes, negatable: false, abbr: 'y', help: 'Skip the confirmation prompt.');
59 60 61 62 63 64 65 66 67 68
  argParser.addFlag(kHelp, negatable: false, help: 'Show this help message.', hide: true);
  ArgResults argResults;
  try {
    argResults = argParser.parse(args);
  } on ArgParserException catch (error) {
    print(error.message);
    print(argParser.usage);
    exit(1);
  }

69 70 71 72 73 74
  final String level = argResults[kIncrement] as String;
  final String commit = argResults[kCommit] as String;
  final String origin = argResults[kOrigin] as String;
  final bool justPrint = argResults[kJustPrint] as bool;
  final bool autoApprove = argResults[kYes] as bool;
  final bool help = argResults[kHelp] as bool;
75 76

  if (help || level == null) {
77
    print('roll_dev.dart --increment=level --commit=hash • update the version tags and roll a new dev build.\n');
78 79 80 81
    print(argParser.usage);
    exit(0);
  }

82
  if (getGitOutput('remote get-url $origin', 'check whether this is a flutter checkout') != kUpstreamRemote) {
83 84 85 86 87
    print('The current directory is not a Flutter repository checkout with a correctly configured upstream remote.');
    print('For more details see: https://github.com/flutter/flutter/wiki/Release-process');
    exit(1);
  }

88 89 90 91 92 93 94 95
  runGit('checkout master', 'switch to master branch');

  if (getGitOutput('status --porcelain', 'check status of your local checkout') != '') {
    print('Your git repository is not clean. Try running "git clean -fd". Warning, this ');
    print('will delete files! Run with -n to find out which ones.');
    exit(1);
  }

96
  runGit('fetch $origin', 'fetch $origin');
97 98
  runGit('reset $commit --hard', 'check out master branch');

99 100 101 102 103 104 105 106 107
  String version = getFullTag();
  final Match match = parseFullTag(version);
  if (match == null) {
    print('Could not determine the version for this build.');
    if (version.isNotEmpty)
      print('Git reported the latest version as "$version", which does not fit the expected pattern.');
    exit(1);
  }

108
  final List<int> parts = match.groups(<int>[1, 2, 3]).map<int>(int.parse).toList();
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

  if (match.group(4) == '0') {
    print('This commit has already been released, as version ${parts.join(".")}.');
    exit(0);
  }

  switch (level) {
    case kX:
      parts[0] += 1;
      parts[1] = 0;
      parts[2] = 0;
      break;
    case kY:
      parts[1] += 1;
      parts[2] = 0;
      break;
    case kZ:
      parts[2] += 1;
      break;
    default:
      print('Unknown increment level. The valid values are "$kX", "$kY", and "$kZ".');
      exit(1);
  }
  version = parts.join('.');

134 135 136 137 138
  if (justPrint) {
    print(version);
    exit(0);
  }

139 140
  final String hash = getGitOutput('rev-parse HEAD', 'Get git hash for $commit');

141
  runGit('tag v$version', 'tag the commit with the version label');
142 143

  // PROMPT
144

145 146 147 148 149 150 151 152 153 154 155
  if (autoApprove) {
    print('Publishing Flutter $version (${hash.substring(0, 10)}) to the "dev" channel.');
  } else {
    print('Your tree is ready to publish Flutter $version (${hash.substring(0, 10)}) '
      'to the "dev" channel.');
    stdout.write('Are you? [yes/no] ');
    if (stdin.readLineSync() != 'yes') {
      runGit('tag -d v$version', 'remove the tag you did not want to publish');
      print('The dev roll has been aborted.');
      exit(0);
    }
156 157
  }

158 159
  runGit('push $origin v$version', 'publish the version');
  runGit('push $origin HEAD:dev', 'land the new version on the "dev" branch');
160 161 162 163 164
  print('Flutter version $version has been rolled to the "dev" channel!');
}

String getFullTag() {
  return getGitOutput(
165
    'describe --match v*.*.* --first-parent --long --tags',
166 167 168 169 170
    'obtain last released version number',
  );
}

Match parseFullTag(String version) {
171
  final RegExp versionPattern = RegExp('^v([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)-g([a-f0-9]+)\$');
172 173 174 175 176
  return versionPattern.matchAsPrefix(version);
}

String getGitOutput(String command, String explanation) {
  final ProcessResult result = _runGit(command);
177 178
  if ((result.stderr as String).isEmpty && result.exitCode == 0)
    return (result.stdout as String).trim();
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
  _reportGitFailureAndExit(result, explanation);
  return null; // for the analyzer's sake
}

void runGit(String command, String explanation) {
  final ProcessResult result = _runGit(command);
  if (result.exitCode != 0)
    _reportGitFailureAndExit(result, explanation);
}

ProcessResult _runGit(String command) {
  return Process.runSync('git', command.split(' '));
}

void _reportGitFailureAndExit(ProcessResult result, String explanation) {
  if (result.exitCode != 0) {
Josh Soref's avatar
Josh Soref committed
195
    print('Failed to $explanation. Git exited with error code ${result.exitCode}.');
196 197 198
  } else {
    print('Failed to $explanation.');
  }
199
  if ((result.stdout as String).isNotEmpty)
200
    print('stdout from git:\n${result.stdout}\n');
201
  if ((result.stderr as String).isNotEmpty)
202 203 204
    print('stderr from git:\n${result.stderr}\n');
  exit(1);
}