Browse Source

Introduce a 'composeResources/files' directory for any files. (#4079)

Introduce a 'composeResources/files' directory for any files.
pull/4096/head v1.6.0-dev1357
Konstantin 11 months ago committed by GitHub
parent
commit
8955e66eac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt
  2. 4
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/StringRes.kt
  3. 0
      components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/compose.png
  4. 0
      components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/droid_icon.xml
  5. 0
      components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/insta_icon.xml
  6. 0
      components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/land.webp
  7. 34
      components/resources/demo/shared/src/commonMain/resources/composeResources/files/icon.xml
  8. 0
      components/resources/demo/shared/src/commonMain/resources/composeResources/font/font_awesome.otf
  9. 0
      components/resources/demo/shared/src/commonMain/resources/composeResources/values/strings.xml
  10. 3
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.kt
  11. 4
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceReader.kt
  12. 2
      components/resources/library/src/iosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.ios.kt
  13. 2
      components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.js.kt
  14. 2
      components/resources/library/src/jvmAndAndroidMain/kotlin/org/jetbrains/compose/resources/ResourceReader.jvmAndAndroid.kt
  15. 2
      components/resources/library/src/macosMain/kotlin/org/jetbrains/compose/resources/ResourceReader.macos.kt
  16. 2
      components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt
  17. 40
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt
  18. 99
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt
  19. 27
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt
  20. 74
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt
  21. 40
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt
  22. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/ignored_1.txt
  23. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable-au-rUS/vector.xml
  24. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable-dark-ge/vector.xml
  25. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable-en/vector.xml
  26. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable/3-strange-name.xml
  27. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable/vector.xml
  28. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable/vector_2.xml
  29. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/files/file.txt
  30. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/font/emptyFont.otf
  31. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/values/strings.xml
  32. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/ignored_2.txt

8
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.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.readResourceBytes import components.resources.demo.generated.resources.Res
@Composable @Composable
fun FileRes(paddingValues: PaddingValues) { fun FileRes(paddingValues: PaddingValues) {
@ -28,7 +28,7 @@ fun FileRes(paddingValues: PaddingValues) {
) { ) {
Text( Text(
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
text = "File: 'composeRes/drawable/droid_icon.xml'", text = "File: 'files/icon.xml'",
style = MaterialTheme.typography.titleLarge style = MaterialTheme.typography.titleLarge
) )
OutlinedCard( OutlinedCard(
@ -38,7 +38,7 @@ fun FileRes(paddingValues: PaddingValues) {
) { ) {
var bytes by remember { mutableStateOf(ByteArray(0)) } var bytes by remember { mutableStateOf(ByteArray(0)) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
bytes = readResourceBytes("composeRes/drawable/droid_icon.xml") bytes = Res.readBytes("files/icon.xml")
} }
Text( Text(
modifier = Modifier.padding(8.dp).height(200.dp).verticalScroll(rememberScrollState()), modifier = Modifier.padding(8.dp).height(200.dp).verticalScroll(rememberScrollState()),
@ -54,7 +54,7 @@ fun FileRes(paddingValues: PaddingValues) {
mutableStateOf(ByteArray(0)) mutableStateOf(ByteArray(0))
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
bytes = readResourceBytes("composeRes/drawable/droid_icon.xml") bytes = Res.readFileBytes("files/icon.xml")
} }
Text(bytes.decodeToString()) Text(bytes.decodeToString())
""".trimIndent() """.trimIndent()

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

@ -33,7 +33,7 @@ fun StringRes(paddingValues: PaddingValues) {
) { ) {
Text( Text(
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
text = "composeRes/values/strings.xml", text = "values/strings.xml",
style = MaterialTheme.typography.titleLarge style = MaterialTheme.typography.titleLarge
) )
OutlinedCard( OutlinedCard(
@ -43,7 +43,7 @@ fun StringRes(paddingValues: PaddingValues) {
) { ) {
var bytes by remember { mutableStateOf(ByteArray(0)) } var bytes by remember { mutableStateOf(ByteArray(0)) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
bytes = readResourceBytes("composeRes/values/strings.xml") bytes = Res.readBytes("values/strings.xml")
} }
Text( Text(
modifier = Modifier.padding(8.dp), modifier = Modifier.padding(8.dp),

0
components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/compose.png → components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/compose.png

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

0
components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/droid_icon.xml → components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/droid_icon.xml

0
components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/insta_icon.xml → components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/insta_icon.xml

0
components/resources/demo/shared/src/commonMain/resources/composeRes/drawable/land.webp → components/resources/demo/shared/src/commonMain/resources/composeResources/drawable/land.webp

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

34
components/resources/demo/shared/src/commonMain/resources/composeResources/files/icon.xml

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
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"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
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"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

0
components/resources/demo/shared/src/commonMain/resources/composeRes/font/font_awesome.otf → components/resources/demo/shared/src/commonMain/resources/composeResources/font/font_awesome.otf

0
components/resources/demo/shared/src/commonMain/resources/composeRes/values/strings.xml → components/resources/demo/shared/src/commonMain/resources/composeResources/values/strings.xml

3
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.") @RequiresOptIn("This API is experimental and is likely to change in the future.")
annotation class ExperimentalResourceApi 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. * Represents a resource with an ID and a set of resource items.
* *

4
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. * @param path The path of the file to read in the resource's directory.
* @return The content of the file as a byte array. * @return The content of the file as a byte array.
*/ */
@ExperimentalResourceApi @InternalResourceApi
expect suspend fun readResourceBytes(path: String): ByteArray expect suspend fun readResourceBytes(path: String): ByteArray
internal interface ResourceReader { internal interface ResourceReader {
@ -19,7 +19,7 @@ internal interface ResourceReader {
} }
internal val DefaultResourceReader: ResourceReader = object : ResourceReader { internal val DefaultResourceReader: ResourceReader = object : ResourceReader {
@OptIn(ExperimentalResourceApi::class) @OptIn(InternalResourceApi::class)
override suspend fun read(path: String): ByteArray = readResourceBytes(path) override suspend fun read(path: String): ByteArray = readResourceBytes(path)
} }

2
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.Foundation.NSFileManager
import platform.posix.memcpy import platform.posix.memcpy
@ExperimentalResourceApi @OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray { actual suspend fun readResourceBytes(path: String): ByteArray {
val fileManager = NSFileManager.defaultManager() val fileManager = NSFileManager.defaultManager()
// todo: support fallback path at bundle root? // todo: support fallback path at bundle root?

2
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 = private fun ArrayBuffer.toByteArray(): ByteArray =
Int8Array(this, 0, byteLength).unsafeCast<ByteArray>() Int8Array(this, 0, byteLength).unsafeCast<ByteArray>()
@ExperimentalResourceApi @OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray { actual suspend fun readResourceBytes(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path) val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await() val response = window.fetch(resPath).await()

2
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 private object JvmResourceReader
@ExperimentalResourceApi @OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray { actual suspend fun readResourceBytes(path: String): ByteArray {
val classLoader = Thread.currentThread().contextClassLoader ?: JvmResourceReader.javaClass.classLoader val classLoader = Thread.currentThread().contextClassLoader ?: JvmResourceReader.javaClass.classLoader
val resource = classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path) val resource = classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path)

2
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.Foundation.NSFileManager
import platform.posix.memcpy import platform.posix.memcpy
@ExperimentalResourceApi @OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray { actual suspend fun readResourceBytes(path: String): ByteArray {
val currentDirectoryPath = NSFileManager.defaultManager().currentDirectoryPath val currentDirectoryPath = NSFileManager.defaultManager().currentDirectoryPath
val contentsAtPath = NSFileManager.defaultManager().run { val contentsAtPath = NSFileManager.defaultManager().run {

2
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. * @param path The path of the file to read in the resource's directory.
* @return The content of the file as a byte array. * @return The content of the file as a byte array.
*/ */
@ExperimentalResourceApi @OptIn(ExperimentalResourceApi::class)
actual suspend fun readResourceBytes(path: String): ByteArray { actual suspend fun readResourceBytes(path: String): ByteArray {
val resPath = WebResourcesConfiguration.getResourcePath(path) val resPath = WebResourcesConfiguration.getResourcePath(path)
val response = window.fetch(resPath).await<Response>() val response = window.fetch(resPath).await<Response>()

40
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() { abstract class GenerateResClassTask : DefaultTask() {
@get:Input @get:Input
abstract val packageName: Property<String> abstract val packageName: Property<String>
@get:InputDirectory @get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE) @get:PathSensitive(PathSensitivity.RELATIVE)
abstract val resDir: DirectoryProperty abstract val resDir: DirectoryProperty
@ -34,14 +34,20 @@ abstract class GenerateResClassTask : DefaultTask() {
logger.info("Generate resources for $rootResDir") logger.info("Generate resources for $rootResDir")
//get first level dirs //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 //type -> id -> resource item
val resources: Map<ResourceType, Map<String, List<ResourceItem>>> = dirs val resources: Map<ResourceType, Map<String, List<ResourceItem>>> = dirs
.flatMap { dir -> .flatMap { dir ->
dir.listFiles { f -> !f.isDirectory } dir.listFiles()
.orEmpty() .orEmpty()
.mapNotNull { it.fileToResourceItems(rootResDir.parentFile.toPath()) } .mapNotNull { it.fileToResourceItems(rootResDir.toPath()) }
.flatten() .flatten()
} }
.groupBy { it.type } .groupBy { it.type }
@ -61,7 +67,6 @@ abstract class GenerateResClassTask : DefaultTask() {
relativeTo: Path relativeTo: Path
): List<ResourceItem>? { ): List<ResourceItem>? {
val file = this val file = this
if (file.isDirectory) return null
val dirName = file.parentFile.name ?: return null val dirName = file.parentFile.name ?: return null
val typeAndQualifiers = dirName.split("-") val typeAndQualifiers = dirName.split("-")
if (typeAndQualifiers.isEmpty()) return null if (typeAndQualifiers.isEmpty()) return null
@ -70,20 +75,25 @@ abstract class GenerateResClassTask : DefaultTask() {
val qualifiers = typeAndQualifiers.takeLast(typeAndQualifiers.size - 1) val qualifiers = typeAndQualifiers.takeLast(typeAndQualifiers.size - 1)
val path = file.toPath().relativeTo(relativeTo) 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) val stringIds = getStringIds(file)
stringIds.map { strId -> return stringIds.map { strId ->
ResourceItem(ResourceType.STRING, qualifiers, strId.asUnderscoredIdentifier(), path) 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") private val stringTypeNames = listOf("string", "string-array")

99
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.ComposePlugin
import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.compose.desktop.application.internal.ComposeProperties 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.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import java.io.File 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" private const val RES_GEN_DIR = "generated/compose/resourceGenerator"
internal fun Project.configureResourceGenerator() { internal fun Project.configureResourceGenerator() {
val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java) pluginManager.withPlugin(KOTLIN_MPP_PLUGIN_ID) {
val commonSourceSet = kotlinExtension.sourceSets.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) ?: return val kotlinExtension = project.extensions.getByType(KotlinProjectExtension::class.java)
val commonResourcesDir = provider { commonSourceSet.resources.sourceDirectories.first() } val commonSourceSet = kotlinExtension.sourceSets.findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) ?: return@withPlugin
val commonResourcesDir = provider { commonSourceSet.resources.sourceDirectories.first() }
val packageName = provider { val packageName = provider {
buildString { buildString {
val group = project.group.toString().asUnderscoredIdentifier() val group = project.group.toString().asUnderscoredIdentifier()
append(group) append(group)
if (group.isNotEmpty()) append(".") if (group.isNotEmpty()) append(".")
append("generated.resources") 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 //lazy check a dependency on the Resources library
val shouldGenerateResourceAccessors: Provider<Boolean> = provider { val shouldGenerateResourceAccessors: Provider<Boolean> = provider {
if (ComposeProperties.alwaysGenerateResourceAccessors(providers).get()) { if (ComposeProperties.alwaysGenerateResourceAccessors(providers).get()) {
true true
} else { } else {
configurations configurations
.getByName(commonSourceSet.implementationConfigurationName) .getByName(commonSourceSet.implementationConfigurationName)
.allDependencies.any { dep -> .allDependencies.any { dep ->
val depStringNotation = dep.let { "${it.group}:${it.name}:${it.version}" } val depStringNotation = dep.let { "${it.group}:${it.name}:${it.version}" }
depStringNotation == ComposePlugin.CommonComponentsDependencies.resources depStringNotation == ComposePlugin.CommonComponentsDependencies.resources
} }
}
} }
}
val genTask = tasks.register( val genTask = tasks.register(
"generateComposeResClass", "generateComposeResClass",
GenerateResClassTask::class.java GenerateResClassTask::class.java
) { ) {
it.packageName.set(packageName) it.packageName.set(packageName)
it.resDir.set(resDir) it.resDir.set(resDir)
it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin")) it.codeDir.set(buildDir("$RES_GEN_DIR/kotlin"))
it.onlyIf { shouldGenerateResourceAccessors.get() } it.onlyIf { shouldGenerateResourceAccessors.get() }
} }
//register generated source set //register generated source set
commonSourceSet.kotlin.srcDir(genTask.map { it.codeDir }) commonSourceSet.kotlin.srcDir(genTask.map { it.codeDir })
//setup task execution during IDE import //setup task execution during IDE import
tasks.configureEach { tasks.configureEach {
if (it.name == "prepareKotlinIdeaImport") { if (it.name == "prepareKotlinIdeaImport") {
it.dependsOn(genTask) it.dependsOn(genTask)
}
} }
}
val androidExtension = project.extensions.findByName("android") val androidExtension = project.extensions.findByName("android")
if (androidExtension != null) { if (androidExtension != null) {
configureAndroidResources( configureAndroidResources(
commonResourcesDir, commonResourcesDir,
buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile }, buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile },
shouldGenerateResourceAccessors shouldGenerateResourceAccessors
) )
}
} }
} }

27
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 { ): FileSpec = FileSpec.builder(packageName, "Res").apply {
addType(TypeSpec.objectBuilder("Res").apply { addType(TypeSpec.objectBuilder("Res").apply {
addModifiers(KModifier.INTERNAL) 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) -> val types = resources.map { (type, idToResources) ->
getResourceTypeObject(type, idToResources) getResourceTypeObject(type, idToResources)
}.sortedBy { it.name } }.sortedBy { it.name }
@ -138,7 +163,7 @@ private fun TypeSpec.Builder.addResourceProperty(name: String, items: List<Resou
add("%T(\n", resourceItemClass).withIndent { add("%T(\n", resourceItemClass).withIndent {
add("setOf(").addQualifiers(item).add("),\n") add("setOf(").addQualifiers(item).add("),\n")
//file separator should be '/' on all platforms //file separator should be '/' on all platforms
add("\"${item.path.invariantSeparatorsPathString}\"\n") add("\"$COMPOSE_RESOURCES_DIR/${item.path.invariantSeparatorsPathString}\"\n") //todo: add module ID here
} }
add("),\n") add("),\n")
} }

74
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt

@ -16,16 +16,12 @@ class ResourcesTest : GradlePluginTestBase() {
file("build/generated/compose/resourceGenerator/kotlin/app/group/generated/resources/Res.kt"), file("build/generated/compose/resourceGenerator/kotlin/app/group/generated/resources/Res.kt"),
file("expected/Res.kt") file("expected/Res.kt")
) )
check.logContains("""
Unknown resource type: 'ignored'.
""".trimIndent())
} }
file("src/commonMain/resources/composeRes/drawable/vector_2.xml").renameTo(
file("src/commonMain/resources/composeRes/drawable/vector_3.xml")
)
//check resource's accessors were regenerated //check resource's accessors were regenerated
file("src/commonMain/resources/composeResources/drawable/vector_2.xml").renameTo(
file("src/commonMain/resources/composeResources/drawable/vector_3.xml")
)
gradle("generateComposeResClass").checks { gradle("generateComposeResClass").checks {
assertNotEqualTextFiles( assertNotEqualTextFiles(
file("build/generated/compose/resourceGenerator/kotlin/app/group/generated/resources/Res.kt"), file("build/generated/compose/resourceGenerator/kotlin/app/group/generated/resources/Res.kt"),
@ -33,52 +29,84 @@ class ResourcesTest : GradlePluginTestBase() {
) )
} }
file("src/commonMain/resources/composeRes/drawable-en").renameTo( file("src/commonMain/resources/composeResources/drawable-en").renameTo(
file("src/commonMain/resources/composeRes/drawable-ren") file("src/commonMain/resources/composeResources/drawable-ren")
) )
gradle("generateComposeResClass").checks { gradle("generateComposeResClass").checks {
check.logContains(""" check.logContains("""
contains unknown qualifier: 'ren'. contains unknown qualifier: 'ren'.
""".trimIndent()) """.trimIndent())
} }
file("src/commonMain/resources/composeRes/drawable-ren").renameTo( file("src/commonMain/resources/composeResources/drawable-ren").renameTo(
file("src/commonMain/resources/composeRes/drawable-rUS-en") file("src/commonMain/resources/composeResources/drawable-rUS-en")
) )
gradle("generateComposeResClass").checks { gradle("generateComposeResClass").checks {
check.logContains(""" check.logContains("""
Region qualifier must be declared after language: 'en-rUS'. Region qualifier must be declared after language: 'en-rUS'.
""".trimIndent()) """.trimIndent())
} }
file("src/commonMain/resources/composeRes/drawable-rUS-en").renameTo( file("src/commonMain/resources/composeResources/drawable-rUS-en").renameTo(
file("src/commonMain/resources/composeRes/drawable-rUS") file("src/commonMain/resources/composeResources/drawable-rUS")
) )
gradle("generateComposeResClass").checks { gradle("generateComposeResClass").checks {
check.logContains(""" check.logContains("""
Region qualifier must be used only with language. Region qualifier must be used only with language.
""".trimIndent()) """.trimIndent())
} }
file("src/commonMain/resources/composeRes/drawable-rUS").renameTo( file("src/commonMain/resources/composeResources/drawable-rUS").renameTo(
file("src/commonMain/resources/composeRes/drawable-en-fr") file("src/commonMain/resources/composeResources/drawable-en-fr")
) )
gradle("generateComposeResClass").checks { gradle("generateComposeResClass").checks {
check.logContains(""" check.logContains("""
contains repetitive qualifiers: 'en' and 'fr'. contains repetitive qualifiers: 'en' and 'fr'.
""".trimIndent()) """.trimIndent())
} }
file("src/commonMain/resources/composeRes/drawable-en-fr").renameTo( file("src/commonMain/resources/composeResources/drawable-en-fr").renameTo(
file("src/commonMain/resources/composeRes/drawable-en") file("src/commonMain/resources/composeResources/image")
)
gradle("generateComposeResClass").checks {
check.logContains("""
Unknown resource type: 'image'
""".trimIndent())
}
file("src/commonMain/resources/composeResources/image").renameTo(
file("src/commonMain/resources/composeResources/files-de")
)
gradle("generateComposeResClass").checks {
check.logContains("""
The 'files' directory doesn't support qualifiers: 'files-de'.
""".trimIndent())
}
file("src/commonMain/resources/composeResources/files-de").renameTo(
file("src/commonMain/resources/composeResources/strings")
)
gradle("generateComposeResClass").checks {
check.logContains("""
Unknown resource type: 'strings'.
""".trimIndent())
}
file("src/commonMain/resources/composeResources/strings").renameTo(
file("src/commonMain/resources/composeResources/string-us")
) )
gradle("generateComposeResClass").checks {
check.logContains("""
Forbidden directory name 'string-us'! String resources should be declared in 'values/strings.xml'.
""".trimIndent())
}
file("src/commonMain/resources/composeRes/drawable/vector_3.xml").renameTo( //restore defaults
file("src/commonMain/resources/composeRes/drawable/vector_2.xml") file("src/commonMain/resources/composeResources/string-us").renameTo(
file("src/commonMain/resources/composeResources/drawable-en")
)
file("src/commonMain/resources/composeResources/drawable/vector_3.xml").renameTo(
file("src/commonMain/resources/composeResources/drawable/vector_2.xml")
) )
//TODO: check a real build after a release a new version of the resources library //TODO: check a real build after a release a new version of the resources library

40
gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt

@ -1,5 +1,8 @@
package app.group.generated.resources package app.group.generated.resources
import kotlin.ByteArray
import kotlin.OptIn
import kotlin.String
import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.FontResource import org.jetbrains.compose.resources.FontResource
import org.jetbrains.compose.resources.LanguageQualifier import org.jetbrains.compose.resources.LanguageQualifier
@ -7,15 +10,28 @@ import org.jetbrains.compose.resources.RegionQualifier
import org.jetbrains.compose.resources.ResourceItem import org.jetbrains.compose.resources.ResourceItem
import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.ThemeQualifier import org.jetbrains.compose.resources.ThemeQualifier
import org.jetbrains.compose.resources.readResourceBytes
internal object Res { internal object Res {
/**
* 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.
*/
@OptIn(org.jetbrains.compose.resources.InternalResourceApi::class)
public suspend fun readBytes(path: String): ByteArray = readResourceBytes("composeResources/" +
path)
public object drawable { public object drawable {
public val _3_strange_name: DrawableResource = DrawableResource( public val _3_strange_name: DrawableResource = DrawableResource(
"drawable:_3_strange_name", "drawable:_3_strange_name",
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/drawable/3-strange-name.xml" "composeResources/drawable/3-strange-name.xml"
), ),
) )
) )
@ -25,19 +41,19 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(LanguageQualifier("au"), RegionQualifier("US"), ), setOf(LanguageQualifier("au"), RegionQualifier("US"), ),
"composeRes/drawable-au-rUS/vector.xml" "composeResources/drawable-au-rUS/vector.xml"
), ),
ResourceItem( ResourceItem(
setOf(ThemeQualifier.DARK, LanguageQualifier("ge"), ), setOf(ThemeQualifier.DARK, LanguageQualifier("ge"), ),
"composeRes/drawable-dark-ge/vector.xml" "composeResources/drawable-dark-ge/vector.xml"
), ),
ResourceItem( ResourceItem(
setOf(LanguageQualifier("en"), ), setOf(LanguageQualifier("en"), ),
"composeRes/drawable-en/vector.xml" "composeResources/drawable-en/vector.xml"
), ),
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/drawable/vector.xml" "composeResources/drawable/vector.xml"
), ),
) )
) )
@ -47,7 +63,7 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/drawable/vector_2.xml" "composeResources/drawable/vector_2.xml"
), ),
) )
) )
@ -59,7 +75,7 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/font/emptyFont.otf" "composeResources/font/emptyFont.otf"
), ),
) )
) )
@ -72,7 +88,7 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/values/strings.xml" "composeResources/values/strings.xml"
), ),
) )
) )
@ -83,7 +99,7 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/values/strings.xml" "composeResources/values/strings.xml"
), ),
) )
) )
@ -94,7 +110,7 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/values/strings.xml" "composeResources/values/strings.xml"
), ),
) )
) )
@ -105,7 +121,7 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/values/strings.xml" "composeResources/values/strings.xml"
), ),
) )
) )
@ -116,7 +132,7 @@ internal object Res {
setOf( setOf(
ResourceItem( ResourceItem(
setOf(), setOf(),
"composeRes/values/strings.xml" "composeResources/values/strings.xml"
), ),
) )
) )

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/ignored_1.txt

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/drawable-au-rUS/vector.xml → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable-au-rUS/vector.xml

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/drawable-dark-ge/vector.xml → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable-dark-ge/vector.xml

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/drawable-en/vector.xml → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable-en/vector.xml

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/drawable/3-strange-name.xml → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable/3-strange-name.xml

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/drawable/vector.xml → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable/vector.xml

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/drawable/vector_2.xml → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/drawable/vector_2.xml

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/ignored/ignored_3.txt → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/files/file.txt

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/font/emptyFont.otf → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/font/emptyFont.otf

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/values/strings.xml → gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeResources/values/strings.xml

0
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/ignored_2.txt

Loading…
Cancel
Save