// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Updates the version numbers of the Flutter repo. // Only tested on Linux. // // See: https://github.com/flutter/flutter/wiki/Release-process import 'dart:io'; import 'package:args/args.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; const String kIncrement = 'increment'; const String kBrokeSdk = 'broke-sdk'; const String kBrokeFramework = 'broke-framework'; const String kBrokeTest = 'broke-test'; const String kBrokeDriver = 'broke-driver'; const String kMarkRelease = 'release'; const String kHelp = 'help'; const String kYamlVersionPrefix = 'version: '; const String kDev = '-dev'; enum VersionKind { dev, release } void main(List<String> args) { // If we're run from the `tools` dir, set the cwd to the repo root. if (path.basename(Directory.current.path) == 'tools') Directory.current = Directory.current.parent.parent; final ArgParser argParser = new ArgParser(); argParser.addFlag(kIncrement, defaultsTo: false, help: 'Increment all the version numbers. Cannot be specified with --$kMarkRelease or with any --broke-* commands.'); argParser.addFlag(kBrokeSdk, defaultsTo: false, negatable: false, help: 'Increment the Flutter SDK version number to indicate that there has been a breaking change to the SDK (for example, to the command line options).'); argParser.addFlag(kBrokeFramework, defaultsTo: false, negatable: false, help: 'Increment the "flutter" package version number to indicate that there has been a breaking change to the Flutter framework.'); argParser.addFlag(kBrokeTest, defaultsTo: false, negatable: false, help: 'Increment the "flutter_test" package version number to indicate that there has been a breaking change to the test API framework.'); argParser.addFlag(kBrokeDriver, defaultsTo: false, negatable: false, help: 'Increment the "flutter_driver" package version number to indicate that there has been a breaking change to the driver API framework.'); argParser.addFlag(kMarkRelease, defaultsTo: false, help: 'Remove "-dev" from each version number. This is used when releasing. When not present, "-dev" is added to each version number. Cannot be specified with --$kIncrement or with any --broke-* commands.'); argParser.addFlag(kHelp, negatable: false, help: 'Show this help message.'); final ArgResults argResults = argParser.parse(args); final bool increment = argResults[kIncrement]; final bool brokeSdk = argResults[kBrokeSdk]; final bool brokeFramework = argResults[kBrokeFramework]; final bool brokeTest = argResults[kBrokeTest]; final bool brokeDriver = argResults[kBrokeDriver]; final bool brokeAnything = brokeSdk || brokeFramework || brokeTest || brokeDriver; final VersionKind level = argResults[kMarkRelease] ? VersionKind.release : VersionKind.dev; final bool help = argResults[kHelp]; if (help) { print('update_versions.dart - update version numbers of Flutter packages and SDK'); print(argParser.usage); exit(0); } final bool release = level == VersionKind.release; if ((brokeAnything && release) || (brokeAnything && increment) || (release && increment)) { print('You can either increment all the version numbers (--$kIncrement), indicate that some packages have had breaking changes (--broke-*), or switch to release mode (--$kMarkRelease).'); print('You cannot combine these, however.'); exit(1); } final RawVersion sdk = new RawVersion('VERSION'); final PubSpecVersion framework = new PubSpecVersion('packages/flutter/pubspec.yaml'); final PubSpecVersion test = new PubSpecVersion('packages/flutter_test/pubspec.yaml'); final PubSpecVersion driver = new PubSpecVersion('packages/flutter_driver/pubspec.yaml'); if (increment || brokeAnything) sdk.increment(brokeAnything); sdk.setMode(level); if (increment || brokeFramework) framework.increment(brokeFramework); framework.setMode(level); if (increment || brokeTest) test.increment(brokeTest); test.setMode(level); if (increment || brokeDriver) driver.increment(brokeDriver); driver.setMode(level); sdk.write(); framework.write(); test.write(); driver.write(); print('Flutter SDK is now at version: $sdk'); print('flutter package is now at version: $framework'); print('flutter_test package is now at version: $test'); print('flutter_driver package is now at version: $driver'); if (release) { print('\nDuring the tagging step in the instructions, the commands will be:'); print('git tag $sdk'); print('git push upstream $sdk'); } } abstract class Version { Version() { read(); } @protected final List<int> version = <int>[]; @protected VersionKind level; @protected bool dirty = false; @protected void read(); void interpret(String value) { level = value.endsWith(kDev) ? VersionKind.dev : VersionKind.release; if (level == VersionKind.dev) value = value.substring(0, value.length - kDev.length); version.addAll(value.split('.').map<int>(int.parse)); } void increment(bool breaking) { assert(version.length == 3); if (breaking) { version[1] += 1; version[2] = 0; } else { version[2] += 1; } dirty = true; } void setMode(VersionKind value) { if (value != level) { level = value; dirty = true; } } void write(); @override String toString() => version.join('.') + (level == VersionKind.dev ? kDev : ''); } class PubSpecVersion extends Version { PubSpecVersion(this.path); final String path; @override void read() { final List<String> lines = new File(path).readAsLinesSync(); final String versionLine = lines.where((String line) => line.startsWith(kYamlVersionPrefix)).single; interpret(versionLine.substring(kYamlVersionPrefix.length)); } @override void write() { if (!dirty) return; final List<String> lines = new File(path).readAsLinesSync(); for (int index = 0; index < lines.length; index += 1) { final String line = lines[index]; if (line.startsWith(kYamlVersionPrefix)) { lines[index] = '$kYamlVersionPrefix$this'; break; } } new File(path).writeAsStringSync(lines.join('\n') + '\n'); } } class RawVersion extends Version { RawVersion(this.path); final String path; @override void read() { final List<String> lines = new File(path).readAsLinesSync(); interpret(lines.where((String line) => line.isNotEmpty && !line.startsWith('#')).single); } @override void write() { if (!dirty) return; final List<String> lines = new File(path).readAsLinesSync(); for (int index = 0; index < lines.length; index += 1) { final String line = lines[index]; if (line.isNotEmpty && !line.startsWith('#')) { lines[index] = '$this'; break; } } new File(path).writeAsStringSync(lines.join('\n') + '\n'); } }