diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy index c247e4430e..3103ff108d 100644 --- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy +++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy @@ -8,6 +8,7 @@ import com.flutter.gradle.BaseApplicationNameHandler import com.flutter.gradle.Deeplink import com.flutter.gradle.DependencyVersionChecker import com.flutter.gradle.IntentFilterCheck +import com.flutter.gradle.VersionUtils import groovy.json.JsonGenerator import groovy.xml.QName import java.nio.file.Paths @@ -789,50 +790,6 @@ class FlutterPlugin implements Plugin { } } - /** - * Compares semantic versions ignoring labels. - * - * If the versions are equal (ignoring labels), returns one of the two strings arbitrarily. - * If minor or patch are omitted (non-conformant to semantic versioning), they are considered zero. - * If the provided versions in both are equal, the longest version string is returned. - * For example, "2.8.0" vs "2.8" will always consider "2.8.0" to be the most recent version. - * For another example, "8.7-rc-2" vs "8.7.2" will always consider "8.7.2" to be the most recent version. - */ - static String mostRecentSemanticVersion(String version1, String version2) { - def v1Parts = version1.tokenize('.-') - def v2Parts = version2.tokenize('.-') - - for (int i = 0; i < Math.max(v1Parts.size(), v2Parts.size()); i++) { - def v1Part = i < v1Parts.size() ? v1Parts[i] : '0' - def v2Part = i < v2Parts.size() ? v2Parts[i] : '0' - - def v1Num = v1Part.isNumber() ? v1Part.toInteger() : 0 - def v2Num = v2Part.isNumber() ? v2Part.toInteger() : 0 - - if (v1Num != v2Num) { - return v1Num > v2Num ? version1 : version2 - } - - if (v1Part.isNumber() && !v2Part.isNumber()) { - return version1 - } else if (!v1Part.isNumber() && v2Part.isNumber()) { - return version2 - } else if (v1Part != v2Part) { - return comparePreReleaseIdentifiers(v1Part, v2Part) ? version1 : version2 - } - } - - // If versions are equal, return the longest version string - return version1.length() >= version2.length() ? version1 : version2 - } - - static boolean comparePreReleaseIdentifiers(String v1Part, String v2Part) { - def v1PreRelease = v1Part.replaceAll("\\D", "") - def v2PreRelease = v2Part.replaceAll("\\D", "") - - return v1PreRelease < v2PreRelease - } - private void forceNdkDownload(Project gradleProject, String flutterSdkRootPath) { // If the project is already configuring a native build, we don't need to do anything. Boolean forcingNotRequired = gradleProject.android.externalNativeBuild.cmake.path != null @@ -900,7 +857,7 @@ class FlutterPlugin implements Plugin { } String pluginNdkVersion = pluginProject.android.ndkVersion ?: ndkVersionIfUnspecified - maxPluginNdkVersion = mostRecentSemanticVersion(pluginNdkVersion, maxPluginNdkVersion) + maxPluginNdkVersion = VersionUtils.mostRecentSemanticVersion(pluginNdkVersion, maxPluginNdkVersion) if (pluginNdkVersion != projectNdkVersion) { pluginsWithDifferentNdkVersion.add(new Tuple(pluginProject.name, pluginNdkVersion)) } diff --git a/packages/flutter_tools/gradle/src/main/kotlin/VersionUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/VersionUtils.kt new file mode 100644 index 0000000000..8f00e7ac8e --- /dev/null +++ b/packages/flutter_tools/gradle/src/main/kotlin/VersionUtils.kt @@ -0,0 +1,67 @@ +// 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. + +package com.flutter.gradle + +import kotlin.math.max + +object VersionUtils { + /** + * Compares semantic versions ignoring labels. + * + * If the versions are equal (ignoring labels), returns one of the two strings arbitrarily. If + * minor or patch are omitted (non-conformant to semantic versioning), they are considered zero. + * If the provided versions in both are equal, the longest version string is returned. For + * example, "2.8.0" vs "2.8" will always consider "2.8.0" to be the most recent version. For + * another example, "8.7-rc-2" vs "8.7.2" will always consider "8.7.2" to be the most recent + * version. + */ + @JvmStatic + fun mostRecentSemanticVersion( + version1: String, + version2: String + ): String { + val v1Parts = version1.split(".", "-") + val v2Parts = version2.split(".", "-") + val maxSize = max(v1Parts.size, v2Parts.size) + + for (i in 0..maxSize - 1) { + val v1Part: String = v1Parts.getOrNull(i) ?: "0" + val v2Part: String = v2Parts.getOrNull(i) ?: "0" + + val v1Num: Int? = v1Part.toIntOrNull() + val v2Num: Int? = v2Part.toIntOrNull() + when { + v1Num != null && v2Num != null -> { // Both are numbers + if (v1Num != v2Num) { + return if (v1Num > v2Num) version1 else version2 + } + } + v1Num != null && v2Num == null -> + return version1 // v1 is a number, v2 is not, so v1 is newer. + v1Num == null && v2Num != null -> + return version2 // v1 is not a number, v2 is, so v2 is newer. + v1Num == null && v2Num == null -> { // Both are not numbers (pre-release identifiers) + if (v1Part != v2Part) { + return if (comparePreReleaseIdentifiers(v1Part, v2Part)) version1 else version2 + } + } + } + } + + // If versions are equal, return the longest version string + return if (version1.length >= version2.length) version1 else version2 + } + + /** Compares only non digits and returns true if v1Part is than v2Part. */ + private fun comparePreReleaseIdentifiers( + v1Part: String, + v2Part: String + ): Boolean { + val digits = Regex("\\d") + val v1PreRelease = v1Part.replace(digits, "") + val v2PreRelease = v2Part.replace(digits, "") + return v1PreRelease < v2PreRelease + } +} diff --git a/packages/flutter_tools/gradle/src/test/kotlin/VersionUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/VersionUtilsTest.kt new file mode 100644 index 0000000000..c665907b39 --- /dev/null +++ b/packages/flutter_tools/gradle/src/test/kotlin/VersionUtilsTest.kt @@ -0,0 +1,36 @@ +package com.flutter.gradle + +import kotlin.test.Test +import kotlin.test.assertEquals + +class VersionUtilsTest { + @Test + fun handles_documenation_examples() { + versionComparison("2.8.0", "2.8", expected = "2.8.0") + versionComparison("8.7-rc-2", "8.7.2", expected = "8.7.2") + } + + @Test + fun expanded_examples() { + versionComparison("1.2", "1.2.0", expected = "1.2.0") + versionComparison("1.0", "1", expected = "1.0") + versionComparison("1.2.0-alpha", "1.2", expected = "1.2") + versionComparison("1.2.3", "1.2.3", expected = "1.2.3") + versionComparison("1.2.3-beta", "1.2.3", expected = "1.2.3") + versionComparison("1.2.3", "1.2.3.4", expected = "1.2.3.4") + versionComparison("rc-2", "rc-1", expected = "rc-2") + versionComparison("8.7-rc-1", "8.7", expected = "8.7") + versionComparison("8.7-rc-1", "8.7.2", expected = "8.7.2") + versionComparison("8.7.2", "8.7.1", expected = "8.7.2") + versionComparison("7.0.2", "8.7.1", expected = "8.7.1") + versionComparison("8.1", "7.5", expected = "8.1") + } + + fun versionComparison( + version1: String, + version2: String, + expected: String + ) { + assertEquals(expected, VersionUtils.mostRecentSemanticVersion(version1, version2)) + } +}