diff --git a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt
index 298c280d522..86675ce6a56 100644
--- a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt
+++ b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt
@@ -19,7 +19,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import org.jetbrains.compose.resources.readResourceBytes
+import components.resources.demo.generated.resources.Res
@Composable
fun FileRes(paddingValues: PaddingValues) {
@@ -28,7 +28,7 @@ fun FileRes(paddingValues: PaddingValues) {
) {
Text(
modifier = Modifier.padding(16.dp),
- text = "File: 'composeRes/drawable/droid_icon.xml'",
+ text = "File: 'files/icon.xml'",
style = MaterialTheme.typography.titleLarge
)
OutlinedCard(
@@ -38,7 +38,7 @@ fun FileRes(paddingValues: PaddingValues) {
) {
var bytes by remember { mutableStateOf(ByteArray(0)) }
LaunchedEffect(Unit) {
- bytes = readResourceBytes("composeRes/drawable/droid_icon.xml")
+ bytes = Res.readBytes("files/icon.xml")
}
Text(
modifier = Modifier.padding(8.dp).height(200.dp).verticalScroll(rememberScrollState()),
@@ -54,7 +54,7 @@ fun FileRes(paddingValues: PaddingValues) {
mutableStateOf(ByteArray(0))
}
LaunchedEffect(Unit) {
- bytes = readResourceBytes("composeRes/drawable/droid_icon.xml")
+ bytes = Res.readFileBytes("files/icon.xml")
}
Text(bytes.decodeToString())
""".trimIndent()
diff --git a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/StringRes.kt b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/StringRes.kt
index 3bcf9312ac3..4bb1111a1aa 100644
--- a/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/StringRes.kt
+++ b/components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/StringRes.kt
@@ -33,7 +33,7 @@ fun StringRes(paddingValues: PaddingValues) {
) {
Text(
modifier = Modifier.padding(16.dp),
- text = "composeRes/values/strings.xml",
+ text = "values/strings.xml",
style = MaterialTheme.typography.titleLarge
)
OutlinedCard(
@@ -43,7 +43,7 @@ fun StringRes(paddingValues: PaddingValues) {
) {
var bytes by remember { mutableStateOf(ByteArray(0)) }
LaunchedEffect(Unit) {
- bytes = readResourceBytes("composeRes/values/strings.xml")
+ bytes = Res.readBytes("values/strings.xml")
}
Text(
modifier = Modifier.padding(8.dp),
diff --git a/components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/compose.png b/components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/compose.png
similarity index 100%
rename from components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/compose.png
rename to components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/compose.png
diff --git a/components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/droid_icon.xml b/components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/droid_icon.xml
similarity index 100%
rename from components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/droid_icon.xml
rename to components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/droid_icon.xml
diff --git a/components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/insta_icon.xml b/components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/insta_icon.xml
similarity index 100%
rename from components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/insta_icon.xml
rename to components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/insta_icon.xml
diff --git a/components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/land.webp b/components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/land.webp
similarity index 100%
rename from components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/land.webp
rename to components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/land.webp
diff --git a/components/resources/demo/shared/src/commonMain/resources/composeResources/files/icon.xml b/components/resources/demo/shared/src/commonMain/resources/composeResources/files/icon.xml
new file mode 100644
index 00000000000..1f6bb290603
--- /dev/null
+++ b/components/resources/demo/shared/src/commonMain/resources/composeResources/files/icon.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/resources/demo/shared/src/commonMain/resources/composeRes/font/font_awesome.otf b/components/resources/demo/shared/src/commonMain/resources/composeResources/font/font_awesome.otf
similarity index 100%
rename from components/resources/demo/shared/src/commonMain/resources/composeRes/font/font_awesome.otf
rename to components/resources/demo/shared/src/commonMain/resources/composeResources/font/font_awesome.otf
diff --git a/components/resources/demo/shared/src/commonMain/resources/composeRes/values/strings.xml b/components/resources/demo/shared/src/commonMain/resources/composeResources/values/strings.xml
similarity index 100%
rename from components/resources/demo/shared/src/commonMain/resources/composeRes/values/strings.xml
rename to components/resources/demo/shared/src/commonMain/resources/composeResources/values/strings.xml
diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.kt
index f9fe27a3477..38e9e371f34 100644
--- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.kt
+++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.kt
@@ -5,6 +5,9 @@ import androidx.compose.runtime.Immutable
@RequiresOptIn("This API is experimental and is likely to change in the future.")
annotation class ExperimentalResourceApi
+@RequiresOptIn("This is internal API of the Compose gradle plugin.")
+annotation class InternalResourceApi
+
/**
* Represents a resource with an ID and a set of resource items.
*
diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt
index f4375193141..c8d7f9ac1a8 100644
--- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt
+++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt
@@ -11,7 +11,7 @@ class MissingResourceException(path: String) : Exception("Missing resource with
* @param path The path of the file to read in the resource's directory.
* @return The content of the file as a byte array.
*/
-@ExperimentalResourceApi
+@InternalResourceApi
expect suspend fun readResourceBytes(path: String): ByteArray
internal interface ResourceReader {
@@ -19,7 +19,7 @@ internal interface ResourceReader {
}
internal val DefaultResourceReader: ResourceReader = object : ResourceReader {
- @OptIn(ExperimentalResourceApi::class)
+ @OptIn(InternalResourceApi::class)
override suspend fun read(path: String): ByteArray = readResourceBytes(path)
}
diff --git a/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt b/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt
index 6865a3bad9f..3eabef4cd18 100644
--- a/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt
+++ b/components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt
@@ -6,7 +6,7 @@ import platform.Foundation.NSBundle
import platform.Foundation.NSFileManager
import platform.posix.memcpy
-@ExperimentalResourceApi
+@OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray {
val fileManager = NSFileManager.defaultManager()
// todo: support fallback path at bundle root?
diff --git a/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt b/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt
index 4897a4eb10c..5ae6dce1b20 100644
--- a/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt
+++ b/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt
@@ -8,7 +8,7 @@ import org.khronos.webgl.Int8Array
private fun ArrayBuffer.toByteArray(): ByteArray =
Int8Array(this, 0, byteLength).unsafeCast()
-@ExperimentalResourceApi
+@OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await()
diff --git a/components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt b/components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt
index c817770744e..3360bcaf472 100644
--- a/components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt
+++ b/components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt
@@ -2,7 +2,7 @@ package org.jetbrains.compose.resources
private object JvmResourceReader
-@ExperimentalResourceApi
+@OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray {
val classLoader = Thread.currentThread().contextClassLoader ?: JvmResourceReader.javaClass.classLoader
val resource = classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path)
diff --git a/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt
index d87d05a1610..23f2255aaaf 100644
--- a/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt
+++ b/components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt
@@ -5,7 +5,7 @@ import kotlinx.cinterop.usePinned
import platform.Foundation.NSFileManager
import platform.posix.memcpy
-@ExperimentalResourceApi
+@OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray {
val currentDirectoryPath = NSFileManager.defaultManager().currentDirectoryPath
val contentsAtPath = NSFileManager.defaultManager().run {
diff --git a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt
index 72513cc9834..2f6577cc418 100644
--- a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt
+++ b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt
@@ -14,7 +14,7 @@ import kotlin.wasm.unsafe.withScopedMemoryAllocator
* @param path The path of the file to read in the resource's directory.
* @return The content of the file as a byte array.
*/
-@ExperimentalResourceApi
+@OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await()
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt
index 862c87d3e4b..7eb84654447 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt
@@ -15,7 +15,7 @@ import kotlin.io.path.relativeTo
abstract class GenerateResClassTask : DefaultTask() {
@get:Input
abstract val packageName: Property
-
+
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val resDir: DirectoryProperty
@@ -34,14 +34,20 @@ abstract class GenerateResClassTask : DefaultTask() {
logger.info("Generate resources for $rootResDir")
//get first level dirs
- val dirs = rootResDir.listFiles { f -> f.isDirectory }.orEmpty()
+ val dirs = rootResDir.listFiles().orEmpty()
+
+ dirs.forEach { f ->
+ if (!f.isDirectory) {
+ error("${f.name} is not directory! Raw files should be placed in '${rootResDir.name}/files' directory.")
+ }
+ }
//type -> id -> resource item
val resources: Map>> = dirs
.flatMap { dir ->
- dir.listFiles { f -> !f.isDirectory }
+ dir.listFiles()
.orEmpty()
- .mapNotNull { it.fileToResourceItems(rootResDir.parentFile.toPath()) }
+ .mapNotNull { it.fileToResourceItems(rootResDir.toPath()) }
.flatten()
}
.groupBy { it.type }
@@ -61,7 +67,6 @@ abstract class GenerateResClassTask : DefaultTask() {
relativeTo: Path
): List? {
val file = this
- if (file.isDirectory) return null
val dirName = file.parentFile.name ?: return null
val typeAndQualifiers = dirName.split("-")
if (typeAndQualifiers.isEmpty()) return null
@@ -70,20 +75,25 @@ abstract class GenerateResClassTask : DefaultTask() {
val qualifiers = typeAndQualifiers.takeLast(typeAndQualifiers.size - 1)
val path = file.toPath().relativeTo(relativeTo)
- return if (typeString == "values" && file.name.equals("strings.xml", true)) {
+
+ if (typeString == "string") {
+ error("Forbidden directory name '$dirName'! String resources should be declared in 'values/strings.xml'.")
+ }
+
+ if (typeString == "files") {
+ if (qualifiers.isNotEmpty()) error("The 'files' directory doesn't support qualifiers: '$dirName'.")
+ return null
+ }
+
+ if (typeString == "values" && file.name.equals("strings.xml", true)) {
val stringIds = getStringIds(file)
- stringIds.map { strId ->
+ return stringIds.map { strId ->
ResourceItem(ResourceType.STRING, qualifiers, strId.asUnderscoredIdentifier(), path)
}
- } else {
- val type = try {
- ResourceType.fromString(typeString)
- } catch (e: Exception) {
- logger.warn("w: Skip file: $path\n${e.message}")
- return null
- }
- listOf(ResourceItem(type, qualifiers, file.nameWithoutExtension.asUnderscoredIdentifier(), path))
}
+
+ val type = ResourceType.fromString(typeString)
+ return listOf(ResourceItem(type, qualifiers, file.nameWithoutExtension.asUnderscoredIdentifier(), path))
}
private val stringTypeNames = listOf("string", "string-array")
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt
index 5f8a75bc077..53233550950 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt
@@ -6,72 +6,75 @@ import org.jetbrains.compose.ComposeExtension
import org.jetbrains.compose.ComposePlugin
import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
+import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import java.io.File
-private const val COMPOSE_RESOURCES_DIR = "composeRes"
+internal const val COMPOSE_RESOURCES_DIR = "composeResources"
private const val RES_GEN_DIR = "generated/compose/resourceGenerator"
internal fun Project.configureResourceGenerator() {
- val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java)
- val commonSourceSet = kotlinExtension.sourceSets.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) ?: return
- val commonResourcesDir = provider { commonSourceSet.resources.sourceDirectories.first() }
+ pluginManager.withPlugin(KOTLIN_MPP_PLUGIN_ID) {
+ val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java)
+ val commonSourceSet = kotlinExtension.sourceSets.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) ?: return@withPlugin
+ val commonResourcesDir = provider { commonSourceSet.resources.sourceDirectories.first() }
- val packageName = provider {
- buildString {
- val group = project.group.toString().asUnderscoredIdentifier()
- append(group)
- if (group.isNotEmpty()) append(".")
- append("generated.resources")
+ val packageName = provider {
+ buildString {
+ val group = project.group.toString().asUnderscoredIdentifier()
+ append(group)
+ if (group.isNotEmpty()) append(".")
+ append("generated.resources")
+ }
}
- }
- fun buildDir(path: String) = layout.dir(layout.buildDirectory.map { File(it.asFile, path) })
+ fun buildDir(path: String) = layout.dir(layout.buildDirectory.map { File(it.asFile, path) })
- val resDir = layout.dir(commonResourcesDir.map { it.resolve(COMPOSE_RESOURCES_DIR) })
+ val resDir = layout.dir(commonResourcesDir.map { it.resolve(COMPOSE_RESOURCES_DIR) })
- //lazy check a dependency on the Resources library
- val shouldGenerateResourceAccessors: Provider = provider {
- if (ComposeProperties.alwaysGenerateResourceAccessors(providers).get()) {
- true
- } else {
- configurations
- .getByName(commonSourceSet.implementationConfigurationName)
- .allDependencies.any { dep ->
- val depStringNotation = dep.let { "${it.group}:${it.name}:${it.version}" }
- depStringNotation == ComposePlugin.CommonComponentsDependencies.resources
- }
+ //lazy check a dependency on the Resources library
+ val shouldGenerateResourceAccessors: Provider = provider {
+ if (ComposeProperties.alwaysGenerateResourceAccessors(providers).get()) {
+ true
+ } else {
+ configurations
+ .getByName(commonSourceSet.implementationConfigurationName)
+ .allDependencies.any { dep ->
+ val depStringNotation = dep.let { "${it.group}:${it.name}:${it.version}" }
+ depStringNotation == ComposePlugin.CommonComponentsDependencies.resources
+ }
+ }
}
- }
- val genTask = tasks.register(
- "generateComposeResClass",
- GenerateResClassTask::class.java
- ) {
- it.packageName.set(packageName)
- it.resDir.set(resDir)
- it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin"))
- it.onlyIf { shouldGenerateResourceAccessors.get() }
- }
+ val genTask = tasks.register(
+ "generateComposeResClass",
+ GenerateResClassTask::class.java
+ ) {
+ it.packageName.set(packageName)
+ it.resDir.set(resDir)
+ it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin"))
+ it.onlyIf { shouldGenerateResourceAccessors.get() }
+ }
- //register generated source set
- commonSourceSet.kotlin.srcDir(genTask.map { it.codeDir })
+ //register generated source set
+ commonSourceSet.kotlin.srcDir(genTask.map { it.codeDir })
- //setup task execution during IDE import
- tasks.configureEach {
- if (it.name == "prepareKotlinIdeaImport") {
- it.dependsOn(genTask)
+ //setup task execution during IDE import
+ tasks.configureEach {
+ if (it.name == "prepareKotlinIdeaImport") {
+ it.dependsOn(genTask)
+ }
}
- }
- val androidExtension = project.extensions.findByName("android")
- if (androidExtension != null) {
- configureAndroidResources(
- commonResourcesDir,
- buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile },
- shouldGenerateResourceAccessors
- )
+ val androidExtension = project.extensions.findByName("android")
+ if (androidExtension != null) {
+ configureAndroidResources(
+ commonResourcesDir,
+ buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile },
+ shouldGenerateResourceAccessors
+ )
+ }
}
}
diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt
index d168d05484f..8d3768ae391 100644
--- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt
+++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt
@@ -104,6 +104,31 @@ internal fun getResFileSpec(
): FileSpec = FileSpec.builder(packageName, "Res").apply {
addType(TypeSpec.objectBuilder("Res").apply {
addModifiers(KModifier.INTERNAL)
+
+ //readFileBytes
+ val readResourceBytes = MemberName("org.jetbrains.compose.resources", "readResourceBytes")
+ addFunction(
+ FunSpec.builder("readBytes")
+ .addAnnotation(
+ AnnotationSpec.builder(ClassName("kotlin", "OptIn"))
+ .addMember("org.jetbrains.compose.resources.InternalResourceApi::class")
+ .build()
+ )
+ .addKdoc("""
+ Reads the content of the resource file at the specified path and returns it as a byte array.
+
+ Example: `val bytes = Res.readBytes("files/key.bin")`
+
+ @param path The path of the file to read in the compose resource's directory.
+ @return The content of the file as a byte array.
+ """.trimIndent())
+ .addParameter("path", String::class)
+ .addModifiers(KModifier.SUSPEND)
+ .returns(ByteArray::class)
+ .addStatement("return %M(\"$COMPOSE_RESOURCES_DIR/\" + path)", readResourceBytes) //todo: add module ID here
+ .build()
+ )
+
val types = resources.map { (type, idToResources) ->
getResourceTypeObject(type, idToResources)
}.sortedBy { it.name }
@@ -138,7 +163,7 @@ private fun TypeSpec.Builder.addResourceProperty(name: String, items: List