Browse Source

Resources gradle plugin (#3961)

dima.avdeev/cocoapods-dynamic-frameworks
Konstantin 5 months ago committed by GitHub
parent
commit
181bfd1612
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/gradle-plugin.yml
  2. 1
      components/gradle.properties
  3. 5
      components/resources/demo/shared/build.gradle.kts
  4. 6
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FileRes.kt
  5. 5
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/FontRes.kt
  6. 33
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/ImagesRes.kt
  7. 25
      components/resources/demo/shared/src/commonMain/kotlin/org/jetbrains/compose/resources/demo/shared/StringRes.kt
  8. 0
      components/resources/demo/shared/src/commonMain/resources/composeRes/fonts/font_awesome.otf
  9. 0
      components/resources/demo/shared/src/commonMain/resources/composeRes/images/compose.png
  10. 0
      components/resources/demo/shared/src/commonMain/resources/composeRes/images/droid_icon.xml
  11. 0
      components/resources/demo/shared/src/commonMain/resources/composeRes/images/insta_icon.xml
  12. 0
      components/resources/demo/shared/src/commonMain/resources/composeRes/images/land.webp
  13. 0
      components/resources/demo/shared/src/commonMain/resources/composeRes/values/strings.xml
  14. BIN
      components/resources/demo/shared/src/commonMain/resources/font_awesome.otf
  15. 38
      components/resources/library/src/androidInstrumentedTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.android.kt
  16. 5
      components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt
  17. 29
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/FontResources.kt
  18. 79
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt
  19. 46
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/Resource.kt
  20. 14
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ResourceIndex.kt
  21. 128
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt
  22. 12
      components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt
  23. 8
      components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt
  24. 38
      components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.desktop.kt
  25. 9
      components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt
  26. 3
      gradle-plugins/build.gradle.kts
  27. 11
      gradle-plugins/compose/build.gradle.kts
  28. 5
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt
  29. 8
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ComposeProjectProperties.kt
  30. 18
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/utils/IosGradleProperties.kt
  31. 42
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidTargetConfiguration.kt
  32. 98
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/GenerateResClassTask.kt
  33. 77
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesGenerator.kt
  34. 95
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ResourcesSpec.kt
  35. 2
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/IosTargetResources.kt
  36. 5
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/SyncComposeResourcesForIosTask.kt
  37. 13
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/configureSyncIosResources.kt
  38. 2
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/determineIosKonanTargetsFromEnv.kt
  39. 46
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt
  40. 2
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt
  41. 2
      gradle-plugins/compose/src/test/test-projects/application/mpp/settings.gradle
  42. 39
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts
  43. 78
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/expected/Res.kt
  44. 1
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/gradle.properties
  45. 22
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/settings.gradle.kts
  46. 20
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/kotlin/App.kt
  47. BIN
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/fonts/emptyFont.otf
  48. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/ignored/ignored_3.txt
  49. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/ignored_1.txt
  50. 36
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images-q1-q2/vector.xml
  51. 36
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images-q1/vector.xml
  52. 36
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images-q2/vector.xml
  53. 36
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images/vector.xml
  54. 36
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images/vector_2.xml
  55. 13
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/values/strings.xml
  56. 0
      gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/ignored_2.txt
  57. 2
      gradle-plugins/gradle.properties
  58. 11
      gradle-plugins/gradle/libs.versions.toml

2
.github/workflows/gradle-plugin.yml

@ -10,7 +10,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-12, windows-2022]
gradle: [7.3.3, 8.3]
gradle: [7.4, 8.3]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3

1
components/gradle.properties

@ -15,6 +15,7 @@ agp.version=8.1.2
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
compose.resources.always.generate.accessors=true
compose.desktop.verbose=true
compose.useMavenLocal=false

5
components/resources/demo/shared/build.gradle.kts

