Skip to content

Commit 8955e66

Browse files
authored
Introduce a 'composeResources/files' directory for any files. (JetBrains#4079)
Introduce a 'composeResources/files' directory for any files.
1 parent 86cfe35 commit 8955e66

File tree

32 files changed

+231
-112
lines changed

32 files changed

+231
-112
lines changed

components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import androidx.compose.runtime.remember
1919
import androidx.compose.runtime.setValue
2020
import androidx.compose.ui.Modifier
2121
import androidx.compose.ui.unit.dp
22-
import org.jetbrains.compose.resources.readResourceBytes
22+
import components.resources.demo.generated.resources.Res
2323

2424
@Composable
2525
fun FileRes(paddingValues: PaddingValues) {
@@ -28,7 +28,7 @@ fun FileRes(paddingValues: PaddingValues) {
2828
) {
2929
Text(
3030
modifier = Modifier.padding(16.dp),
31-
text = "File: 'composeRes/drawable/droid_icon.xml'",
31+
text = "File: 'files/icon.xml'",
3232
style = MaterialTheme.typography.titleLarge
3333
)
3434
OutlinedCard(
@@ -38,7 +38,7 @@ fun FileRes(paddingValues: PaddingValues) {
3838
) {
3939
var bytes by remember { mutableStateOf(ByteArray(0)) }
4040
LaunchedEffect(Unit) {
41-
bytes = readResourceBytes("composeRes/drawable/droid_icon.xml")
41+
bytes = Res.readBytes("files/icon.xml")
4242
}
4343
Text(
4444
modifier = Modifier.padding(8.dp).height(200.dp).verticalScroll(rememberScrollState()),
@@ -54,7 +54,7 @@ fun FileRes(paddingValues: PaddingValues) {
5454
mutableStateOf(ByteArray(0))
5555
}
5656
LaunchedEffect(Unit) {
57-
bytes = readResourceBytes("composeRes/drawable/droid_icon.xml")
57+
bytes = Res.readFileBytes("files/icon.xml")
5858
}
5959
Text(bytes.decodeToString())
6060
""".trimIndent()

components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/StringRes.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fun StringRes(paddingValues: PaddingValues) {
3333
) {
3434
Text(
3535
modifier = Modifier.padding(16.dp),
36-
text = "composeRes/values/strings.xml",
36+
text = "values/strings.xml",
3737
style = MaterialTheme.typography.titleLarge
3838
)
3939
OutlinedCard(
@@ -43,7 +43,7 @@ fun StringRes(paddingValues: PaddingValues) {
4343
) {
4444
var bytes by remember { mutableStateOf(ByteArray(0)) }
4545
LaunchedEffect(Unit) {
46-
bytes = readResourceBytes("composeRes/values/strings.xml")
46+
bytes = Res.readBytes("values/strings.xml")
4747
}
4848
Text(
4949
modifier = Modifier.padding(8.dp),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:aapt="http://schemas.android.com/aapt"
3+
android:width="108dp"
4+
android:height="108dp"
5+
android:viewportWidth="108"
6+
android:viewportHeight="108">
7+
<path
8+
android:fillType="evenOdd"
9+
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
10+
android:strokeWidth="1"
11+
android:strokeColor="#00000000">
12+
<aapt:attr name="android:fillColor">
13+
<gradient
14+
android:endX="78.5885"
15+
android:endY="90.9159"
16+
android:startX="48.7653"
17+
android:startY="61.0927"
18+
android:type="linear">
19+
<item
20+
android:color="#44000000"
21+
android:offset="0.0" />
22+
<item
23+
android:color="#00000000"
24+
android:offset="1.0" />
25+
</gradient>
26+
</aapt:attr>
27+
</path>
28+
<path
29+
android:fillColor="#FFFFFF"
30+
android:fillType="nonZero"
31+
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
32+
android:strokeWidth="1"
33+
android:strokeColor="#00000000" />
34+
</vector>

components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.kt

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import androidx.compose.runtime.Immutable
55
@RequiresOptIn("This API is experimental and is likely to change in the future.")
66
annotation class ExperimentalResourceApi
77

8+
@RequiresOptIn("This is internal API of the Compose gradle plugin.")
9+
annotation class InternalResourceApi
10+
811
/**
912
* Represents a resource with an ID and a set of resource items.
1013
*

components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ class MissingResourceException(path: String) : Exception("Missing resource with
1111
* @param path The path of the file to read in the resource's directory.
1212
* @return The content of the file as a byte array.
1313
*/
14-
@ExperimentalResourceApi
14+
@InternalResourceApi
1515
expect suspend fun readResourceBytes(path: String): ByteArray
1616

1717
internal interface ResourceReader {
1818
suspend fun read(path: String): ByteArray
1919
}
2020

2121
internal val DefaultResourceReader: ResourceReader = object : ResourceReader {
22-
@OptIn(ExperimentalResourceApi::class)
22+
@OptIn(InternalResourceApi::class)
2323
override suspend fun read(path: String): ByteArray = readResourceBytes(path)
2424
}
2525

components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import platform.Foundation.NSBundle
66
import platform.Foundation.NSFileManager
77
import platform.posix.memcpy
88

9-
@ExperimentalResourceApi
9+
@OptIn(ExperimentalResourceApi::class)
1010
actual suspend fun readResourceBytes(path: String): ByteArray {
1111
val fileManager = NSFileManager.defaultManager()
1212
// todo: support fallback path at bundle root?

components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import org.khronos.webgl.Int8Array
88
private fun ArrayBuffer.toByteArray(): ByteArray =
99
Int8Array(this, 0, byteLength).unsafeCast<ByteArray>()
1010

11-
@ExperimentalResourceApi
11+
@OptIn(ExperimentalResourceApi::class)
1212
actual suspend fun readResourceBytes(path: String): ByteArray {
1313
val resPath = WebResourcesConfiguration.getResourcePath(path)
1414
val response = window.fetch(resPath).await()

components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package org.jetbrains.compose.resources
22

33
private object JvmResourceReader
44

5-
@ExperimentalResourceApi
5+
@OptIn(ExperimentalResourceApi::class)
66
actual suspend fun readResourceBytes(path: String): ByteArray {
77
val classLoader = Thread.currentThread().contextClassLoader ?: JvmResourceReader.javaClass.classLoader
88
val resource = classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path)

components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import kotlinx.cinterop.usePinned
55
import platform.Foundation.NSFileManager
66
import platform.posix.memcpy
77

8-
@ExperimentalResourceApi
8+
@OptIn(ExperimentalResourceApi::class)
99
actual suspend fun readResourceBytes(path: String): ByteArray {
1010
val currentDirectoryPath = NSFileManager.defaultManager().currentDirectoryPath
1111
val contentsAtPath = NSFileManager.defaultManager().run {

components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import kotlin.wasm.unsafe.withScopedMemoryAllocator
1414
* @param path The path of the file to read in the resource's directory.
1515
* @return The content of the file as a byte array.
1616
*/
17-
@ExperimentalResourceApi
17+
@OptIn(ExperimentalResourceApi::class)
1818
actual suspend fun readResourceBytes(path: String): ByteArray {
1919
val resPath = WebResourcesConfiguration.getResourcePath(path)
2020
val response = window.fetch(resPath).await<Response>()

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt

+25-15
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import kotlin.io.path.relativeTo
1515
abstract class GenerateResClassTask : DefaultTask() {
1616
@get:Input
1717
abstract val packageName: Property<String>
18-
18+
1919
@get:InputDirectory
2020
@get:PathSensitive(PathSensitivity.RELATIVE)
2121
abstract val resDir: DirectoryProperty
@@ -34,14 +34,20 @@ abstract class GenerateResClassTask : DefaultTask() {
3434
logger.info("Generate resources for $rootResDir")
3535

3636
//get first level dirs
37-
val dirs = rootResDir.listFiles { f -> f.isDirectory }.orEmpty()
37+
val dirs = rootResDir.listFiles().orEmpty()
38+
39+
dirs.forEach { f ->
40+
if (!f.isDirectory) {
41+
error("${f.name} is not directory! Raw files should be placed in '${rootResDir.name}/files' directory.")
42+
}
43+
}
3844

3945
//type -> id -> resource item
4046
val resources: Map<ResourceType, Map<String, List<ResourceItem>>> = dirs
4147
.flatMap { dir ->
42-
dir.listFiles { f -> !f.isDirectory }
48+
dir.listFiles()
4349
.orEmpty()
44-
.mapNotNull { it.fileToResourceItems(rootResDir.parentFile.toPath()) }
50+
.mapNotNull { it.fileToResourceItems(rootResDir.toPath()) }
4551
.flatten()
4652
}
4753
.groupBy { it.type }
@@ -61,7 +67,6 @@ abstract class GenerateResClassTask : DefaultTask() {
6167
relativeTo: Path
6268
): List<ResourceItem>? {
6369
val file = this
64-
if (file.isDirectory) return null
6570
val dirName = file.parentFile.name ?: return null
6671
val typeAndQualifiers = dirName.split("-")
6772
if (typeAndQualifiers.isEmpty()) return null
@@ -70,20 +75,25 @@ abstract class GenerateResClassTask : DefaultTask() {
7075
val qualifiers = typeAndQualifiers.takeLast(typeAndQualifiers.size - 1)
7176
val path = file.toPath().relativeTo(relativeTo)
7277

73-
return if (typeString == "values" && file.name.equals("strings.xml", true)) {
78+
79+
if (typeString == "string") {
80+
error("Forbidden directory name '$dirName'! String resources should be declared in 'values/strings.xml'.")
81+
}
82+
83+
if (typeString == "files") {
84+
if (qualifiers.isNotEmpty()) error("The 'files' directory doesn't support qualifiers: '$dirName'.")
85+
return null
86+
}
87+
88+
if (typeString == "values" && file.name.equals("strings.xml", true)) {
7489
val stringIds = getStringIds(file)
75-
stringIds.map { strId ->
90+
return stringIds.map { strId ->
7691
ResourceItem(ResourceType.STRING, qualifiers, strId.asUnderscoredIdentifier(), path)
7792
}
78-
} else {
79-
val type = try {
80-
ResourceType.fromString(typeString)
81-
} catch (e: Exception) {
82-
logger.warn("w: Skip file: $path\n${e.message}")
83-
return null
84-
}
85-
listOf(ResourceItem(type, qualifiers, file.nameWithoutExtension.asUnderscoredIdentifier(), path))
8693
}
94+
95+
val type = ResourceType.fromString(typeString)
96+
return listOf(ResourceItem(type, qualifiers, file.nameWithoutExtension.asUnderscoredIdentifier(), path))
8797
}
8898

8999
private val stringTypeNames = listOf("string", "string-array")

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt

+51-48
Original file line numberDiff line numberDiff line change
@@ -6,72 +6,75 @@ import org.jetbrains.compose.ComposeExtension
66
import org.jetbrains.compose.ComposePlugin
77
import org.jetbrains.compose.ExperimentalComposeLibrary
88
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
9+
import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID
910
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
1011
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
1112
import java.io.File
1213

13-
private const val COMPOSE_RESOURCES_DIR = "composeRes"
14+
internal const val COMPOSE_RESOURCES_DIR = "composeResources"
1415
private const val RES_GEN_DIR = "generated/compose/resourceGenerator"
1516

1617
internal fun Project.configureResourceGenerator() {
17-
val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java)
18-
val commonSourceSet = kotlinExtension.sourceSets.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) ?: return
19-
val commonResourcesDir = provider { commonSourceSet.resources.sourceDirectories.first() }
18+
pluginManager.withPlugin(KOTLIN_MPP_PLUGIN_ID) {
19+
val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java)
20+
val commonSourceSet = kotlinExtension.sourceSets.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) ?: return@withPlugin
21+
val commonResourcesDir = provider { commonSourceSet.resources.sourceDirectories.first() }
2022

21-
val packageName = provider {
22-
buildString {
23-
val group = project.group.toString().asUnderscoredIdentifier()
24-
append(group)
25-
if (group.isNotEmpty()) append(".")
26-
append("generated.resources")
23+
val packageName = provider {
24+
buildString {
25+
val group = project.group.toString().asUnderscoredIdentifier()
26+
append(group)
27+
if (group.isNotEmpty()) append(".")
28+
append("generated.resources")
29+
}
2730
}
28-
}
2931

30-
fun buildDir(path: String) = layout.dir(layout.buildDirectory.map { File(it.asFile, path) })
32+
fun buildDir(path: String) = layout.dir(layout.buildDirectory.map { File(it.asFile, path) })
3133

32-
val resDir = layout.dir(commonResourcesDir.map { it.resolve(COMPOSE_RESOURCES_DIR) })
34+
val resDir = layout.dir(commonResourcesDir.map { it.resolve(COMPOSE_RESOURCES_DIR) })
3335

34-
//lazy check a dependency on the Resources library
35-
val shouldGenerateResourceAccessors: Provider<Boolean> = provider {
36-
if (ComposeProperties.alwaysGenerateResourceAccessors(providers).get()) {
37-
true
38-
} else {
39-
configurations
40-
.getByName(commonSourceSet.implementationConfigurationName)
41-
.allDependencies.any { dep ->
42-
val depStringNotation = dep.let { "${it.group}:${it.name}:${it.version}" }
43-
depStringNotation == ComposePlugin.CommonComponentsDependencies.resources
44-
}
36+
//lazy check a dependency on the Resources library
37+
val shouldGenerateResourceAccessors: Provider<Boolean> = provider {
38+
if (ComposeProperties.alwaysGenerateResourceAccessors(providers).get()) {
39+
true
40+
} else {
41+
configurations
42+
.getByName(commonSourceSet.implementationConfigurationName)
43+
.allDependencies.any { dep ->
44+
val depStringNotation = dep.let { "${it.group}:${it.name}:${it.version}" }
45+
depStringNotation == ComposePlugin.CommonComponentsDependencies.resources
46+
}
47+
}
4548
}
46-
}
4749

48-
val genTask = tasks.register(
49-
"generateComposeResClass",
50-
GenerateResClassTask::class.java
51-
) {
52-
it.packageName.set(packageName)
53-
it.resDir.set(resDir)
54-
it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin"))
55-
it.onlyIf { shouldGenerateResourceAccessors.get() }
56-
}
50+
val genTask = tasks.register(
51+
"generateComposeResClass",
52+
GenerateResClassTask::class.java
53+
) {
54+
it.packageName.set(packageName)
55+
it.resDir.set(resDir)
56+
it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin"))
57+
it.onlyIf { shouldGenerateResourceAccessors.get() }
58+
}
5759

58-
//register generated source set
59-
commonSourceSet.kotlin.srcDir(genTask.map { it.codeDir })
60+
//register generated source set
61+
commonSourceSet.kotlin.srcDir(genTask.map { it.codeDir })
6062

61-
//setup task execution during IDE import
62-
tasks.configureEach {
63-
if (it.name == "prepareKotlinIdeaImport") {
64-
it.dependsOn(genTask)
63+
//setup task execution during IDE import
64+
tasks.configureEach {
65+
if (it.name == "prepareKotlinIdeaImport") {
66+
it.dependsOn(genTask)
67+
}
6568
}
66-
}
6769

68-
val androidExtension = project.extensions.findByName("android")
69-
if (androidExtension != null) {
70-
configureAndroidResources(
71-
commonResourcesDir,
72-
buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile },
73-
shouldGenerateResourceAccessors
74-
)
70+
val androidExtension = project.extensions.findByName("android")
71+
if (androidExtension != null) {
72+
configureAndroidResources(
73+
commonResourcesDir,
74+
buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile },
75+
shouldGenerateResourceAccessors
76+
)
77+
}
7578
}
7679
}
7780

0 commit comments

Comments
 (0)