diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureNativeApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureNativeApplication.kt index 2cf2c7961c7..254e77cfd23 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureNativeApplication.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureNativeApplication.kt @@ -70,6 +70,12 @@ private fun configureNativeApplication( copyright.set(project.provider { app.distributions.copyright ?: "Copyright (C) ${Calendar.getInstance().get(Calendar.YEAR)}" }) + if (binary.outputKind == NativeOutputKind.EXECUTABLE) { + val binaryResources = (binary.compilation.associatedCompilations + binary.compilation).flatMap { compilation -> + compilation.allKotlinSourceSets.map { it.resources } + } + composeResourcesDirs.setFrom(binaryResources) + } } if (TargetFormat.Dmg in app.distributions.targetFormats) { diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractNativeMacApplicationPackageAppDirTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractNativeMacApplicationPackageAppDirTask.kt index 1b051e8505b..44686e3e9df 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractNativeMacApplicationPackageAppDirTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractNativeMacApplicationPackageAppDirTask.kt @@ -5,6 +5,7 @@ package org.jetbrains.compose.desktop.application.tasks +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* @@ -43,6 +44,11 @@ abstract class AbstractNativeMacApplicationPackageAppDirTask : AbstractNativeMac @get:Optional val minimumSystemVersion: Property = objects.nullableProperty() + @get:InputFiles + @get:Optional + @get:PathSensitive(PathSensitivity.ABSOLUTE) + val composeResourcesDirs: ConfigurableFileCollection = objects.fileCollection() + override fun createPackage(destinationDir: File, workingDir: File) { val packageName = packageName.get() val appDir = destinationDir.resolve("$packageName.app").apply { mkdirs() } @@ -61,6 +67,13 @@ abstract class AbstractNativeMacApplicationPackageAppDirTask : AbstractNativeMac setupInfoPlist(executableName = appExecutableFile.name) writeToFile(contentsDir.resolve("Info.plist")) } + + if (!composeResourcesDirs.isEmpty) { + fileOperations.copy { copySpec -> + copySpec.from(composeResourcesDirs) + copySpec.into(appResourcesDir.resolve("compose-resources").apply { mkdirs() }) + } + } } private fun InfoPlistBuilder.setupInfoPlist(executableName: String) { diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt index 0a2019c2370..7fe128f87cd 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt @@ -13,6 +13,7 @@ import org.jetbrains.compose.test.utils.TestProject import org.jetbrains.compose.test.utils.assertEqualTextFiles import org.jetbrains.compose.test.utils.assertNotEqualTextFiles import org.jetbrains.compose.test.utils.checkExists +import org.jetbrains.compose.test.utils.checkNotExists import org.jetbrains.compose.test.utils.checks import org.jetbrains.compose.test.utils.modify import org.junit.jupiter.api.Assumptions @@ -1026,6 +1027,88 @@ class ResourcesTest : GradlePluginTestBase() { } } + @Test + fun macosExecutableResources() { + Assumptions.assumeTrue(currentOS == OS.MacOS) + with(testProject("misc/macosNativeResources")) { + val appName = "Test Resources" + gradle(":createDistributableNativeDebugMacosX64").checks { + val targetResourcesDir = "build/compose/binaries/main/native-macosX64-debug-app-image/${appName}.app/Contents/Resources" + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/compose-multiplatform.xml").checkExists() + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/icon.xml").checkExists() + } + } + } + + @Test + fun macosExecutableResourcesWithResourceChanged() { + Assumptions.assumeTrue(currentOS == OS.MacOS) + with(testProject("misc/macosNativeResources")) { + val appName = "Test Resources" + val taskName = ":createDistributableNativeDebugMacosX64" + val comment = "" + val fileNames = listOf( + "compose-multiplatform.xml", + "icon.xml" + ) + val targetResourcesDir = "build/compose/binaries/main/native-macosX64-debug-app-image/${appName}.app/Contents/Resources/compose-resources/composeResources/appleresources.generated.resources/drawable/" + gradle(taskName).checks { + fileNames.forEach { name -> + check(!file(targetResourcesDir + name).readText().startsWith(comment)) { + "The resources file contains the test content before change" + } + } + } + + listOf( + "src/commonMain/composeResources/drawable/compose-multiplatform.xml", + "src/macosMain/composeResources/drawable/icon.xml" + ).forEach { path -> + file(path).modify { + comment + it + } + } + gradle(taskName).checks { + check.taskSuccessful(taskName) + fileNames.forEach { name -> + check(file(targetResourcesDir + name).readText().startsWith(comment)) { + "The resources file does not contain the test content after changed" + } + } + } + } + } + + @Test + fun macosExecutableResourcesWithResourceDeleted() { + Assumptions.assumeTrue(currentOS == OS.MacOS) + with(testProject("misc/macosNativeResources")) { + val appName = "Test Resources" + val taskName = ":createDistributableNativeDebugMacosX64" + + val targetResource = "src/commonMain/composeResources/drawable/compose-multiplatform2.xml" + file(targetResource).apply { + check(createNewFile()) + writeText(file(targetResource.replace("compose-multiplatform2", "compose-multiplatform")).readText()) + } + + gradle(taskName).checks { + val targetResourcesDir = "build/compose/binaries/main/native-macosX64-debug-app-image/${appName}.app/Contents/Resources" + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/compose-multiplatform.xml").checkExists() + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/compose-multiplatform2.xml").checkExists() + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/icon.xml").checkExists() + } + check(file(targetResource).delete()) + gradle(taskName).checks { + check.taskSuccessful(taskName) + val targetResourcesDir = "build/compose/binaries/main/native-macosX64-debug-app-image/${appName}.app/Contents/Resources" + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/compose-multiplatform.xml").checkExists() + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/compose-multiplatform2.xml").checkNotExists() + file("$targetResourcesDir/compose-resources/composeResources/appleresources.generated.resources/drawable/icon.xml").checkExists() + } + } + } + @Test fun iosTestResources() { Assumptions.assumeTrue(currentOS == OS.MacOS) diff --git a/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/build.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/build.gradle.kts new file mode 100644 index 00000000000..1eee194b905 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/build.gradle.kts @@ -0,0 +1,58 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + kotlin("multiplatform") + kotlin("plugin.compose") + id("org.jetbrains.compose") +} + +kotlin { + + listOf( + macosX64(), + macosArm64(), + ).forEach { + it.binaries { + executable { + entryPoint = "main" + freeCompilerArgs += listOf( + "-linker-options", + "-framework", + "-linker-option", + "Metal", + "-Xdisable-phases=VerifyBitcode" + ) + } + } + } + + sourceSets { + commonMain { + dependencies { + implementation(compose.runtime) + implementation(compose.material) + implementation(compose.components.resources) + } + } + } +} + +compose.desktop { + nativeApplication { + targets( + targets = kotlin.targets.filter { + it.platformType == KotlinPlatformType.native && + it.name.contains("macos") + }.toTypedArray() + ) + distributions { + macOS { + targetFormats(TargetFormat.Dmg) + packageName = "Test Resources" + packageVersion = "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/gradle.properties b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/gradle.properties new file mode 100644 index 00000000000..dc1dcfb61b8 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8096M +org.jetbrains.compose.experimental.macos.enabled=true \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/settings.gradle.kts b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/settings.gradle.kts new file mode 100644 index 00000000000..a9407f5c395 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/settings.gradle.kts @@ -0,0 +1,26 @@ +rootProject.name = "appleResources" +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + google() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + maven("https://packages.jetbrains.team/maven/p/kt/dev") + } + plugins { + id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.kotlin.plugin.compose").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.kotlin.native.cocoapods").version("KOTLIN_VERSION_PLACEHOLDER") + id("org.jetbrains.compose").version("COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER") + } +} +dependencyResolutionManagement { + repositories { + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + maven("https://packages.jetbrains.team/maven/p/kt/dev") + mavenCentral() + gradlePluginPortal() + google() + mavenLocal() + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/commonMain/composeResources/drawable/compose-multiplatform.xml new file mode 100644 index 00000000000..d7bf7955f44 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/commonMain/composeResources/drawable/compose-multiplatform.xml @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/commonMain/kotlin/App.kt b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/commonMain/kotlin/App.kt new file mode 100644 index 00000000000..504f3cf5137 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/commonMain/kotlin/App.kt @@ -0,0 +1,37 @@ +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.jetbrains.compose.resources.painterResource +import appleresources.generated.resources.* + +@Composable +fun App() { + MaterialTheme { + var greetingText by remember { mutableStateOf("Hello, World!") } + var showImage by remember { mutableStateOf(false) } + Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Button(onClick = { + showImage = !showImage + }) { + Text(greetingText) + } + AnimatedVisibility(showImage) { + Image( + painterResource(Res.drawable.compose_multiplatform), + null + ) + } + } + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/macosMain/composeResources/drawable/icon.xml b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/macosMain/composeResources/drawable/icon.xml new file mode 100644 index 00000000000..d7bf7955f44 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/macosMain/composeResources/drawable/icon.xml @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/macosMain/kotlin/main.kt b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/macosMain/kotlin/main.kt new file mode 100644 index 00000000000..46f2e8b1c56 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/misc/macosNativeResources/src/macosMain/kotlin/main.kt @@ -0,0 +1,2 @@ + +fun main() {} \ No newline at end of file