Skip to content

Commit 01a82b4

Browse files
authored
Merge f8fa7e1 into 16d2ba8
2 parents 16d2ba8 + f8fa7e1 commit 01a82b4

File tree

5 files changed

+154
-1
lines changed

5 files changed

+154
-1
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Metalava SemVer Check
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
semver-check:
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+
with:
30+
ref: ${{ github.head_ref }}
31+
clean: false
32+
33+
- name: Run Metalava SemVer check
34+
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: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.gradle.plugins
18+
19+
import com.google.firebase.gradle.plugins.semver.VersionDelta
20+
import java.io.ByteArrayOutputStream
21+
import org.gradle.api.DefaultTask
22+
import org.gradle.api.GradleException
23+
import org.gradle.api.file.RegularFileProperty
24+
import org.gradle.api.provider.Property
25+
import org.gradle.api.tasks.Input
26+
import org.gradle.api.tasks.InputFile
27+
import org.gradle.api.tasks.OutputFile
28+
import org.gradle.api.tasks.TaskAction
29+
30+
abstract class SemVerTask : DefaultTask() {
31+
@get:InputFile abstract val apiTxtFile: RegularFileProperty
32+
@get:InputFile abstract val otherApiFile: RegularFileProperty
33+
@get:Input abstract val currentVersionString: Property<String>
34+
@get:Input abstract val previousVersionString: Property<String>
35+
36+
@get:OutputFile abstract val outputApiFile: RegularFileProperty
37+
38+
@TaskAction
39+
fun run() {
40+
val previous = ModuleVersion.fromStringOrNull(previousVersionString.get()) ?: return
41+
val current = ModuleVersion.fromStringOrNull(currentVersionString.get()) ?: return
42+
43+
val bump =
44+
when {
45+
previous.major != current.major -> VersionDelta.MAJOR
46+
previous.minor != current.minor -> VersionDelta.MINOR
47+
else -> VersionDelta.PATCH
48+
}
49+
val stream = ByteArrayOutputStream()
50+
project.runMetalavaWithArgs(
51+
listOf(
52+
"--source-files",
53+
apiTxtFile.get().asFile.absolutePath,
54+
"--check-compatibility:api:released",
55+
otherApiFile.get().asFile.absolutePath,
56+
) +
57+
MAJOR.flatMap { m -> listOf("--error", m) } +
58+
MINOR.flatMap { m -> listOf("--error", m) } +
59+
IGNORED.flatMap { m -> listOf("--hide", m) } +
60+
listOf("--format=v3", "--no-color"),
61+
ignoreFailure = true,
62+
stdOut = stream,
63+
)
64+
65+
val string = String(stream.toByteArray())
66+
val reg = Regex("(.*)\\s+error:\\s+(.*\\s+\\[(.*)\\])")
67+
val minorChanges = mutableListOf<String>()
68+
val majorChanges = mutableListOf<String>()
69+
for (match in reg.findAll(string)) {
70+
val loc = match.groups[1]!!.value
71+
val message = match.groups[2]!!.value
72+
val type = match.groups[3]!!.value
73+
if (IGNORED.contains(type)) {
74+
continue // Shouldn't be possible
75+
} else if (MINOR.contains(type)) {
76+
minorChanges.add(message)
77+
} else {
78+
majorChanges.add(message)
79+
}
80+
}
81+
val allChanges =
82+
(majorChanges.joinToString(separator = "") { m -> " MAJOR: $m\n" }) +
83+
minorChanges.joinToString(separator = "") { m -> " MINOR: $m\n" }
84+
if (majorChanges.isNotEmpty()) {
85+
if (bump != VersionDelta.MAJOR) {
86+
throw GradleException(
87+
"API has non-bumped breaking MAJOR changes\nCurrent version bump is ${bump}, update the gradle.properties or fix the changes\n$allChanges"
88+
)
89+
}
90+
} else if (minorChanges.isNotEmpty()) {
91+
if (bump != VersionDelta.MAJOR && bump != VersionDelta.MINOR) {
92+
throw GradleException(
93+
"API has non-bumped MINOR changes\nCurrent version bump is ${bump}, update the gradle.properties or fix the changes\n$allChanges"
94+
)
95+
}
96+
}
97+
}
98+
99+
companion object {
100+
private val MAJOR = setOf("AddedFinal")
101+
private val MINOR = setOf("AddedClass", "AddedMethod", "AddedField", "ChangedDeprecated")
102+
private val IGNORED = setOf("ReferencesDeprecated")
103+
}
104+
}

0 commit comments

Comments
 (0)