Create VersionUtils class and unit tests and extract logic out of flutter.groovy (#163166)

Related to #162103 

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Reid Baker 2025-02-18 12:00:54 -05:00 committed by GitHub
parent 29d1e10a02
commit b18b6a8665
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 105 additions and 45 deletions

View File

@ -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<Project> {
}
}
/**
* 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<Project> {
}
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))
}

View File

@ -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
}
}

View File

@ -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))
}
}