Skip to content

Commit 68e3921

Browse files
committed
Add Metalava SemVer task
1 parent 1dd0650 commit 68e3921

File tree

5 files changed

+138
-1
lines changed

5 files changed

+138
-1
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Metalava SemVer Check
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
build:
8+
runs-on: ubuntu-latest
9+
permissions:
10+
pull-requests: write
11+
steps:
12+
- name: Checkout main
13+
uses: actions/[email protected]
14+
with:
15+
ref: ${{ github.base_ref }}
16+
17+
- name: Set up JDK 17
18+
uses: actions/[email protected]
19+
with:
20+
java-version: 17
21+
distribution: temurin
22+
cache: gradle
23+
24+
- name: Copy previous api.txt files
25+
run: ./gradlew copyApiTxtFile
26+
27+
- name: Checkout PR
28+
uses: actions/[email protected]
29+
30+
- name: Run Metalava SemVer check
31+
run: ./gradlew metalavaSemver

plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseAndroidLibraryPlugin.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ class FirebaseAndroidLibraryPlugin : BaseFirebaseLibraryPlugin() {
165165
apiTxtFile.set(project.file("api.txt"))
166166
output.set(project.file("previous_api.txt"))
167167
}
168+
169+
project.tasks.register<SemVerTask>("metalavaSemver") {
170+
apiTxtFile.set(project.file("api.txt"))
171+
otherApiFile.set(project.file("previous_api.txt"))
172+
outputApiFile.set(project.file("opi.txt"))
173+
currentVersionString.value(firebaseLibrary.version)
174+
previousVersionString.value(firebaseLibrary.previousVersion)
175+
}
168176
}
169177

170178
private fun setupApiInformationAnalysis(project: Project, android: LibraryExtension) {

plugins/src/main/java/com/google/firebase/gradle/plugins/FirebaseJavaLibraryPlugin.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ class FirebaseJavaLibraryPlugin : BaseFirebaseLibraryPlugin() {
108108
apiTxtFile.set(project.file("api.txt"))
109109
output.set(project.file("previous_api.txt"))
110110
}
111+
112+
project.tasks.register<SemVerTask>("metalavaSemver") {
113+
apiTxtFile.set(project.file("api.txt"))
114+
otherApiFile.set(project.file("previous_api.txt"))
115+
outputApiFile.set(project.file("opi.txt"))
116+
currentVersionString.value(firebaseLibrary.version)
117+
previousVersionString.value(firebaseLibrary.previousVersion)
118+
}
111119
}
112120

113121
private fun setupApiInformationAnalysis(project: Project) {

plugins/src/main/java/com/google/firebase/gradle/plugins/Metalava.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ fun Project.runMetalavaWithArgs(
5555
) {
5656
val allArgs =
5757
listOf(
58-
"--no-banner",
5958
"--hide",
6059
"HiddenSuperclass", // We allow having a hidden parent class
6160
"--hide",
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.google.firebase.gradle.plugins
2+
3+
import com.google.firebase.gradle.plugins.semver.VersionDelta
4+
import org.gradle.api.DefaultTask
5+
import org.gradle.api.GradleException
6+
import org.gradle.api.file.RegularFileProperty
7+
import org.gradle.api.provider.Property
8+
import org.gradle.api.tasks.Input
9+
import org.gradle.api.tasks.InputFile
10+
import org.gradle.api.tasks.OutputFile
11+
import org.gradle.api.tasks.TaskAction
12+
import java.io.ByteArrayOutputStream
13+
14+
abstract class SemVerTask : DefaultTask() {
15+
@get:InputFile
16+
abstract val apiTxtFile: RegularFileProperty
17+
@get:InputFile
18+
abstract val otherApiFile: RegularFileProperty
19+
@get:Input
20+
abstract val currentVersionString: Property<String>
21+
@get:Input
22+
abstract val previousVersionString: Property<String>
23+
24+
@get:OutputFile
25+
abstract val outputApiFile: RegularFileProperty
26+
27+
@TaskAction
28+
fun run() {
29+
val regex = Regex("\\d+\\.\\d+\\.\\d.*")
30+
if (!previousVersionString.get().matches(regex) || !currentVersionString.get().matches(regex)) {
31+
return // If these variables don't exist, no reason to check API
32+
}
33+
val (previousMajor, previousMinor, previousPatch) = previousVersionString.get().split(".")
34+
val (currentMajor, currentMinor, currentPatch) = currentVersionString.get().split(".")
35+
val bump = if (previousMajor != currentMajor) VersionDelta.MAJOR else if (previousMinor != currentMinor) VersionDelta.MINOR else VersionDelta.PATCH
36+
val stream = ByteArrayOutputStream()
37+
project.runMetalavaWithArgs(
38+
listOf(
39+
"--source-files",
40+
apiTxtFile.get().asFile.absolutePath,
41+
"--check-compatibility:api:released",
42+
otherApiFile.get().asFile.absolutePath,
43+
)
44+
+ MAJOR.flatMap{ m -> listOf("--error", m) }
45+
+ MINOR.flatMap{ m -> listOf("--error", m) }
46+
+ IGNORED.flatMap{ m -> listOf("--hide", m) }
47+
+ listOf(
48+
"--format=v3",
49+
"--no-color",
50+
),
51+
ignoreFailure = true,
52+
stdOut = stream
53+
)
54+
55+
val string = String(stream.toByteArray())
56+
val reg = Regex("(.*)\\s+error:\\s+(.*\\s+\\[(.*)\\])")
57+
val minorChanges = mutableListOf<String>()
58+
val majorChanges = mutableListOf<String>()
59+
for (match in reg.findAll(string)) {
60+
val loc = match.groups[1]!!.value
61+
val message = match.groups[2]!!.value
62+
val type = match.groups[3]!!.value
63+
if (IGNORED.contains(type)) {
64+
continue // Shouldn't be possible
65+
} else if (MINOR.contains(type)) {
66+
minorChanges.add(message)
67+
} else {
68+
majorChanges.add(message)
69+
}
70+
}
71+
val allChanges =
72+
(majorChanges
73+
.joinToString(separator = "") { m -> " MAJOR: $m\n" }) +
74+
minorChanges
75+
.joinToString(separator = "") { m -> " MINOR: $m\n" }
76+
if (majorChanges.isNotEmpty()) {
77+
if (bump != VersionDelta.MAJOR) {
78+
throw GradleException("API has non-bumped breaking MAJOR changes\nCurrent version bump is ${bump}, update the gradle.properties or fix the changes\n$allChanges")
79+
}
80+
} else if (minorChanges.isNotEmpty()) {
81+
if (bump != VersionDelta.MAJOR && bump != VersionDelta.MINOR) {
82+
throw GradleException("API has non-bumped MINOR changes\nCurrent version bump is ${bump}, update the gradle.properties or fix the changes\n$allChanges")
83+
}
84+
}
85+
}
86+
companion object {
87+
private val MAJOR = setOf("AddedFinal")
88+
private val MINOR = setOf("AddedClass", "AddedMethod", "AddedField", "ChangedDeprecated")
89+
private val IGNORED = setOf("ReferencesDeprecated")
90+
}
91+
}

0 commit comments

Comments
 (0)