@ -78,11 +78,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
sourceSets {
named("main") {
resources.srcDir("src/commonMain/resources")
}
}
}
compose.experimental {

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

@ -28,7 +28,7 @@ fun FileRes(paddingValues: PaddingValues) {
) {
Text(
modifier = Modifier.padding(16.dp),
text = "File: 'images/droid_icon.xml'",
text = "File: 'composeRes/images/droid_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("images/droid_icon.xml")
bytes = readResourceBytes("composeRes/images/droid_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 = readBytes("images/droid_icon.xml")
bytes = readBytes("composeRes/images/droid_icon.xml")
}
Text(bytes.decodeToString())
""".trimIndent()

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

@ -13,6 +13,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import components.resources.demo.generated.resources.Res
import org.jetbrains.compose.resources.Font
@Composable
@ -28,7 +29,7 @@ fun FontRes(paddingValues: PaddingValues) {
Text(
modifier = Modifier.padding(8.dp),
text = """
val fontAwesome = FontFamily(Font("font_awesome.otf"))
val fontAwesome = FontFamily(Font(Res.fonts.font_awesome))
val symbols = arrayOf(0xf1ba, 0xf238, 0xf21a, 0xf1bb, 0xf1b8, 0xf09b, 0xf269, 0xf1d0, 0xf15a, 0xf293, 0xf1c6)
Text(
modifier = Modifier.padding(16.dp),
@ -42,7 +43,7 @@ fun FontRes(paddingValues: PaddingValues) {
)
}
val fontAwesome = FontFamily(Font("font_awesome.otf"))
val fontAwesome = FontFamily(Font(Res.fonts.font_awesome))
val symbols = arrayOf(0xf1ba, 0xf238, 0xf21a, 0xf1bb, 0xf1b8, 0xf09b, 0xf269, 0xf1d0, 0xf15a, 0xf293, 0xf1c6)
Text(
modifier = Modifier.padding(16.dp),

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

@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import components.resources.demo.generated.resources.Res
import org.jetbrains.compose.resources.imageResource
import org.jetbrains.compose.resources.vectorResource
import org.jetbrains.compose.resources.painterResource
@ -27,13 +28,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Image(
modifier = Modifier.size(100.dp),
painter = painterResource("images/compose.png"),
painter = painterResource(Res.images.compose),
contentDescription = null
)
Text(
"""
Image(
painter = painterResource("images/compose.png")
painter = painterResource(Res.images.compose)
)
""".trimIndent()
)
@ -46,13 +47,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Image(
modifier = Modifier.size(100.dp),
painter = painterResource("images/insta_icon.xml"),
painter = painterResource(Res.images.insta_icon),
contentDescription = null
)
Text(
"""
Image(
painter = painterResource("images/insta_icon.xml")
painter = painterResource(Res.images.insta_icon)
)
""".trimIndent()
)
@ -65,13 +66,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Image(
modifier = Modifier.size(140.dp),
bitmap = imageResource("images/land.webp"),
bitmap = imageResource(Res.images.land),
contentDescription = null
)
Text(
"""
Image(
bitmap = imageResource("images/land.webp")
bitmap = imageResource(Res.images.land)
)
""".trimIndent()
)
@ -84,13 +85,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Image(
modifier = Modifier.size(100.dp),
imageVector = vectorResource("images/droid_icon.xml"),
imageVector = vectorResource(Res.images.droid_icon),
contentDescription = null
)
Text(
"""
Image(
imageVector = vectorResource("images/droid_icon.xml")
imageVector = vectorResource(Res.images.droid_icon)
)
""".trimIndent()
)
@ -103,13 +104,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Icon(
modifier = Modifier.size(100.dp),
painter = painterResource("images/compose.png"),
painter = painterResource(Res.images.compose),
contentDescription = null
)
Text(
"""
Icon(
painter = painterResource("images/compose.png")
painter = painterResource(Res.images.compose)
)
""".trimIndent()
)
@ -122,13 +123,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Icon(
modifier = Modifier.size(100.dp),
painter = painterResource("images/insta_icon.xml"),
painter = painterResource(Res.images.insta_icon),
contentDescription = null
)
Text(
"""
Icon(
painter = painterResource("images/insta_icon.xml")
painter = painterResource(Res.images.insta_icon)
)
""".trimIndent()
)
@ -141,13 +142,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Icon(
modifier = Modifier.size(140.dp),
bitmap = imageResource("images/land.webp"),
bitmap = imageResource(Res.images.land),
contentDescription = null
)
Text(
"""
Icon(
bitmap = imageResource("images/land.webp")
bitmap = imageResource(Res.images.land)
)
""".trimIndent()
)
@ -160,13 +161,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Icon(
modifier = Modifier.size(100.dp),
imageVector = vectorResource("images/droid_icon.xml"),
imageVector = vectorResource(Res.images.droid_icon),
contentDescription = null
)
Text(
"""
Icon(
imageVector = vectorResource("images/droid_icon.xml")
imageVector = vectorResource(Res.images.droid_icon)
)
""".trimIndent()
)

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

@ -21,6 +21,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import components.resources.demo.generated.resources.Res
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.getStringArray
import org.jetbrains.compose.resources.readResourceBytes
@ -32,7 +33,7 @@ fun StringRes(paddingValues: PaddingValues) {
) {
Text(
modifier = Modifier.padding(16.dp),
text = "strings.xml",
text = "composeRes/values/strings.xml",
style = MaterialTheme.typography.titleLarge
)
OutlinedCard(
@ -42,7 +43,7 @@ fun StringRes(paddingValues: PaddingValues) {
) {
var bytes by remember { mutableStateOf(ByteArray(0)) }
LaunchedEffect(Unit) {
bytes = readResourceBytes("strings.xml")
bytes = readResourceBytes("composeRes/values/strings.xml")
}
Text(
modifier = Modifier.padding(8.dp),
@ -53,9 +54,9 @@ fun StringRes(paddingValues: PaddingValues) {
}
OutlinedTextField(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
value = getString("app_name"),
value = getString(Res.strings.app_name),
onValueChange = {},
label = { Text("Text(getString(\"app_name\"))") },
label = { Text("Text(getString(Res.strings.app_name)") },
enabled = false,
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
@ -65,9 +66,9 @@ fun StringRes(paddingValues: PaddingValues) {
)
OutlinedTextField(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
value = getString("hello"),
value = getString(Res.strings.hello),
onValueChange = {},
label = { Text("Text(getString(\"hello\"))") },
label = { Text("Text(getString(Res.strings.hello)") },
enabled = false,
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
@ -77,9 +78,9 @@ fun StringRes(paddingValues: PaddingValues) {
)
OutlinedTextField(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
value = getString("multi_line"),
value = getString(Res.strings.multi_line),
onValueChange = {},
label = { Text("Text(getString(\"multi_line\"))") },
label = { Text("Text(getString(Res.strings.multi_line)") },
enabled = false,
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
@ -89,9 +90,9 @@ fun StringRes(paddingValues: PaddingValues) {
)
OutlinedTextField(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
value = getString("str_template", "User_name", 100),
value = getString(Res.strings.str_template, "User_name", 100),
onValueChange = {},
label = { Text("Text(getString(\"str_template\", \"User_name\", 100))") },
label = { Text("Text(getString(Res.strings.str_template, \"User_name\", 100)") },
enabled = false,
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
@ -101,9 +102,9 @@ fun StringRes(paddingValues: PaddingValues) {
)
OutlinedTextField(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
value = getStringArray("str_arr").toString(),
value = getStringArray(Res.strings.str_arr).toString(),
onValueChange = {},
label = { Text("Text(getStringArray(\"str_arr\").toString())") },
label = { Text("Text(getStringArray(Res.strings.str_arr).toString())") },
enabled = false,
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,

0
components/resources/demo/shared/src/androidMain/assets/font_awesome.otf → components/resources/demo/shared/src/commonMain/resources/composeRes/fonts/font_awesome.otf

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

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

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

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

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

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

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

BIN
components/resources/demo/shared/src/commonMain/resources/font_awesome.otf

Binary file not shown.

38
components/resources/library/src/androidInstrumentedTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.android.kt

@ -25,17 +25,17 @@ class ComposeResourceTest {
@Test
fun testCountRecompositions() = runComposeUiTest {
runBlockingTest {
val imagePathFlow = MutableStateFlow("1.png")
val imagePathFlow = MutableStateFlow(ImageResource("1.png"))
val recompositionsCounter = RecompositionsCounter()
setContent {
val path by imagePathFlow.collectAsState()
val res = imageResource(path)
val res by imagePathFlow.collectAsState()
val imgRes = imageResource(res)
recompositionsCounter.content {
Image(bitmap = res, contentDescription = null)
Image(bitmap = imgRes, contentDescription = null)
}
}
awaitIdle()
imagePathFlow.emit("2.png")
imagePathFlow.emit(ImageResource("2.png"))
awaitIdle()
assertEquals(2, recompositionsCounter.count)
}
@ -45,17 +45,17 @@ class ComposeResourceTest {
fun testImageResourceCache() = runComposeUiTest {
runBlockingTest {
val testResourceReader = TestResourceReader()
val imagePathFlow = MutableStateFlow("1.png")
val imagePathFlow = MutableStateFlow(ImageResource("1.png"))
setContent {
CompositionLocalProvider(LocalResourceReader provides testResourceReader) {
val path by imagePathFlow.collectAsState()
Image(painterResource(path), null)
val res by imagePathFlow.collectAsState()
Image(painterResource(res), null)
}
}
awaitIdle()
imagePathFlow.emit("2.png")
imagePathFlow.emit(ImageResource("2.png"))
awaitIdle()
imagePathFlow.emit("1.png")
imagePathFlow.emit(ImageResource("1.png"))
awaitIdle()
assertEquals(
@ -69,18 +69,18 @@ class ComposeResourceTest {
fun testStringResourceCache() = runComposeUiTest {
runBlockingTest {
val testResourceReader = TestResourceReader()
val stringIdFlow = MutableStateFlow("app_name")
val stringIdFlow = MutableStateFlow(TestStringResource("app_name"))
setContent {
CompositionLocalProvider(LocalResourceReader provides testResourceReader) {
val textId by stringIdFlow.collectAsState()
Text(getString(textId))
Text(getStringArray("str_arr").joinToString())
val res by stringIdFlow.collectAsState()
Text(getString(res))
Text(getStringArray(TestStringResource("str_arr")).joinToString())
}
}
awaitIdle()
stringIdFlow.emit("hello")
stringIdFlow.emit(TestStringResource("hello"))
awaitIdle()
stringIdFlow.emit("app_name")
stringIdFlow.emit(TestStringResource("app_name"))
awaitIdle()
assertEquals(
@ -94,12 +94,12 @@ class ComposeResourceTest {
fun testReadStringResource() = runComposeUiTest {
runBlockingTest {
setContent {
assertEquals("Compose Resources App", getString("app_name"))
assertEquals("Compose Resources App", getString(TestStringResource("app_name")))
assertEquals(
"Hello, test-name! You have 42 new messages.",
getString("str_template", "test-name", 42)
getString(TestStringResource("str_template"), "test-name", 42)
)
assertEquals(listOf("item 1", "item 2", "item 3"), getStringArray("str_arr"))
assertEquals(listOf("item 1", "item 2", "item 3"), getStringArray(TestStringResource("str_arr")))
}
awaitIdle()
}

5
components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/FontResources.android.kt

@ -1,7 +1,6 @@
package org.jetbrains.compose.resources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
@ -9,7 +8,7 @@ import androidx.compose.ui.text.font.FontWeight
@ExperimentalResourceApi
@Composable
actual fun Font(id: ResourceId, weight: FontWeight, style: FontStyle): Font {
val path by rememberState(id, { "" }) { getPathById(id) }
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val path = resource.getPathByEnvironment()
return Font(path, LocalContext.current.assets, weight, style)
}

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

@ -1,14 +1,37 @@
package org.jetbrains.compose.resources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
/**
* Creates a font with the specified resource ID, weight, and style.
* Represents a font resource.
*
* @param id The resource ID of the font.
* @param id The identifier of the font resource.
* @param items The set of resource items associated with the font resource.
*
* @see Resource
*/
@Immutable
class FontResource(id: String, items: Set<ResourceItem>): Resource(id, items)
/**
* Creates an [FontResource] object with the specified path.
*
* @param path The path to the font resource file.
* @return A new [FontResource] object.
*/
fun FontResource(path: String): FontResource = FontResource(
id = "FontResource:$path",
items = setOf(ResourceItem(emptySet(), path))
)
/**
* Creates a font using the specified font resource, weight, and style.
*
* @param resource The font resource to be used.
* @param weight The weight of the font. Default value is [FontWeight.Normal].
* @param style The style of the font. Default value is [FontStyle.Normal].
*
@ -19,7 +42,7 @@ import androidx.compose.ui.text.font.FontWeight
@ExperimentalResourceApi
@Composable
expect fun Font(
id: ResourceId,
resource: FontResource,
weight: FontWeight = FontWeight.Normal,
style: FontStyle = FontStyle.Normal
): Font

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

@ -1,7 +1,9 @@
package org.jetbrains.compose.resources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
@ -9,46 +11,68 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jetbrains.compose.resources.vector.toImageVector
import org.jetbrains.compose.resources.vector.xmldom.Element
/**
* Represents an image resource.
*
* @param id The unique identifier of the image resource.
* @param items The set of resource items associated with the image resource.
*/
@Immutable
class ImageResource(id: String, items: Set<ResourceItem>) : Resource(id, items)
/**
* Creates an [ImageResource] object with the specified path.
*
* @param path The path of the image resource.
* @return An [ImageResource] object.
*/
fun ImageResource(path: String): ImageResource = ImageResource(
id = "ImageResource:$path",
items = setOf(ResourceItem(emptySet(), path))
)
/**
* Retrieves a [Painter] for the given [ResourceId].
* Retrieves a [Painter] using the specified image resource.
* Automatically select a type of the Painter depending on the file extension.
*
* @param id The ID of the resource to retrieve the [Painter] from.
* @param resource The image resource to be used.
* @return The [Painter] loaded from the resource.
*/
@ExperimentalResourceApi
@Composable
fun painterResource(id: ResourceId): Painter {
val filePath by rememberFilePath(id)
fun painterResource(resource: ImageResource): Painter {
val filePath = remember(resource) { resource.getPathByEnvironment() }
val isXml = filePath.endsWith(".xml", true)
if (isXml) {
return rememberVectorPainter(vectorResource(id))
return rememberVectorPainter(vectorResource(resource))
} else {
return BitmapPainter(imageResource(id))
return BitmapPainter(imageResource(resource))
}
}
private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) }
/**
* Retrieves an ImageBitmap for the given resource ID.
* Retrieves an ImageBitmap using the specified image resource.
*
* @param id The ID of the resource to load the ImageBitmap from.
* @param resource The image resource to be used.
* @return The ImageBitmap loaded from the resource.
*/
@ExperimentalResourceApi
@Composable
fun imageResource(id: ResourceId): ImageBitmap {
fun imageResource(resource: ImageResource): ImageBitmap {
val resourceReader = LocalResourceReader.current
val imageBitmap by rememberState(id, { emptyImageBitmap }) {
val path = getPathById(id)
val imageBitmap by rememberState(resource, { emptyImageBitmap }) {
val path = resource.getPathByEnvironment()
val cached = loadImage(path, resourceReader) {
ImageCache.Bitmap(it.toImageBitmap())
} as ImageCache.Bitmap
@ -62,18 +86,18 @@ private val emptyImageVector: ImageVector by lazy {
}
/**
* Retrieves an ImageVector for the given resource ID.
* Retrieves an ImageVector using the specified image resource.
*
* @param id The ID of the resource to load the ImageVector from.
* @param resource The image resource to be used.
* @return The ImageVector loaded from the resource.
*/
@ExperimentalResourceApi
@Composable
fun vectorResource(id: ResourceId): ImageVector {
fun vectorResource(resource: ImageResource): ImageVector {
val resourceReader = LocalResourceReader.current
val density = LocalDensity.current
val imageVector by rememberState(id, { emptyImageVector }) {
val path = getPathById(id)
val imageVector by rememberState(resource, { emptyImageVector }) {
val path = resource.getPathByEnvironment()
val cached = loadImage(path, resourceReader) {
ImageCache.Vector(it.toXmlElement().toImageVector(density))
} as ImageCache.Vector
@ -90,9 +114,8 @@ private sealed interface ImageCache {
class Vector(val vector: ImageVector) : ImageCache
}
@OptIn(ExperimentalCoroutinesApi::class)
private val imageCacheDispatcher = Dispatchers.Default.limitedParallelism(1)
private val imageCache = mutableMapOf<String, ImageCache>()
private val imageCacheMutex = Mutex()
private val imageCache = mutableMapOf<String, Deferred<ImageCache>>()
//@TestOnly
internal fun dropImageCache() {
@ -103,6 +126,14 @@ private suspend fun loadImage(
path: String,
resourceReader: ResourceReader,
decode: (ByteArray) -> ImageCache
): ImageCache = withContext(imageCacheDispatcher) {
imageCache.getOrPut(path) { decode(resourceReader.read(path)) }
): ImageCache = coroutineScope {
val deferred = imageCacheMutex.withLock {
imageCache.getOrPut(path) {
//LAZY - to free the mutex lock as fast as possible
async(start = CoroutineStart.LAZY) {
decode(resourceReader.read(path))
}
}
}
deferred.await()
}

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

@ -1,6 +1,48 @@
package org.jetbrains.compose.resources
internal typealias ResourceId = String
import androidx.compose.runtime.Immutable
@RequiresOptIn("This API is experimental and is likely to change in the future.")
annotation class ExperimentalResourceApi
annotation class ExperimentalResourceApi
/**
* Represents a resource with an ID and a set of resource items.
*
* @property id The ID of the resource.
* @property items The set of resource items associated with the resource.
*/
@Immutable
sealed class Resource(
internal val id: String,
internal val items: Set<ResourceItem>
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Resource
return id == other.id
}
override fun hashCode(): Int {
return id.hashCode()
}
}
/**
* Represents a resource item with qualifiers and a path.
*
* @property qualifiers The qualifiers of the resource item.
* @property path The path of the resource item.
*/
@Immutable
data class ResourceItem(
internal val qualifiers: Set<String>,
internal val path: String
)
internal fun Resource.getPathByEnvironment(): String {
//TODO
return items.first().path
}

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

@ -1,14 +0,0 @@
package org.jetbrains.compose.resources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
//TODO Here will be logic to map a static ID to a file path in resources dir
//at the moment ID = file path
internal suspend fun getPathById(id: ResourceId): String = id
@Composable
internal fun rememberFilePath(id: ResourceId): State<String> =
rememberState(id, { "" }) { getPathById(id) }
internal val ResourceId.stringKey get() = this

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

@ -1,85 +1,110 @@
package org.jetbrains.compose.resources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jetbrains.compose.resources.vector.xmldom.Element
import org.jetbrains.compose.resources.vector.xmldom.NodeList
private const val STRINGS_XML = "strings.xml" //todo
private val SimpleStringFormatRegex = Regex("""%(\d)\$[ds]""")
/**
* Represents a string resource in the application.
*
* @param id The unique identifier of the resource.
* @param key The key used to retrieve the string resource.
* @param items The set of resource items associated with the string resource.
*/
@Immutable
class StringResource(id: String, val key: String, items: Set<ResourceItem>): Resource(id, items)
private sealed interface StringItem {
data class Value(val text: String) : StringItem
data class Array(val items: List<String>) : StringItem
}
@OptIn(ExperimentalCoroutinesApi::class)
private val stringsCacheDispatcher = Dispatchers.Default.limitedParallelism(1)
private val parsedStringsCache = mutableMapOf<String, Map<String, StringItem>>()
private val stringsCacheMutex = Mutex()
private val parsedStringsCache = mutableMapOf<String, Deferred<Map<String, StringItem>>>()
//@TestOnly
internal fun dropStringsCache() {
parsedStringsCache.clear()
}
private suspend fun getParsedStrings(path: String, resourceReader: ResourceReader): Map<String, StringItem> =
withContext(stringsCacheDispatcher) {
private suspend fun getParsedStrings(
path: String,
resourceReader: ResourceReader
): Map<String, StringItem> = coroutineScope {
val deferred = stringsCacheMutex.withLock {
parsedStringsCache.getOrPut(path) {
val nodes = resourceReader.read(path).toXmlElement().childNodes
val strings = nodes.getElementsWithName("string").associate { element ->
element.getAttribute("name") to StringItem.Value(element.textContent.orEmpty())
}
val arrays = nodes.getElementsWithName("string-array").associate { arrayElement ->
val items = arrayElement.childNodes.getElementsWithName("item").map { element ->
element.textContent.orEmpty()
}
arrayElement.getAttribute("name") to StringItem.Array(items)
//LAZY - to free the mutex lock as fast as possible
async(start = CoroutineStart.LAZY) {
parseStringXml(path, resourceReader)
}
strings + arrays
}
}
deferred.await()
}
private suspend fun parseStringXml(path: String, resourceReader: ResourceReader): Map<String, StringItem> {
val nodes = resourceReader.read(path).toXmlElement().childNodes
val strings = nodes.getElementsWithName("string").associate { element ->
element.getAttribute("name") to StringItem.Value(element.textContent.orEmpty())
}
val arrays = nodes.getElementsWithName("string-array").associate { arrayElement ->
val items = arrayElement.childNodes.getElementsWithName("item").map { element ->
element.textContent.orEmpty()
}
arrayElement.getAttribute("name") to StringItem.Array(items)
}
return strings + arrays
}
/**
* Retrieves a string resource using the provided ID.
* Retrieves a string using the specified string resource.
*
* @param id The ID of the string resource to retrieve.
* @param resource The string resource to be used.
* @return The retrieved string resource.
*
* @throws IllegalArgumentException If the provided ID is not found in the resource file.
*/
@ExperimentalResourceApi
@Composable
fun getString(id: ResourceId): String {
fun getString(resource: StringResource): String {
val resourceReader = LocalResourceReader.current
val str by rememberState(id, { "" }) { loadString(id, resourceReader) }
val str by rememberState(resource, { "" }) { loadString(resource, resourceReader) }
return str
}
/**
* Loads a string resource using the provided ID.
* Loads a string using the specified string resource.
*
* @param id The ID of the string resource to load.
* @param resource The string resource to be used.
* @return The loaded string resource.
*
* @throws IllegalArgumentException If the provided ID is not found in the resource file.
*/
@ExperimentalResourceApi
suspend fun loadString(id: ResourceId): String = loadString(id, DefaultResourceReader)
suspend fun loadString(resource: StringResource): String = loadString(resource, DefaultResourceReader)
private suspend fun loadString(id: ResourceId, resourceReader: ResourceReader): String {
val nameToValue = getParsedStrings(getPathById(STRINGS_XML), resourceReader)
val item = nameToValue[id.stringKey] as? StringItem.Value
?: error("String ID=`${id.stringKey}` is not found!")
private suspend fun loadString(resource: StringResource, resourceReader: ResourceReader): String {
val path = resource.getPathByEnvironment()
val keyToValue = getParsedStrings(path, resourceReader)
val item = keyToValue[resource.key] as? StringItem.Value
?: error("String ID=`${resource.key}` is not found!")
return item.text
}
/**
* Retrieves a formatted string resource using the provided ID and arguments.
* Retrieves a formatted string using the specified string resource and arguments.
*
* @param id The ID of the string resource to retrieve.
* @param resource The string resource to be used.
* @param formatArgs The arguments to be inserted into the formatted string.
* @return The formatted string resource.
*
@ -87,67 +112,68 @@ private suspend fun loadString(id: ResourceId, resourceReader: ResourceReader):
*/
@ExperimentalResourceApi
@Composable
fun getString(id: ResourceId, vararg formatArgs: Any): String {
fun getString(resource: StringResource, vararg formatArgs: Any): String {
val resourceReader = LocalResourceReader.current
val args = formatArgs.map { it.toString() }
val str by rememberState(id, { "" }) { loadString(id, args, resourceReader) }
val str by rememberState(resource, { "" }) { loadString(resource, args, resourceReader) }
return str
}
/**
* Loads a formatted string resource using the provided ID and arguments.
* Loads a formatted string using the specified string resource and arguments.
*
* @param id The ID of the string resource to load.
* @param resource The string resource to be used.
* @param formatArgs The arguments to be inserted into the formatted string.
* @return The formatted string resource.
*
* @throws IllegalArgumentException If the provided ID is not found in the resource file.
*/
@ExperimentalResourceApi
suspend fun loadString(id: ResourceId, vararg formatArgs: Any): String = loadString(
id,
suspend fun loadString(resource: StringResource, vararg formatArgs: Any): String = loadString(
resource,
formatArgs.map { it.toString() },
DefaultResourceReader
)
private suspend fun loadString(id: ResourceId, args: List<String>, resourceReader: ResourceReader): String {
val str = loadString(id, resourceReader)
private suspend fun loadString(resource: StringResource, args: List<String>, resourceReader: ResourceReader): String {
val str = loadString(resource, resourceReader)
return SimpleStringFormatRegex.replace(str) { matchResult ->
args[matchResult.groupValues[1].toInt() - 1]
}
}
/**
* Retrieves a list of strings from a string array resource.
* Retrieves a list of strings using the specified string array resource.
*
* @param id The ID of the string array resource.
* @param resource The string resource to be used.
* @return A list of strings representing the items in the string array.
*
* @throws IllegalStateException if the string array with the given ID is not found.
*/
@ExperimentalResourceApi
@Composable
fun getStringArray(id: ResourceId): List<String> {
fun getStringArray(resource: StringResource): List<String> {
val resourceReader = LocalResourceReader.current
val array by rememberState(id, { emptyList() }) { loadStringArray(id, resourceReader) }
val array by rememberState(resource, { emptyList() }) { loadStringArray(resource, resourceReader) }
return array
}
/**
* Loads a string array from a resource file.
* Loads a list of strings using the specified string array resource.
*
* @param id The ID of the string array resource.
* @param resource The string resource to be used.
* @return A list of strings representing the items in the string array.
*
* @throws IllegalStateException if the string array with the given ID is not found.
*/
@ExperimentalResourceApi
suspend fun loadStringArray(id: ResourceId): List<String> = loadStringArray(id, DefaultResourceReader)
suspend fun loadStringArray(resource: StringResource): List<String> = loadStringArray(resource, DefaultResourceReader)
private suspend fun loadStringArray(id: ResourceId, resourceReader: ResourceReader): List<String> {
val nameToValue = getParsedStrings(getPathById(STRINGS_XML), resourceReader)
val item = nameToValue[id.stringKey] as? StringItem.Array
?: error("String array ID=`${id.stringKey}` is not found!")
private suspend fun loadStringArray(resource: StringResource, resourceReader: ResourceReader): List<String> {
val path = resource.getPathByEnvironment()
val keyToValue = getParsedStrings(path, resourceReader)
val item = keyToValue[resource.key] as? StringItem.Array
?: error("String array ID=`${resource.key}` is not found!")
return item.items
}

12
components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt

@ -14,12 +14,12 @@ import kotlin.test.assertNotEquals
class ResourceTest {
@Test
fun testResourceEquals() = runBlockingTest {
assertEquals(getPathById("a"), getPathById("a"))
assertEquals(ImageResource("a"), ImageResource("a"))
}
@Test
fun testResourceNotEquals() = runBlockingTest {
assertNotEquals(getPathById("a"), getPathById("b"))
assertNotEquals(ImageResource("a"), ImageResource("b"))
}
@Test
@ -28,7 +28,7 @@ class ResourceTest {
readResourceBytes("missing.png")
}
val error = assertFailsWith<IllegalStateException> {
loadString("unknown_id")
loadString(TestStringResource("unknown_id"))
}
assertEquals("String ID=`unknown_id` is not found!", error.message)
}
@ -56,11 +56,11 @@ class ResourceTest {
@Test
fun testLoadStringResource() = runBlockingTest {
assertEquals("Compose Resources App", loadString("app_name"))
assertEquals("Compose Resources App", loadString(TestStringResource("app_name")))
assertEquals(
"Hello, test-name! You have 42 new messages.",
loadString("str_template", "test-name", 42)
loadString(TestStringResource("str_template"), "test-name", 42)
)
assertEquals(listOf("item 1", "item 2", "item 3"), loadStringArray("str_arr"))
assertEquals(listOf("item 1", "item 2", "item 3"), loadStringArray(TestStringResource("str_arr")))
}
}

8
components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt

@ -2,4 +2,10 @@ package org.jetbrains.compose.resources
import kotlinx.coroutines.CoroutineScope
expect fun runBlockingTest(block: suspend CoroutineScope.() -> Unit)
expect fun runBlockingTest(block: suspend CoroutineScope.() -> Unit)
internal fun TestStringResource(key: String) = StringResource(
"STRING:$key",
key,
setOf(ResourceItem(emptySet(), "strings.xml"))
)

38
components/resources/library/src/desktopTest/kotlin/org/jetbrains/compose/resources/ComposeResourceTest.desktop.kt

@ -25,17 +25,17 @@ class ComposeResourceTest {
@Test
fun testCountRecompositions() = runComposeUiTest {
runBlockingTest {
val imagePathFlow = MutableStateFlow("1.png")
val imagePathFlow = MutableStateFlow(ImageResource("1.png"))
val recompositionsCounter = RecompositionsCounter()
setContent {
val path by imagePathFlow.collectAsState()
val res = imageResource(path)
val res by imagePathFlow.collectAsState()
val imgRes = imageResource(res)
recompositionsCounter.content {
Image(bitmap = res, contentDescription = null)
Image(bitmap = imgRes, contentDescription = null)
}
}
awaitIdle()
imagePathFlow.emit("2.png")
imagePathFlow.emit(ImageResource("2.png"))
awaitIdle()
assertEquals(2, recompositionsCounter.count)
}
@ -45,17 +45,17 @@ class ComposeResourceTest {
fun testImageResourceCache() = runComposeUiTest {
runBlockingTest {
val testResourceReader = TestResourceReader()
val imagePathFlow = MutableStateFlow("1.png")
val imagePathFlow = MutableStateFlow(ImageResource("1.png"))
setContent {
CompositionLocalProvider(LocalResourceReader provides testResourceReader) {
val path by imagePathFlow.collectAsState()
Image(painterResource(path), null)
val res by imagePathFlow.collectAsState()
Image(painterResource(res), null)
}
}
awaitIdle()
imagePathFlow.emit("2.png")
imagePathFlow.emit(ImageResource("2.png"))
awaitIdle()
imagePathFlow.emit("1.png")
imagePathFlow.emit(ImageResource("1.png"))
awaitIdle()
assertEquals(
@ -69,18 +69,18 @@ class ComposeResourceTest {
fun testStringResourceCache() = runComposeUiTest {
runBlockingTest {
val testResourceReader = TestResourceReader()
val stringIdFlow = MutableStateFlow("app_name")
val stringIdFlow = MutableStateFlow(TestStringResource("app_name"))
setContent {
CompositionLocalProvider(LocalResourceReader provides testResourceReader) {
val textId by stringIdFlow.collectAsState()
Text(getString(textId))
Text(getStringArray("str_arr").joinToString())
val res by stringIdFlow.collectAsState()
Text(getString(res))
Text(getStringArray(TestStringResource("str_arr")).joinToString())
}
}
awaitIdle()
stringIdFlow.emit("hello")
stringIdFlow.emit(TestStringResource("hello"))
awaitIdle()
stringIdFlow.emit("app_name")
stringIdFlow.emit(TestStringResource("app_name"))
awaitIdle()
assertEquals(
@ -94,12 +94,12 @@ class ComposeResourceTest {
fun testReadStringResource() = runComposeUiTest {
runBlockingTest {
setContent {
assertEquals("Compose Resources App", getString("app_name"))
assertEquals("Compose Resources App", getString(TestStringResource("app_name")))
assertEquals(
"Hello, test-name! You have 42 new messages.",
getString("str_template", "test-name", 42)
getString(TestStringResource("str_template"), "test-name", 42)
)
assertEquals(listOf("item 1", "item 2", "item 3"), getStringArray("str_arr"))
assertEquals(listOf("item 1", "item 2", "item 3"), getStringArray(TestStringResource("str_arr")))
}
awaitIdle()
}

9
components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/FontResources.skiko.kt

@ -33,11 +33,12 @@ private val defaultEmptyFont by lazy { Font("org.jetbrains.compose.emptyFont", B
@ExperimentalResourceApi
@Composable
actual fun Font(id: ResourceId, weight: FontWeight, style: FontStyle): Font {
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val resourceReader = LocalResourceReader.current
val fontFile by rememberState(id, { defaultEmptyFont }) {
val fontBytes = resourceReader.read(getPathById(id))
Font(id, fontBytes, weight, style)
val fontFile by rememberState(resource, { defaultEmptyFont }) {
val path = resource.getPathByEnvironment()
val fontBytes = resourceReader.read(path)
Font(path, fontBytes, weight, style)
}
return fontFile
}

3
gradle-plugins/build.gradle.kts

@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.publish.plugin.portal) apply false
alias(libs.plugins.publish.plugin) apply false
alias(libs.plugins.shadow.jar) apply false
alias(libs.plugins.download) apply false
}
@ -14,6 +14,7 @@ subprojects {
repositories {
mavenCentral()
google()
mavenLocal()
}

11
gradle-plugins/compose/build.gradle.kts

@ -1,9 +1,10 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import de.undercouch.gradle.tasks.download.Download
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.publish.plugin.portal)
alias(libs.plugins.publish.plugin)
id("java-gradle-plugin")
id("maven-publish")
alias(libs.plugins.shadow.jar)
@ -30,8 +31,9 @@ val buildConfig = tasks.register("buildConfig", GenerateBuildConfig::class.java)
fieldsToGenerate.put("composeVersion", BuildProperties.composeVersion(project))
fieldsToGenerate.put("composeGradlePluginVersion", BuildProperties.deployVersion(project))
}
tasks.named("compileKotlin") {
tasks.named("compileKotlin", KotlinCompilationTask::class) {
dependsOn(buildConfig)
compilerOptions.freeCompilerArgs.add("-opt-in=org.jetbrains.compose.ExperimentalComposeLibrary")
}
sourceSets.main.configure {
java.srcDir(buildConfig.flatMap { it.generatedOutputDir })
@ -58,11 +60,14 @@ dependencies {
compileOnly(kotlin("gradle-plugin-api"))
compileOnly(kotlin("gradle-plugin"))
compileOnly(kotlin("native-utils"))
compileOnly(libs.plugin.android)
compileOnly(libs.plugin.android.api)
testImplementation(gradleTestKit())
testImplementation(kotlin("gradle-plugin-api"))
embedded(libs.download.task)
embedded(libs.kotlin.poet)
embedded(project(":preview-rpc"))
embedded(project(":jdk-version-probe"))
}
@ -98,7 +103,7 @@ val gradleTestsPattern = "org.jetbrains.compose.test.tests.integration.*"
tasks.registerVerificationTask<CheckJarPackagesTask>("checkJar") {
dependsOn(jar)
jarFile.set(jar.archiveFile)
allowedPackagePrefixes.addAll("org.jetbrains.compose", "kotlinx.serialization")
allowedPackagePrefixes.addAll("org.jetbrains.compose", "kotlinx.serialization", "com.squareup.kotlinpoet")
}
tasks.test {

5
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt

@ -22,13 +22,14 @@ import org.jetbrains.compose.experimental.dsl.ExperimentalExtension
import org.jetbrains.compose.experimental.internal.configureExperimentalTargetsFlagsCheck
import org.jetbrains.compose.experimental.internal.configureExperimental
import org.jetbrains.compose.experimental.internal.configureNativeCompilerCaching
import org.jetbrains.compose.experimental.uikit.internal.resources.configureSyncTask
import org.jetbrains.compose.internal.KOTLIN_MPP_PLUGIN_ID
import org.jetbrains.compose.internal.mppExt
import org.jetbrains.compose.internal.mppExtOrNull
import org.jetbrains.compose.internal.service.ConfigurationProblemReporterService
import org.jetbrains.compose.internal.service.GradlePropertySnapshotService
import org.jetbrains.compose.internal.utils.currentTarget
import org.jetbrains.compose.resources.configureResourceGenerator
import org.jetbrains.compose.resources.ios.configureSyncTask
import org.jetbrains.compose.web.WebExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
@ -64,6 +65,8 @@ abstract class ComposePlugin : Plugin<Project> {
project.plugins.apply(ComposeCompilerKotlinSupportPlugin::class.java)
project.configureNativeCompilerCaching()
project.configureResourceGenerator()
project.afterEvaluate {
configureDesktop(project, desktopExtension)
project.configureExperimental(composeExtension, experimentalExtension)

8
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/ComposeProjectProperties.kt

@ -21,6 +21,8 @@ internal object ComposeProperties {
internal const val MAC_NOTARIZATION_PASSWORD = "compose.desktop.mac.notarization.password"
internal const val MAC_NOTARIZATION_TEAM_ID_PROVIDER = "compose.desktop.mac.notarization.teamID"
internal const val CHECK_JDK_VENDOR = "compose.desktop.packaging.checkJdkVendor"
internal const val ALWAYS_GENERATE_RESOURCE_ACCESSORS = "compose.resources.always.generate.accessors"
internal const val SYNC_RESOURCES_PROPERTY = "compose.ios.resources.sync"
fun isVerbose(providers: ProviderFactory): Provider<Boolean> =
providers.valueOrNull(VERBOSE).toBooleanProvider(false)
@ -51,4 +53,10 @@ internal object ComposeProperties {
fun checkJdkVendor(providers: ProviderFactory): Provider<Boolean> =
providers.valueOrNull(CHECK_JDK_VENDOR).toBooleanProvider(true)
fun alwaysGenerateResourceAccessors(providers: ProviderFactory): Provider<Boolean> =
providers.valueOrNull(ALWAYS_GENERATE_RESOURCE_ACCESSORS).toBooleanProvider(false)
fun syncResources(providers: ProviderFactory): Provider<Boolean> =
providers.valueOrNull(SYNC_RESOURCES_PROPERTY).toBooleanProvider(true)
}

18
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/utils/IosGradleProperties.kt

@ -1,18 +0,0 @@
/*
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal.utils
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.jetbrains.compose.internal.utils.valueOrNull
import org.jetbrains.compose.internal.utils.toBooleanProvider
internal object IosGradleProperties {
const val SYNC_RESOURCES_PROPERTY = "compose.ios.resources.sync"
fun syncResources(providers: ProviderFactory): Provider<Boolean> =
providers.valueOrNull(SYNC_RESOURCES_PROPERTY).toBooleanProvider(true)
}

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

@ -0,0 +1,42 @@
package org.jetbrains.compose.resources
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.tasks.MergeSourceSetFolders
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.SourceSet
import org.jetbrains.compose.internal.utils.registerTask
import java.io.File
internal fun Project.configureAndroidResources(
commonResourcesDir: Provider<File>,
androidFontsDir: Provider<File>,
onlyIfProvider: Provider<Boolean>
) {
val androidExtension = project.extensions.findByName("android") as? BaseExtension ?: return
val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return
val androidMainSourceSet = androidExtension.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
androidMainSourceSet.resources.srcDir(commonResourcesDir)
androidMainSourceSet.assets.srcDir(androidFontsDir)
val copyFonts = registerTask<Copy>("copyFontsToAndroidAssets") {
includeEmptyDirs = false
from(commonResourcesDir)
include("**/fonts/*")
into(androidFontsDir)
onlyIf { onlyIfProvider.get() }
}
androidComponents.onVariants { variant ->
variant.sources?.assets?.addGeneratedSourceDirectory(
taskProvider = copyFonts,
wiredWith = {
objects.directoryProperty().fileProvider(
copyFonts.map { t -> t.destinationDir }
)
}
)
}
}

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

@ -0,0 +1,98 @@
package org.jetbrains.compose.resources
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import java.io.File
import java.nio.file.Path
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.io.path.relativeTo
/**
* This task should be FAST and SAFE! Because it is being run during IDE import.
*/
abstract class GenerateResClassTask : DefaultTask() {
@get:Input
abstract val packageName: Property<String>
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val resDir: DirectoryProperty
@get:OutputDirectory
abstract val codeDir: DirectoryProperty
init {
this.onlyIf { resDir.asFile.get().exists() }
}
@TaskAction
fun generate() {
try {
val rootResDir = resDir.get().asFile
logger.info("Generate resources for $rootResDir")
//get first level dirs
val dirs = rootResDir.listFiles { f -> f.isDirectory }.orEmpty()
//type -> id -> resource item
val resources: Map<ResourceType, Map<String, List<ResourceItem>>> = dirs
.flatMap { dir ->
dir.listFiles { f -> !f.isDirectory }
.orEmpty()
.mapNotNull { it.fileToResourceItems(rootResDir.parentFile.toPath()) }
.flatten()
}
.groupBy { it.type }
.mapValues { (_, items) -> items.groupBy { it.name } }
val kotlinDir = codeDir.get().asFile
kotlinDir.deleteRecursively()
kotlinDir.mkdirs()
getResFileSpec(resources, packageName.get()).writeTo(kotlinDir)
} catch (e: Exception) {
//message must contain two ':' symbols to be parsed by IDE UI!
logger.error("e: GenerateResClassTask was failed:", e)
}
}
private fun File.fileToResourceItems(
relativeTo: Path
): List<ResourceItem>? {
val file = this
if (file.isDirectory) return null
val dirName = file.parentFile.name ?: return null
val typeAndQualifiers = dirName.lowercase().split("-")
if (typeAndQualifiers.isEmpty()) return null
val typeString = typeAndQualifiers.first().lowercase()
val qualifiers = typeAndQualifiers.takeLast(typeAndQualifiers.size - 1).map { it.lowercase() }.toSet()
val path = file.toPath().relativeTo(relativeTo)
return if (typeString == "values" && file.name.equals("strings.xml", true)) {
val stringIds = getStringIds(file)
stringIds.map { strId ->
ResourceItem(ResourceType.STRING, qualifiers, strId.lowercase(), path)
}
} else {
val type = try {
ResourceType.fromString(typeString)
} catch (e: Exception) {
logger.error("e: Error: $path", e)
return null
}
listOf(ResourceItem(type, qualifiers, file.nameWithoutExtension.lowercase(), path))
}
}
private val stringTypeNames = listOf("string", "string-array")
private fun getStringIds(stringsXml: File): Set<String> {
val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stringsXml)
val items = doc.getElementsByTagName("resources").item(0).childNodes
val ids = List(items.length) { items.item(it) }
.filter { it.nodeName in stringTypeNames }
.map { it.attributes.getNamedItem("name").nodeValue }
return ids.toSet()
}
}

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

@ -0,0 +1,77 @@
package org.jetbrains.compose.resources
import org.gradle.api.Project
import org.gradle.api.provider.Provider
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.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import java.io.File
private const val COMPOSE_RESOURCES_DIR = "composeRes"
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() }
val packageName = provider {
buildString {
val group = project.group.toString()
append(group)
if (group.isNotEmpty()) append(".")
append("generated.resources")
}
}
fun buildDir(path: String) = layout.dir(layout.buildDirectory.map { File(it.asFile, path) })
val resDir = layout.dir(commonResourcesDir.map { it.resolve(COMPOSE_RESOURCES_DIR) })
//lazy check a dependency on the Resources library
val shouldGenerateResourceAccessors: Provider<Boolean> = 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() }
}
//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)
}
}
val androidExtension = project.extensions.findByName("android")
if (androidExtension != null) {
configureAndroidResources(
commonResourcesDir,
buildDir("$RES_GEN_DIR/androidFonts").map { it.asFile },
shouldGenerateResourceAccessors
)
}
}

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

@ -0,0 +1,95 @@
package org.jetbrains.compose.resources
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.withIndent
import java.nio.file.Path
import kotlin.io.path.invariantSeparatorsPathString
import kotlin.io.path.pathString
internal enum class ResourceType(val typeName: String) {
IMAGE("images"),
STRING("strings"),
FONT("fonts");
companion object {
fun fromString(str: String) = when (str) {
"images" -> ResourceType.IMAGE
"strings" -> ResourceType.STRING
"fonts" -> ResourceType.FONT
else -> error("Unknown resource type: $str")
}
}
}
internal data class ResourceItem(
val type: ResourceType,
val qualifiers: Set<String>,
val name: String,
val path: Path
)
private fun ResourceItem.getClassName(): ClassName = when (type) {
ResourceType.IMAGE -> ClassName("org.jetbrains.compose.resources", "ImageResource")
ResourceType.STRING -> ClassName("org.jetbrains.compose.resources", "StringResource")
ResourceType.FONT -> ClassName("org.jetbrains.compose.resources", "FontResource")
}
internal fun getResFileSpec(
//type -> id -> items
resources: Map<ResourceType, Map<String, List<ResourceItem>>>,
packageName: String
): FileSpec = FileSpec.builder(packageName, "Res").apply {
addType(TypeSpec.objectBuilder("Res").apply {
addModifiers(KModifier.INTERNAL)
val types = resources.map { (type, idToResources) ->
getResourceTypeObject(type, idToResources)
}.sortedBy { it.name }
addTypes(types)
}.build())
}.build()
private fun getResourceTypeObject(type: ResourceType, nameToResources: Map<String, List<ResourceItem>>) =
TypeSpec.objectBuilder(type.typeName).apply {
nameToResources.entries
.sortedBy { it.key }
.forEach { (name, items) ->
addResourceProperty(name, items.sortedBy { it.path })
}
}.build()
private fun TypeSpec.Builder.addResourceProperty(name: String, items: List<ResourceItem>) {
val resourceItemClass = ClassName("org.jetbrains.compose.resources", "ResourceItem")
val first = items.first()
val propertyClassName = first.getClassName()
val resourceId = first.let { "${it.type}:${it.name}" }
val initializer = CodeBlock.builder()
.add("%T(\n", propertyClassName).withIndent {
add("\"$resourceId\",\n")
if (first.type == ResourceType.STRING) {
add("\"${first.name}\",\n")
}
add("setOf(\n").withIndent {
items.forEach { item ->
val qualifiers = item.qualifiers.sorted().joinToString { "\"$it\"" }
//file separator should be '/' on all platforms
add("%T(setOf($qualifiers), \"${item.path.invariantSeparatorsPathString}\"),\n", resourceItemClass)
}
}
add(")\n")
}
.add(")")
.build()
addProperty(
PropertySpec.builder(name, propertyClassName)
.initializer(initializer)
.build()
)
}

2
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/resources/IosTargetResources.kt → gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/IosTargetResources.kt

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal.resources
package org.jetbrains.compose.resources.ios
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty

5
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/tasks/SyncComposeResourcesForIosTask.kt → gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/SyncComposeResourcesForIosTask.kt

@ -3,15 +3,14 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.tasks
package org.jetbrains.compose.resources.ios
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.jetbrains.compose.experimental.uikit.internal.resources.determineIosKonanTargetsFromEnv
import org.jetbrains.compose.experimental.uikit.internal.resources.IosTargetResources
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
import org.jetbrains.compose.internal.utils.clearDirs
import java.io.File
import kotlin.io.path.Path

13
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/resources/configureSyncIosResources.kt → gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/configureSyncIosResources.kt

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal.resources
package org.jetbrains.compose.resources.ios
import org.gradle.api.Project
import org.gradle.api.file.Directory
@ -11,12 +11,11 @@ import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskAction
import org.jetbrains.compose.experimental.uikit.internal.utils.IosGradleProperties
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
import org.jetbrains.compose.experimental.uikit.internal.utils.asIosNativeTargetOrNull
import org.jetbrains.compose.experimental.uikit.internal.utils.cocoapodsExt
import org.jetbrains.compose.experimental.uikit.internal.utils.withCocoapodsPlugin
import org.jetbrains.compose.experimental.uikit.tasks.AbstractComposeIosTask
import org.jetbrains.compose.experimental.uikit.tasks.SyncComposeResourcesForIosTask
import org.jetbrains.compose.internal.utils.joinLowerCamelCase
import org.jetbrains.compose.internal.utils.new
import org.jetbrains.compose.internal.utils.registerOrConfigure
@ -36,8 +35,8 @@ internal fun Project.configureSyncTask(mppExt: KotlinMultiplatformExtension) {
logger.info("Compose Multiplatform resource management for iOS is disabled: $reason")
}
if (!IosGradleProperties.syncResources(providers).get()) {
reportSyncIsDisabled("'${IosGradleProperties.SYNC_RESOURCES_PROPERTY}' value is 'false'")
if (!ComposeProperties.syncResources(providers).get()) {
reportSyncIsDisabled("'${ComposeProperties.SYNC_RESOURCES_PROPERTY}' value is 'false'")
return
}
@ -48,7 +47,7 @@ internal fun Project.configureSyncTask(mppExt: KotlinMultiplatformExtension) {
}
}
with (SyncIosResourcesContext(project, mppExt)) {
with(SyncIosResourcesContext(project, mppExt)) {
configureSyncResourcesTasks()
configureCocoapodsResourcesAttribute()
}
@ -98,7 +97,7 @@ private fun SyncIosResourcesContext.configureCocoapodsResourcesAttribute() {
error("""
|Kotlin.cocoapods.extraSpecAttributes["resources"] is not compatible with Compose Multiplatform's resources management for iOS.
| * Recommended action: remove extraSpecAttributes["resources"] from '${project.buildFile}' and run '${project.path}:podInstall' once;
| * Alternative action: turn off Compose Multiplatform's resources management for iOS by adding '${IosGradleProperties.SYNC_RESOURCES_PROPERTY}=false' to your gradle.properties;
| * Alternative action: turn off Compose Multiplatform's resources management for iOS by adding '${ComposeProperties.SYNC_RESOURCES_PROPERTY}=false' to your gradle.properties;
""".trimMargin())
}
cocoapodsExt.framework {

2
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/experimental/uikit/internal/resources/determineIosKonanTargetsFromEnv.kt → gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ios/determineIosKonanTargetsFromEnv.kt

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.experimental.uikit.internal.resources
package org.jetbrains.compose.resources.ios
import org.jetbrains.kotlin.konan.target.KonanTarget

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

@ -0,0 +1,46 @@
package org.jetbrains.compose.test.tests.integration
import org.jetbrains.compose.test.utils.GradlePluginTestBase
import org.jetbrains.compose.test.utils.assertEqualTextFiles
import org.jetbrains.compose.test.utils.assertNotEqualTextFiles
import org.jetbrains.compose.test.utils.checks
import org.junit.jupiter.api.Test
import kotlin.io.path.Path
class ResourcesTest : GradlePluginTestBase() {
@Test
fun testGeneratedAccessorsAndCopiedFonts() = with(testProject("misc/commonResources")) {
//check generated resource's accessors
gradle("generateComposeResClass").checks {
assertEqualTextFiles(
file("build/generated/compose/resourceGenerator/kotlin/generated/resources/Res.kt"),
file("expected/Res.kt")
)
check.logContains("""
java.lang.IllegalStateException: Unknown resource type: ignored
""".trimIndent())
}
file("src/commonMain/resources/composeRes/images/vector_2.xml").renameTo(
file("src/commonMain/resources/composeRes/images/vector_3.xml")
)
//check resource's accessors were regenerated
gradle("generateComposeResClass").checks {
assertNotEqualTextFiles(
file("build/generated/compose/resourceGenerator/kotlin/generated/resources/Res.kt"),
file("expected/Res.kt")
)
}
file("src/commonMain/resources/composeRes/images/vector_3.xml").renameTo(
file("src/commonMain/resources/composeRes/images/vector_2.xml")
)
//TODO: check a real build after a release a new version of the resources library
//because generated accessors depend on classes from the new version
gradle("assembleDebug", "--dry-run").checks {
check.taskSkipped("copyFontsToAndroidAssets")
}
}
}

2
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/utils/TestProject.kt

@ -15,6 +15,7 @@ import java.util.Properties
data class TestEnvironment(
val workingDir: File,
val kotlinVersion: String = TestKotlinVersions.Default,
val agpVersion: String = "7.3.1",
val composeGradlePluginVersion: String = TestProperties.composeGradlePluginVersion,
val mokoResourcesPluginVersion: String = "0.23.0",
val composeCompilerPlugin: String? = null,
@ -26,6 +27,7 @@ data class TestEnvironment(
private val placeholders = linkedMapOf(
"COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER" to composeGradlePluginVersion,
"KOTLIN_VERSION_PLACEHOLDER" to kotlinVersion,
"AGP_VERSION_PLACEHOLDER" to agpVersion,
"COMPOSE_COMPILER_PLUGIN_PLACEHOLDER" to composeCompilerPlugin,
"COMPOSE_COMPILER_PLUGIN_ARGS_PLACEHOLDER" to composeCompilerArgs,
"MOKO_RESOURCES_PLUGIN_VERSION_PLACEHOLDER" to mokoResourcesPluginVersion,

2
gradle-plugins/compose/src/test/test-projects/application/mpp/settings.gradle

@ -2,7 +2,7 @@ pluginManagement {
plugins {
id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER'
id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER'
id 'com.android.application' version '7.0.4'
id 'com.android.application' version 'AGP_VERSION_PLACEHOLDER'
}
repositories {
mavenLocal()

39
gradle-plugins/compose/src/test/test-projects/misc/commonResources/build.gradle.kts

@ -0,0 +1,39 @@
plugins {
kotlin("multiplatform")
id("com.android.library")
id("org.jetbrains.compose")
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}
jvm("desktop")
sourceSets {
commonMain {
dependencies {
implementation(compose.runtime)
implementation(compose.material)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
}
}
}
}
android {
compileSdk = 31
namespace = "org.jetbrains.compose.resources.test"
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

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

@ -0,0 +1,78 @@
package generated.resources
import org.jetbrains.compose.resources.FontResource
import org.jetbrains.compose.resources.ImageResource
import org.jetbrains.compose.resources.ResourceItem
import org.jetbrains.compose.resources.StringResource
internal object Res {
public object fonts {
public val emptyfont: FontResource = FontResource(
"FONT:emptyfont",
setOf(
ResourceItem(setOf(), "composeRes/fonts/emptyFont.otf"),
)
)
}
public object images {
public val vector: ImageResource = ImageResource(
"IMAGE:vector",
setOf(
ResourceItem(setOf("q1", "q2"), "composeRes/images-q1-q2/vector.xml"),
ResourceItem(setOf("q1"), "composeRes/images-q1/vector.xml"),
ResourceItem(setOf("q2"), "composeRes/images-q2/vector.xml"),
ResourceItem(setOf(), "composeRes/images/vector.xml"),
)
)
public val vector_2: ImageResource = ImageResource(
"IMAGE:vector_2",
setOf(
ResourceItem(setOf(), "composeRes/images/vector_2.xml"),
)
)
}
public object strings {
public val app_name: StringResource = StringResource(
"STRING:app_name",
"app_name",
setOf(
ResourceItem(setOf(), "composeRes/values/strings.xml"),
)
)
public val hello: StringResource = StringResource(
"STRING:hello",
"hello",
setOf(
ResourceItem(setOf(), "composeRes/values/strings.xml"),
)
)
public val multi_line: StringResource = StringResource(
"STRING:multi_line",
"multi_line",
setOf(
ResourceItem(setOf(), "composeRes/values/strings.xml"),
)
)
public val str_arr: StringResource = StringResource(
"STRING:str_arr",
"str_arr",
setOf(
ResourceItem(setOf(), "composeRes/values/strings.xml"),
)
)
public val str_template: StringResource = StringResource(
"STRING:str_template",
"str_template",
setOf(
ResourceItem(setOf(), "composeRes/values/strings.xml"),
)
)
}
}

1
gradle-plugins/compose/src/test/test-projects/misc/commonResources/gradle.properties

@ -0,0 +1 @@
android.useAndroidX=true

22
gradle-plugins/compose/src/test/test-projects/misc/commonResources/settings.gradle.kts

@ -0,0 +1,22 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
plugins {
id("com.android.library").version("AGP_VERSION_PLACEHOLDER")
id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER")
id("org.jetbrains.compose").version("COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER")
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
mavenCentral()
gradlePluginPortal()
google()
}
}

20
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/kotlin/App.kt

@ -0,0 +1,20 @@
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.FontFamily
import generated.resources.Res
import org.jetbrains.compose.resources.*
@OptIn(ExperimentalResourceApi::class)
@Composable
fun App() {
Column {
Image(
modifier = Modifier.size(100.dp),
painter = painterResource(Res.images.vector),
contentDescription = null
)
Text(getString(Res.strings.app_name))
val font = FontFamily(Font(Res.fonts.emptyfont))
}
}

BIN
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/fonts/emptyFont.otf

Binary file not shown.

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

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

36
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images-q1-q2/vector.xml

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
android:fillColor="#041619"
android:fillType="nonZero"/>
<path
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
android:fillColor="#37BF6E"
android:fillType="nonZero"/>
<path
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
android:fillColor="#3870B2"
android:fillType="nonZero"/>
<path
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
</vector>

36
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images-q1/vector.xml

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
android:fillColor="#041619"
android:fillType="nonZero"/>
<path
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
android:fillColor="#37BF6E"
android:fillType="nonZero"/>
<path
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
android:fillColor="#3870B2"
android:fillType="nonZero"/>
<path
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
</vector>

36
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images-q2/vector.xml

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
android:fillColor="#041619"
android:fillType="nonZero"/>
<path
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
android:fillColor="#37BF6E"
android:fillType="nonZero"/>
<path
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
android:fillColor="#3870B2"
android:fillType="nonZero"/>
<path
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
</vector>

36
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images/vector.xml

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
android:fillColor="#041619"
android:fillType="nonZero"/>
<path
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
android:fillColor="#37BF6E"
android:fillType="nonZero"/>
<path
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
android:fillColor="#3870B2"
android:fillType="nonZero"/>
<path
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
</vector>

36
gradle-plugins/compose/src/test/test-projects/misc/commonResources/src/commonMain/resources/composeRes/images/vector_2.xml

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="600dp"
android:height="600dp"
android:viewportWidth="600"
android:viewportHeight="600">
<path
android:pathData="M301.21,418.53C300.97,418.54 300.73,418.56 300.49,418.56C297.09,418.59 293.74,417.72 290.79,416.05L222.6,377.54C220.63,376.43 219,374.82 217.85,372.88C216.7,370.94 216.09,368.73 216.07,366.47L216.07,288.16C216.06,287.32 216.09,286.49 216.17,285.67C216.38,283.54 216.91,281.5 217.71,279.6L199.29,268.27L177.74,256.19C175.72,260.43 174.73,265.23 174.78,270.22L174.79,387.05C174.85,393.89 178.57,400.2 184.53,403.56L286.26,461.02C290.67,463.51 295.66,464.8 300.73,464.76C300.91,464.76 301.09,464.74 301.27,464.74C301.24,449.84 301.22,439.23 301.22,439.23L301.21,418.53Z"
android:fillColor="#041619"
android:fillType="nonZero"/>
<path
android:pathData="M409.45,242.91L312.64,188.23C303.64,183.15 292.58,183.26 283.68,188.51L187.92,245C183.31,247.73 179.93,251.62 177.75,256.17L177.74,256.19L199.29,268.27L217.71,279.6C217.83,279.32 217.92,279.02 218.05,278.74C218.24,278.36 218.43,277.98 218.64,277.62C219.06,276.88 219.52,276.18 220.04,275.51C221.37,273.8 223.01,272.35 224.87,271.25L289.06,233.39C290.42,232.59 291.87,231.96 293.39,231.51C295.53,230.87 297.77,230.6 300,230.72C302.98,230.88 305.88,231.73 308.47,233.2L373.37,269.85C375.54,271.08 377.49,272.68 379.13,274.57C379.68,275.19 380.18,275.85 380.65,276.53C380.86,276.84 381.05,277.15 381.24,277.47L397.79,266.39L420.34,252.93L420.31,252.88C417.55,248.8 413.77,245.35 409.45,242.91Z"
android:fillColor="#37BF6E"
android:fillType="nonZero"/>
<path
android:pathData="M381.24,277.47C381.51,277.92 381.77,278.38 382.01,278.84C382.21,279.24 382.39,279.65 382.57,280.06C382.91,280.88 383.19,281.73 383.41,282.59C383.74,283.88 383.92,285.21 383.93,286.57L383.93,361.1C383.96,363.95 383.35,366.77 382.16,369.36C381.93,369.86 381.69,370.35 381.42,370.83C379.75,373.79 377.32,376.27 374.39,378L310.2,415.87C307.47,417.48 304.38,418.39 301.21,418.53L301.22,439.23C301.22,439.23 301.24,449.84 301.27,464.74C306.1,464.61 310.91,463.3 315.21,460.75L410.98,404.25C419.88,399 425.31,389.37 425.22,379.03L425.22,267.85C425.17,262.48 423.34,257.34 420.34,252.93L397.79,266.39L381.24,277.47Z"
android:fillColor="#3870B2"
android:fillType="nonZero"/>
<path
android:pathData="M177.75,256.17C179.93,251.62 183.31,247.73 187.92,245L283.68,188.51C292.58,183.26 303.64,183.15 312.64,188.23L409.45,242.91C413.77,245.35 417.55,248.8 420.31,252.88L420.34,252.93L498.59,206.19C494.03,199.46 487.79,193.78 480.67,189.75L320.86,99.49C306.01,91.1 287.75,91.27 273.07,99.95L114.99,193.2C107.39,197.69 101.81,204.11 98.21,211.63L177.74,256.19L177.75,256.17ZM301.27,464.74C301.09,464.74 300.91,464.76 300.73,464.76C295.66,464.8 290.67,463.51 286.26,461.02L184.53,403.56C178.57,400.2 174.85,393.89 174.79,387.05L174.78,270.22C174.73,265.23 175.72,260.43 177.74,256.19L98.21,211.63C94.86,218.63 93.23,226.58 93.31,234.82L93.31,427.67C93.42,438.97 99.54,449.37 109.4,454.92L277.31,549.77C284.6,553.88 292.84,556.01 301.2,555.94L301.2,555.8C301.39,543.78 301.33,495.26 301.27,464.74Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M498.59,206.19L420.34,252.93C423.34,257.34 425.17,262.48 425.22,267.85L425.22,379.03C425.31,389.37 419.88,399 410.98,404.25L315.21,460.75C310.91,463.3 306.1,464.61 301.27,464.74C301.33,495.26 301.39,543.78 301.2,555.8L301.2,555.94C309.48,555.87 317.74,553.68 325.11,549.32L483.18,456.06C497.87,447.39 506.85,431.49 506.69,414.43L506.69,230.91C506.6,222.02 503.57,213.5 498.59,206.19Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
<path
android:pathData="M301.2,555.94C292.84,556.01 284.6,553.88 277.31,549.76L109.4,454.92C99.54,449.37 93.42,438.97 93.31,427.67L93.31,234.82C93.23,226.58 94.86,218.63 98.21,211.63C101.81,204.11 107.39,197.69 114.99,193.2L273.07,99.95C287.75,91.27 306.01,91.1 320.86,99.49L480.67,189.75C487.79,193.78 494.03,199.46 498.59,206.19C503.57,213.5 506.6,222.02 506.69,230.91L506.69,414.43C506.85,431.49 497.87,447.39 483.18,456.06L325.11,549.32C317.74,553.68 309.48,555.87 301.2,555.94Z"
android:strokeWidth="10"
android:fillColor="#00000000"
android:strokeColor="#083042"
android:fillType="nonZero"/>
</vector>

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

@ -0,0 +1,13 @@
<resources>
<string name="app_name">Compose Resources App</string>
<string name="hello">😊 Hello world!</string>
<string name="multi_line">Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Donec eget turpis ac sem ultricies consequat.</string>
<string name="str_template">Hello, %1$s! You have %2$d new messages.</string>
<string-array name="str_arr">
<item>item 1</item>
<item>item 2</item>
<item>item 3</item>
</string-array>
</resources>

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

2
gradle-plugins/gradle.properties

@ -11,7 +11,7 @@ compose.tests.compiler.compatible.kotlin.version=1.9.21
compose.tests.js.compiler.compatible.kotlin.version=1.9.21
# __SUPPORTED_GRADLE_VERSIONS__
# Don't forget to edit versions in .github/workflows/gradle-plugin.yml as well
compose.tests.gradle.versions=7.3.3, 8.3
compose.tests.gradle.versions=7.4, 8.3
# A version of Gradle plugin, that will be published,
# unless overridden by COMPOSE_GRADLE_PLUGIN_VERSION env var.

11
gradle-plugins/gradle/libs.versions.toml

@ -1,12 +1,19 @@
[versions]
kotlin = "1.9.0"
gradle-download-plugin = "5.5.0"
kotlin-poet = "1.14.2"
plugin-android = "7.3.0"
shadow-jar = "8.1.1"
publish-plugin = "1.2.1"
[libraries]
download-task = { module = "de.undercouch:gradle-download-task", version.ref = "gradle-download-plugin" }
kotlin-poet = { module = "com.squareup:kotlinpoet", version.ref = "kotlin-poet" }
plugin-android = { module = "com.android.tools.build:gradle", version.ref = "plugin-android" }
plugin-android-api = { module = "com.android.tools.build:gradle-api", version.ref = "plugin-android" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
shadow-jar = "com.github.johnrengelman.shadow:8.1.1"
download = { id = "de.undercouch.download", version.ref = "gradle-download-plugin" }
publish-plugin-portal = "com.gradle.plugin-publish:1.2.1"
shadow-jar = { id = "com.github.johnrengelman.shadow", version.ref = "shadow-jar" }
publish-plugin = { id = "com.gradle.plugin-publish", version.ref = "publish-plugin" }

Loading…
Cancel
Save