1
1
package org.jetbrains.compose.resources
2
2
3
- import org.gradle.api.DefaultTask
4
3
import org.gradle.api.Project
5
- import org.gradle.api.file.*
4
+ import org.gradle.api.file.DirectoryProperty
5
+ import org.gradle.api.file.FileSystemOperations
6
+ import org.gradle.api.file.FileTree
6
7
import org.gradle.api.provider.Property
7
8
import org.gradle.api.provider.Provider
8
- import org.gradle.api.tasks.*
9
+ import org.gradle.api.tasks.IgnoreEmptyDirectories
10
+ import org.gradle.api.tasks.Input
11
+ import org.gradle.api.tasks.InputFiles
12
+ import org.gradle.api.tasks.Internal
13
+ import org.gradle.api.tasks.OutputDirectory
14
+ import org.gradle.api.tasks.OutputFiles
15
+ import org.gradle.api.tasks.SkipWhenEmpty
16
+ import org.gradle.api.tasks.TaskProvider
9
17
import org.jetbrains.compose.internal.utils.uppercaseFirstChar
10
18
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
11
19
import org.w3c.dom.Node
20
+ import org.xml.sax.SAXParseException
12
21
import java.io.File
13
22
import java.util.*
14
23
import javax.inject.Inject
@@ -60,7 +69,7 @@ internal fun Project.getPreparedComposeResourcesDir(sourceSet: KotlinSourceSet):
60
69
private fun getPrepareComposeResourcesTaskName (sourceSet : KotlinSourceSet ) =
61
70
" prepareComposeResourcesTaskFor${sourceSet.name.uppercaseFirstChar()} "
62
71
63
- internal abstract class CopyNonXmlValueResourcesTask : DefaultTask () {
72
+ internal abstract class CopyNonXmlValueResourcesTask : IdeaImportTask () {
64
73
@get:Inject
65
74
abstract val fileSystem: FileSystemOperations
66
75
@@ -82,8 +91,7 @@ internal abstract class CopyNonXmlValueResourcesTask : DefaultTask() {
82
91
dir.asFileTree.matching { it.exclude(" values*/*.${XmlValuesConverterTask .CONVERTED_RESOURCE_EXT } " ) }
83
92
}
84
93
85
- @TaskAction
86
- fun run () {
94
+ override fun safeAction () {
87
95
realOutputFiles.get().forEach { f -> f.delete() }
88
96
fileSystem.copy { copy ->
89
97
copy.includeEmptyDirs = false
@@ -95,7 +103,7 @@ internal abstract class CopyNonXmlValueResourcesTask : DefaultTask() {
95
103
}
96
104
}
97
105
98
- internal abstract class PrepareComposeResourcesTask : DefaultTask () {
106
+ internal abstract class PrepareComposeResourcesTask : IdeaImportTask () {
99
107
@get:InputFiles
100
108
@get:SkipWhenEmpty
101
109
@get:IgnoreEmptyDirectories
@@ -109,8 +117,7 @@ internal abstract class PrepareComposeResourcesTask : DefaultTask() {
109
117
@get:OutputDirectory
110
118
abstract val outputDir: DirectoryProperty
111
119
112
- @TaskAction
113
- fun run () = Unit
120
+ override fun safeAction () = Unit
114
121
}
115
122
116
123
internal data class ValueResourceRecord (
@@ -135,7 +142,7 @@ internal data class ValueResourceRecord(
135
142
}
136
143
}
137
144
138
- internal abstract class XmlValuesConverterTask : DefaultTask () {
145
+ internal abstract class XmlValuesConverterTask : IdeaImportTask () {
139
146
companion object {
140
147
const val CONVERTED_RESOURCE_EXT = " cvr" // Compose Value Resource
141
148
private const val FORMAT_VERSION = 0
@@ -163,8 +170,7 @@ internal abstract class XmlValuesConverterTask : DefaultTask() {
163
170
dir.asFileTree.matching { it.include(" values*/*.$suffix .$CONVERTED_RESOURCE_EXT " ) }
164
171
}
165
172
166
- @TaskAction
167
- fun run () {
173
+ override fun safeAction () {
168
174
val outDir = outputDir.get().asFile
169
175
val suffix = fileSuffix.get()
170
176
realOutputFiles.get().forEach { f -> f.delete() }
@@ -176,7 +182,13 @@ internal abstract class XmlValuesConverterTask : DefaultTask() {
176
182
.resolve(f.parentFile.name)
177
183
.resolve(f.nameWithoutExtension + " .$suffix .$CONVERTED_RESOURCE_EXT " )
178
184
output.parentFile.mkdirs()
179
- convert(f, output)
185
+ try {
186
+ convert(f, output)
187
+ } catch (e: SAXParseException ) {
188
+ error(" XML file ${f.absolutePath} is not valid. Check the file content." )
189
+ } catch (e: Exception ) {
190
+ error(" XML file ${f.absolutePath} is not valid. ${e.message} " )
191
+ }
180
192
}
181
193
}
182
194
}
@@ -186,17 +198,28 @@ internal abstract class XmlValuesConverterTask : DefaultTask() {
186
198
private fun convert (original : File , converted : File ) {
187
199
val doc = DocumentBuilderFactory .newInstance().newDocumentBuilder().parse(original)
188
200
val items = doc.getElementsByTagName(" resources" ).item(0 ).childNodes
189
- val records = List (items.length) { items.item(it) }.mapNotNull { getItemRecord(it)?.getAsString() }
201
+ val records = List (items.length) { items.item(it) }
202
+ .filter { it.hasAttributes() }
203
+ .map { getItemRecord(it) }
204
+
205
+ // check there are no duplicates type + key
206
+ records.groupBy { it.key }
207
+ .filter { it.value.size > 1 }
208
+ .forEach { (key, records) ->
209
+ val allTypes = records.map { it.type }
210
+ require(allTypes.size == allTypes.toSet().size) { " Duplicated key '$key '." }
211
+ }
212
+
190
213
val fileContent = buildString {
191
214
appendLine(" version:$FORMAT_VERSION " )
192
- records.sorted().forEach { appendLine(it) }
215
+ records.map { it.getAsString() }. sorted().forEach { appendLine(it) }
193
216
}
194
217
converted.writeText(fileContent)
195
218
}
196
219
197
- private fun getItemRecord (node : Node ): ValueResourceRecord ? {
198
- val type = ResourceType .fromString(node.nodeName) ? : return null
199
- val key = node.attributes.getNamedItem(" name" ).nodeValue
220
+ private fun getItemRecord (node : Node ): ValueResourceRecord {
221
+ val type = ResourceType .fromString(node.nodeName) ? : error( " Unknown resource type: ' ${node.nodeName} '. " )
222
+ val key = node.attributes.getNamedItem(" name" )? .nodeValue ? : error( " Attribute 'name' not found. " )
200
223
val value: String
201
224
when (type) {
202
225
ResourceType .STRING -> {
@@ -225,7 +248,7 @@ internal abstract class XmlValuesConverterTask : DefaultTask() {
225
248
}
226
249
}
227
250
228
- else -> error(" Unknown string resource type: '$type '" )
251
+ else -> error(" Unknown string resource type: '$type '. " )
229
252
}
230
253
return ValueResourceRecord (type, key, value)
231
254
}
0 commit comments