// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Possible string formats that `flutter --version` can return. enum VersionType { /// A stable flutter release. /// /// Example: '1.2.3' stable, /// A pre-stable flutter release. /// /// Example: '1.2.3-4.5.pre' development, /// A master channel flutter version. /// /// Example: '1.2.3-4.0.pre.10' /// /// The last number is the number of commits past the last tagged version. latest, } final Map versionPatterns = { VersionType.stable: RegExp(r'^(\d+)\.(\d+)\.(\d+)$'), VersionType.development: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre$'), VersionType.latest: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre\.(\d+)$'), }; class Version { Version({ required this.x, required this.y, required this.z, this.m, this.n, this.commits, required this.type, }) { switch (type) { case VersionType.stable: assert(m == null); assert(n == null); assert(commits == null); break; case VersionType.development: assert(m != null); assert(n != null); assert(commits == null); break; case VersionType.latest: assert(m != null); assert(n != null); assert(commits != null); break; } } /// Create a new [Version] from a version string. /// /// It is expected that [versionString] will be generated by /// `flutter --version` and match one of `stablePattern`, `developmentPattern` /// and `latestPattern`. factory Version.fromString(String versionString) { assert(versionString != null); versionString = versionString.trim(); // stable tag Match? match = versionPatterns[VersionType.stable]!.firstMatch(versionString); if (match != null) { // parse stable final List parts = match .groups([1, 2, 3]) .map((String? s) => int.parse(s!)) .toList(); return Version( x: parts[0], y: parts[1], z: parts[2], type: VersionType.stable, ); } // development tag match = versionPatterns[VersionType.development]!.firstMatch(versionString); if (match != null) { // parse development final List parts = match.groups([1, 2, 3, 4, 5]).map((String? s) => int.parse(s!)).toList(); return Version( x: parts[0], y: parts[1], z: parts[2], m: parts[3], n: parts[4], type: VersionType.development, ); } // latest tag match = versionPatterns[VersionType.latest]!.firstMatch(versionString); if (match != null) { // parse latest final List parts = match.groups( [1, 2, 3, 4, 5, 6], ).map( (String? s) => int.parse(s!), ).toList(); return Version( x: parts[0], y: parts[1], z: parts[2], m: parts[3], n: parts[4], commits: parts[5], type: VersionType.latest, ); } throw Exception('${versionString.trim()} cannot be parsed'); } // Returns a new version with the given [increment] part incremented. // NOTE new version must be of same type as previousVersion. factory Version.increment( Version previousVersion, String increment, { VersionType? nextVersionType, }) { final int nextX = previousVersion.x; int nextY = previousVersion.y; int nextZ = previousVersion.z; int? nextM = previousVersion.m; int? nextN = previousVersion.n; if (nextVersionType == null) { if (previousVersion.type == VersionType.latest) { nextVersionType = VersionType.development; } else { nextVersionType = previousVersion.type; } } switch (increment) { case 'x': // This was probably a mistake. throw Exception('Incrementing x is not supported by this tool.'); case 'y': // Dev release following a beta release. nextY += 1; nextZ = 0; if (previousVersion.type != VersionType.stable) { nextM = 0; nextN = 0; } break; case 'z': // Hotfix to stable release. assert(previousVersion.type == VersionType.stable); nextZ += 1; break; case 'm': // Regular dev release. assert(previousVersion.type == VersionType.development); nextM = nextM! + 1; nextN = 0; break; case 'n': // Hotfix to internal roll. nextN = nextN! + 1; break; default: throw Exception('Unknown increment level $increment.'); } return Version( x: nextX, y: nextY, z: nextZ, m: nextM, n: nextN, type: nextVersionType, ); } /// Major version. final int x; /// Zero-indexed count of beta releases after a major release. final int y; /// Number of hotfix releases after a stable release. final int z; /// Zero-indexed count of dev releases after a beta release. final int? m; /// Number of hotfixes required to make a dev release. final int? n; /// Number of commits past last tagged dev release. final int? commits; final VersionType type; @override String toString() { switch (type) { case VersionType.stable: return '$x.$y.$z'; case VersionType.development: return '$x.$y.$z-$m.$n.pre'; case VersionType.latest: return '$x.$y.$z-$m.$n.pre.$commits'; } } }