• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Rolls the dev channel.
6// Only tested on Linux.
7//
8// See: https://github.com/flutter/flutter/wiki/Release-process
9
10import 'dart:io';
11
12import 'package:args/args.dart';
13
14const String kIncrement = 'increment';
15const String kX = 'x';
16const String kY = 'y';
17const String kZ = 'z';
18const String kCommit = 'commit';
19const String kOrigin = 'origin';
20const String kJustPrint = 'just-print';
21const String kYes = 'yes';
22const String kHelp = 'help';
23
24const String kUpstreamRemote = 'git@github.com:flutter/flutter.git';
25
26void main(List<String> args) {
27  final ArgParser argParser = ArgParser(allowTrailingOptions: false);
28  argParser.addOption(
29    kIncrement,
30    help: 'Specifies which part of the x.y.z version number to increment. Required.',
31    valueHelp: 'level',
32    allowed: <String>[kX, kY, kZ],
33    allowedHelp: <String, String>{
34      kX: 'Indicates a major development, e.g. typically changed after a big press event.',
35      kY: 'Indicates a minor development, e.g. typically changed after a beta release.',
36      kZ: 'Indicates the least notable level of change. You normally want this.',
37    },
38  );
39  argParser.addOption(
40    kCommit,
41    help: 'Specifies which git commit to roll to the dev branch.',
42    valueHelp: 'hash',
43    defaultsTo: 'upstream/master',
44  );
45  argParser.addOption(
46    kOrigin,
47    help: 'Specifies the name of the upstream repository',
48    valueHelp: 'repository',
49    defaultsTo: 'upstream',
50  );
51  argParser.addFlag(
52    kJustPrint,
53    negatable: false,
54    help:
55        'Don\'t actually roll the dev channel; '
56        'just print the would-be version and quit.',
57  );
58  argParser.addFlag(kYes, negatable: false, abbr: 'y', help: 'Skip the confirmation prompt.');
59  argParser.addFlag(kHelp, negatable: false, help: 'Show this help message.', hide: true);
60  ArgResults argResults;
61  try {
62    argResults = argParser.parse(args);
63  } on ArgParserException catch (error) {
64    print(error.message);
65    print(argParser.usage);
66    exit(1);
67  }
68
69  final String level = argResults[kIncrement];
70  final String commit = argResults[kCommit];
71  final String origin = argResults[kOrigin];
72  final bool justPrint = argResults[kJustPrint];
73  final bool autoApprove = argResults[kYes];
74  final bool help = argResults[kHelp];
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    print(argParser.usage);
79    exit(0);
80  }
81
82  if (getGitOutput('remote get-url $origin', 'check whether this is a flutter checkout') != kUpstreamRemote) {
83    print('The current directory is not a Flutter repository checkout with a correctly configured upstream remote.');
84    print('For more details see: https://github.com/flutter/flutter/wiki/Release-process');
85    exit(1);
86  }
87
88  runGit('checkout master', 'switch to master branch');
89
90  if (getGitOutput('status --porcelain', 'check status of your local checkout') != '') {
91    print('Your git repository is not clean. Try running "git clean -fd". Warning, this ');
92    print('will delete files! Run with -n to find out which ones.');
93    exit(1);
94  }
95
96  runGit('fetch $origin', 'fetch $origin');
97  runGit('reset $commit --hard', 'check out master branch');
98
99  String version = getFullTag();
100  final Match match = parseFullTag(version);
101  if (match == null) {
102    print('Could not determine the version for this build.');
103    if (version.isNotEmpty)
104      print('Git reported the latest version as "$version", which does not fit the expected pattern.');
105    exit(1);
106  }
107
108  final List<int> parts = match.groups(<int>[1, 2, 3]).map<int>(int.parse).toList();
109
110  if (match.group(4) == '0') {
111    print('This commit has already been released, as version ${parts.join(".")}.');
112    exit(0);
113  }
114
115  switch (level) {
116    case kX:
117      parts[0] += 1;
118      parts[1] = 0;
119      parts[2] = 0;
120      break;
121    case kY:
122      parts[1] += 1;
123      parts[2] = 0;
124      break;
125    case kZ:
126      parts[2] += 1;
127      break;
128    default:
129      print('Unknown increment level. The valid values are "$kX", "$kY", and "$kZ".');
130      exit(1);
131  }
132  version = parts.join('.');
133
134  if (justPrint) {
135    print(version);
136    exit(0);
137  }
138
139  final String hash = getGitOutput('rev-parse HEAD', 'Get git hash for $commit');
140
141  runGit('tag v$version', 'tag the commit with the version label');
142
143  // PROMPT
144
145  if (autoApprove) {
146    print('Publishing Flutter $version (${hash.substring(0, 10)}) to the "dev" channel.');
147  } else {
148    print('Your tree is ready to publish Flutter $version (${hash.substring(0, 10)}) '
149      'to the "dev" channel.');
150    stdout.write('Are you? [yes/no] ');
151    if (stdin.readLineSync() != 'yes') {
152      runGit('tag -d v$version', 'remove the tag you did not want to publish');
153      print('The dev roll has been aborted.');
154      exit(0);
155    }
156  }
157
158  runGit('push $origin v$version', 'publish the version');
159  runGit('push $origin HEAD:dev', 'land the new version on the "dev" branch');
160  print('Flutter version $version has been rolled to the "dev" channel!');
161}
162
163String getFullTag() {
164  return getGitOutput(
165    'describe --match v*.*.* --first-parent --long --tags',
166    'obtain last released version number',
167  );
168}
169
170Match parseFullTag(String version) {
171  final RegExp versionPattern = RegExp('^v([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)-g([a-f0-9]+)\$');
172  return versionPattern.matchAsPrefix(version);
173}
174
175String getGitOutput(String command, String explanation) {
176  final ProcessResult result = _runGit(command);
177  if (result.stderr.isEmpty && result.exitCode == 0)
178    return result.stdout.trim();
179  _reportGitFailureAndExit(result, explanation);
180  return null; // for the analyzer's sake
181}
182
183void runGit(String command, String explanation) {
184  final ProcessResult result = _runGit(command);
185  if (result.exitCode != 0)
186    _reportGitFailureAndExit(result, explanation);
187}
188
189ProcessResult _runGit(String command) {
190  return Process.runSync('git', command.split(' '));
191}
192
193void _reportGitFailureAndExit(ProcessResult result, String explanation) {
194  if (result.exitCode != 0) {
195    print('Failed to $explanation. Git exited with error code ${result.exitCode}.');
196  } else {
197    print('Failed to $explanation.');
198  }
199  if (result.stdout.isNotEmpty)
200    print('stdout from git:\n${result.stdout}\n');
201  if (result.stderr.isNotEmpty)
202    print('stderr from git:\n${result.stderr}\n');
203  exit(1);
204}
205