Skip to content

Commit 67b9f5c

Browse files
authored
fix(build): VersioningPlugin failing on clean build (#10546)
2 parents 2498b82 + 240c386 commit 67b9f5c

File tree

2 files changed

+60
-66
lines changed

2 files changed

+60
-66
lines changed

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/PrintVersionInfoTask.kt

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
package net.thunderbird.gradle.plugin.app.versioning
22

33
import org.gradle.api.DefaultTask
4+
import org.gradle.api.file.ConfigurableFileCollection
45
import org.gradle.api.file.RegularFileProperty
56
import org.gradle.api.provider.Property
67
import org.gradle.api.tasks.Input
8+
import org.gradle.api.tasks.InputFile
79
import org.gradle.api.tasks.InputFiles
810
import org.gradle.api.tasks.Optional
911
import org.gradle.api.tasks.OutputFile
10-
import org.gradle.api.tasks.PathSensitive
11-
import org.gradle.api.tasks.PathSensitivity
1212
import org.gradle.api.tasks.TaskAction
13+
import java.io.File
14+
import javax.xml.parsers.DocumentBuilderFactory
15+
import javax.xml.xpath.XPathConstants
16+
import javax.xml.xpath.XPathFactory
1317

1418
abstract class PrintVersionInfoTask : DefaultTask() {
1519
@get:Input
1620
abstract val applicationId: Property<String>
1721

18-
@get:Input
19-
abstract val applicationLabel: Property<String>
22+
@get:InputFile
23+
abstract val mergedManifest: RegularFileProperty
24+
25+
@get:InputFiles
26+
abstract val resourceFiles: ConfigurableFileCollection
2027

2128
@get:Input
2229
abstract val versionCode: Property<Int>
@@ -31,20 +38,16 @@ abstract class PrintVersionInfoTask : DefaultTask() {
3138
@get:Optional
3239
abstract val outputFile: RegularFileProperty
3340

34-
@get:InputFiles
35-
@get:Optional
36-
@get:PathSensitive(PathSensitivity.RELATIVE)
37-
abstract val stringsXmlFile: RegularFileProperty
38-
3941
init {
4042
outputs.upToDateWhen { false } // This forces Gradle to always re-run the task
4143
}
4244

4345
@TaskAction
4446
fun printVersionInfo() {
47+
val label = getApplicationLabel()
4548
val output = """
4649
APPLICATION_ID=${applicationId.get()}
47-
APPLICATION_LABEL=${applicationLabel.get()}
50+
APPLICATION_LABEL=$label
4851
VERSION_CODE=${versionCode.get()}
4952
VERSION_NAME=${versionName.get()}
5053
VERSION_NAME_SUFFIX=${versionNameSuffix.get()}
@@ -57,4 +60,49 @@ abstract class PrintVersionInfoTask : DefaultTask() {
5760
outputFile.get().asFile.writeText(output + "\n")
5861
}
5962
}
63+
64+
private fun getApplicationLabel(): String {
65+
val manifestFile = mergedManifest.get().asFile
66+
if (!manifestFile.exists()) return "Unknown"
67+
68+
val labelRaw = readManifestApplicationLabel(manifestFile) ?: return "Unknown"
69+
70+
// Return raw label if not a resource string
71+
val match = STRING_RESOURCE_REGEX.matchEntire(labelRaw.trim()) ?: return labelRaw
72+
val resourceName = match.groupValues[1]
73+
74+
val resolvedApplicationLabel = resourceFiles
75+
.map { it }
76+
.mapNotNull { dir -> File(dir, "values/strings.xml").takeIf { it.exists() } }
77+
.firstNotNullOfOrNull { stringResourceFile -> readStringResource(stringResourceFile, resourceName) }
78+
79+
return resolvedApplicationLabel ?: "Unknown"
80+
}
81+
82+
private fun readManifestApplicationLabel(manifest: File): String? {
83+
val document = DocumentBuilderFactory.newInstance()
84+
.apply { isNamespaceAware = true }
85+
.newDocumentBuilder()
86+
.parse(manifest)
87+
88+
val apps = document.getElementsByTagName("application")
89+
if (apps.length == 0) return null
90+
91+
val appElement = apps.item(0)
92+
return appElement.attributes?.getNamedItemNS("http://schemas.android.com/apk/res/android", "label")?.nodeValue
93+
?: appElement.attributes?.getNamedItem("android:label")?.nodeValue
94+
?: appElement.attributes?.getNamedItem("label")?.nodeValue
95+
}
96+
97+
private fun readStringResource(stringResourceFile: File, resourceName: String): String? {
98+
val xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stringResourceFile)
99+
val xPath = XPathFactory.newInstance().newXPath()
100+
val expression = "/resources/string[@name='$resourceName']/text()"
101+
val value = xPath.evaluate(expression, xmlDocument, XPathConstants.STRING) as String
102+
return value.trim().takeIf { it.isNotEmpty() }
103+
}
104+
105+
private companion object {
106+
val STRING_RESOURCE_REGEX = "^@string/([A-Za-z0-9_]+)$".toRegex()
107+
}
60108
}

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/VersioningPlugin.kt

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import com.android.build.api.artifact.SingleArtifact
44
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
55
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
66
import com.android.build.api.variant.ApplicationVariant
7-
import com.android.build.api.variant.Variant
87
import java.io.File
9-
import javax.xml.parsers.DocumentBuilderFactory
10-
import javax.xml.xpath.XPathConstants
11-
import javax.xml.xpath.XPathFactory
128
import org.gradle.api.Plugin
139
import org.gradle.api.Project
1410
import org.gradle.api.provider.Provider
@@ -37,7 +33,8 @@ class VersioningPlugin : Plugin<Project> {
3733
val versionInfoProvider = getVersionInfo(variant)
3834

3935
applicationId = variant.applicationId
40-
applicationLabel = getApplicationLabel(variant)
36+
mergedManifest = variant.artifacts.get(SingleArtifact.MERGED_MANIFEST)
37+
resourceFiles.from(variant.sources.res?.all)
4138
versionCode = versionInfoProvider.map { it.versionCode }
4239
versionName = versionInfoProvider.map { it.versionName }
4340
versionNameSuffix = versionInfoProvider.map { it.versionNameSuffix }
@@ -73,60 +70,9 @@ class VersioningPlugin : Plugin<Project> {
7370
}
7471
}
7572

76-
private fun Project.getApplicationLabel(variant: Variant): Provider<String> {
77-
val mergedManifest = variant.artifacts.get(SingleArtifact.MERGED_MANIFEST)
78-
79-
return providers.zip(mergedManifest, provider { variant }) { mergedManifest, _ ->
80-
val labelRaw = readManifestApplicationLabel(mergedManifest.asFile) ?: return@zip "Unknown"
81-
82-
// Return raw label if not a resource string
83-
val match = STRING_RESOURCE_REGEX.matchEntire(labelRaw.trim()) ?: return@zip labelRaw
84-
val resourceName = match.groupValues[1]
85-
86-
val resourceDirs = variant.sources.res?.all?.get()?.filter { it.isNotEmpty() }?.flatten() ?: emptyList()
87-
88-
val resolvedApplicationLabel = resourceDirs
89-
.map { it.asFile }
90-
.mapNotNull { dir -> File(dir, "values/strings.xml").takeIf { it.exists() } }
91-
.firstNotNullOfOrNull { stringResourceFile -> readStringResource(stringResourceFile, resourceName) }
92-
93-
resolvedApplicationLabel ?: "Unknown"
94-
}
95-
}
96-
97-
private fun readManifestApplicationLabel(manifest: File): String? {
98-
val document = DocumentBuilderFactory.newInstance()
99-
.apply { isNamespaceAware = true }
100-
.newDocumentBuilder()
101-
.parse(manifest)
102-
103-
val apps = document.getElementsByTagName("application")
104-
if (apps.length == 0) return null
105-
106-
val appElement = apps.item(0)
107-
return appElement.attributes?.getNamedItemNS("http://schemas.android.com/apk/res/android", "label")?.nodeValue
108-
?: appElement.attributes?.getNamedItem("android:label")?.nodeValue
109-
?: appElement.attributes?.getNamedItem("label")?.nodeValue
110-
}
111-
112-
/**
113-
* Parses stringResourceFile to extract `<string name="resourceName">...</string>`
114-
*/
115-
private fun readStringResource(stringResourceFile: File, resourceName: String): String? {
116-
val xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stringResourceFile)
117-
val xPath = XPathFactory.newInstance().newXPath()
118-
val expression = "/resources/string[@name='$resourceName']/text()"
119-
val value = xPath.evaluate(expression, xmlDocument, XPathConstants.STRING) as String
120-
return value.trim().takeIf { it.isNotEmpty() }
121-
}
122-
12373
private fun String.capitalized() = replaceFirstChar {
12474
if (it.isLowerCase()) it.titlecase() else it.toString()
12575
}
126-
127-
private companion object {
128-
val STRING_RESOURCE_REGEX = "^@string/([A-Za-z0-9_]+)$".toRegex()
129-
}
13076
}
13177

13278

0 commit comments

Comments
 (0)