Add an IT for publication of multiplatform resources in Android target

^KT-65540
This commit is contained in:
Timofey Solonin
2024-02-15 19:05:01 +01:00
committed by Space Team
parent a2989a3711
commit bba39dd4fe
38 changed files with 492 additions and 48 deletions
@@ -46,6 +46,18 @@ import java.util.zip.GZIPInputStream
// Set this to true if you want to dump all bytecode (test will fail in this case)
private val DUMP_ALL = System.getProperty("comparison.dump.all") == "true"
fun assertEqualDirectoriesIgnoringDotFiles(
expected: File,
actual: File,
forgiveOtherExtraFiles: Boolean,
) = assertEqualDirectories(
expected,
actual,
forgiveExtraFiles = forgiveOtherExtraFiles,
// e.g. ignore .DS_Store on macOS
filter = { !it.name.startsWith(".") },
)
fun assertEqualDirectories(
expected: File,
actual: File,
@@ -0,0 +1,110 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.mpp.resources
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectoriesIgnoringDotFiles
import org.junit.jupiter.api.DisplayName
import java.nio.file.Path
@MppGradlePluginTests
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_73)
@DisplayName("Test multiplatform resources consumption and publication in androidTarget()")
class AndroidMultiplatformResourcesIT : KGPBaseTest() {
@DisplayName("Multiplatform resources consumption from self, published and project dependencies for Android target")
@GradleAndroidTest
fun testConsumption(
gradleVersion: GradleVersion,
androidVersion: String,
providedJdk: JdkVersions.ProvidedJdk,
) {
publishDependencyProject(
gradleVersion,
providedJdk,
androidVersion
) { repositoryRoot ->
project(
"multiplatformResources/android",
gradleVersion,
buildJdk = providedJdk.location,
localRepoDir = repositoryRoot,
) {
settingsGradleKts.append(
"""
include(":consumer")
include(":projectDependency")
""".trimIndent()
)
buildWithAGPVersion(
":consumer:zipApksForDebug",
androidVersion,
defaultBuildOptions,
)
val apkPath = projectPath.resolve("consumer/build/outputs/apk/debug/consumer-debug.apk")
assertAssetsMatchReference(apkPath)
assertEmbeddedResourcesMatchReference(apkPath)
}
}
}
private fun TestProject.assertAssetsMatchReference(
apkPath: Path,
) {
val assetsPath = projectPath.resolve("assetsInApk")
unzip(
apkPath,
outputDir = assetsPath,
filesStartingWith = "assets",
)
val referenceAssets = projectPath.resolve("reference/assets").toFile()
assertEqualDirectoriesIgnoringDotFiles(
expected = referenceAssets,
actual = assetsPath.toFile(),
forgiveOtherExtraFiles = false,
)
}
private fun TestProject.assertEmbeddedResourcesMatchReference(
apkPath: Path,
) {
val resourcesPath = projectPath.resolve("resourcesInApk")
unzip(
apkPath,
outputDir = resourcesPath,
filesStartingWith = "embed",
)
val referenceResources = projectPath.resolve("reference/resources").toFile()
assertEqualDirectoriesIgnoringDotFiles(
expected = referenceResources,
actual = resourcesPath.toFile(),
forgiveOtherExtraFiles = false,
)
}
private fun publishDependencyProject(
gradleVersion: GradleVersion,
providedJdk: JdkVersions.ProvidedJdk,
androidVersion: String,
withRepositoryRoot: (Path) -> (Unit),
) {
project(
"multiplatformResources/publication",
gradleVersion,
buildJdk = providedJdk.location,
) {
buildWithAGPVersion(
":publishAllPublicationsToMavenRepository",
androidVersion,
defaultBuildOptions,
)
withRepositoryRoot(projectPath.resolve("build/repo"))
}
}
}
@@ -5,16 +5,61 @@ import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.capitalize
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectories
import org.jetbrains.kotlin.incremental.testingUtils.assertEqualDirectoriesIgnoringDotFiles
import org.junit.jupiter.api.DisplayName
import java.nio.file.Path
import java.util.zip.ZipFile
import kotlin.io.path.name
@MppGradlePluginTests
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_73)
@DisplayName("Test multiplatform resources publication")
class MultiplatformResourcesPublicationIT : KGPBaseTest() {
@DisplayName("Multiplatform resources publication for Android target with release build type")
@GradleAndroidTest
fun testAndroidReleaseResourcesPublication(
gradleVersion: GradleVersion,
androidVersion: String,
providedJdk: JdkVersions.ProvidedJdk,
) {
project(
"multiplatformResources/publication",
gradleVersion,
buildJdk = providedJdk.location,
) {
buildWithAGPVersion(
":publishAndroidReleasePublicationToMavenRepository",
androidVersion,
defaultBuildOptions,
)
val publishedAarPath = "build/repo/test/publication-android/1.0/publication-android-1.0.aar"
val classesInAar = projectPath.resolve("classesInAar")
val classesJar = "classes.jar"
unzip(
projectPath.resolve(publishedAarPath),
classesInAar,
filesStartingWith = classesJar
)
compareEmbeddedResources(
inputZip = classesInAar.resolve(classesJar),
reference = reference("androidMain")
)
val assetsInAar = projectPath.resolve("assetsInAar")
unzip(
projectPath.resolve(publishedAarPath),
assetsInAar,
filesStartingWith = "assets"
)
assertEqualDirectoriesIgnoringDotFiles(
assetsInAar.toFile(),
reference("androidFonts").toFile(),
forgiveOtherExtraFiles = false,
)
}
}
@DisplayName("Multiplatform resources publication for jvm target")
@GradleAndroidTest
fun testJvmResourcesPublication(
@@ -28,7 +73,7 @@ class MultiplatformResourcesPublicationIT : KGPBaseTest() {
providedJdk,
publicationTask = ":publishJvmPublicationToMavenRepository",
publishedArchive = "build/repo/test/publication-jvm/1.0/publication-jvm-1.0.jar",
reference = "jvm",
referenceName = "jvm",
)
}
@@ -38,7 +83,7 @@ class MultiplatformResourcesPublicationIT : KGPBaseTest() {
providedJdk: JdkVersions.ProvidedJdk,
publicationTask: String,
publishedArchive: String,
reference: String,
referenceName: String,
) {
project(
"multiplatformResources/publication",
@@ -48,61 +93,36 @@ class MultiplatformResourcesPublicationIT : KGPBaseTest() {
buildWithAGPVersion(
publicationTask,
androidVersion,
defaultBuildOptions,
)
compareEmbeddedResources(
publishedArchive,
reference
projectPath.resolve(publishedArchive),
reference(referenceName)
)
}
}
private fun TestProject.buildWithAGPVersion(
task: String,
androidVersion: String,
) {
build(
task,
buildOptions = defaultBuildOptions.copy(androidVersion = androidVersion)
)
}
private fun TestProject.reference(
named: String
): Path = projectPath.resolve("reference/$named")
private fun TestProject.compareEmbeddedResources(
inputZip: String,
reference: String,
inputZip: Path,
reference: Path,
) {
val publishedResources = projectPath.resolve("published/${reference}")
val publishedResources = projectPath.resolve("published/${reference.name}")
unzip(
inputZip = inputZip,
outputDir = publishedResources,
filesStartingWith = "embed",
)
val referenceResources = projectPath.resolve("reference/$reference")
assertDirectoryExists(publishedResources)
assertDirectoryExists(referenceResources)
assertEqualDirectories(
assertDirectoryExists(reference)
assertEqualDirectoriesIgnoringDotFiles(
publishedResources.toFile(),
referenceResources.toFile(),
forgiveExtraFiles = false,
filter = { !it.name.startsWith(".") }
reference.toFile(),
forgiveOtherExtraFiles = false,
)
}
private fun TestProject.unzip(
inputZip: String,
outputDir: Path,
filesStartingWith: String,
) {
ZipFile(projectPath.resolve(inputZip).toFile()).use {
it.entries().asSequence().filter { it.name.startsWith(filesStartingWith) && !it.isDirectory }.forEach { entry ->
val outputFile = outputDir.resolve(Paths.get(entry.name))
if (!outputFile.parent.toFile().exists())
Files.createDirectories(outputFile.parent)
it.getInputStream(entry).use { input ->
Files.copy(input, outputFile, StandardCopyOption.REPLACE_EXISTING)
}
}
}
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.mpp.resources
import org.jetbrains.kotlin.gradle.testbase.BuildOptions
import org.jetbrains.kotlin.gradle.testbase.TestProject
import org.jetbrains.kotlin.gradle.testbase.build
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.zip.ZipFile
fun TestProject.buildWithAGPVersion(
task: String,
androidVersion: String,
defaultBuildOptions: BuildOptions,
) {
build(
task,
buildOptions = defaultBuildOptions.copy(androidVersion = androidVersion)
)
}
fun unzip(
inputZip: Path,
outputDir: Path,
filesStartingWith: String,
) {
ZipFile(inputZip.toFile()).use {
it.entries().asSequence().filter { it.name.startsWith(filesStartingWith) && !it.isDirectory }.forEach { entry ->
val outputFile = outputDir.resolve(Paths.get(entry.name))
if (!outputFile.parent.toFile().exists())
Files.createDirectories(outputFile.parent)
it.getInputStream(entry).use { input ->
Files.copy(input, outputFile, StandardCopyOption.REPLACE_EXISTING)
}
}
}
}
@@ -0,0 +1,4 @@
plugins {
kotlin("multiplatform") apply false
id("com.android.application") apply false
}
@@ -0,0 +1,85 @@
@file:OptIn(ComposeKotlinGradlePluginApi::class)
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.ComposeKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublication
import java.io.File
group = "test"
version = "1.0"
plugins {
kotlin("multiplatform")
id("com.android.application")
}
repositories {
google()
mavenCentral()
mavenLocal()
}
kotlin {
sourceSets {
commonMain {
dependencies {
api(project(":projectDependency"))
api("test:publication:+")
}
}
}
val publication = project.ext.get(
KotlinTargetResourcesPublication.EXTENSION_NAME
) as KotlinTargetResourcesPublication
listOf(
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
}
).forEach { target ->
val fontsFilter = if (target is KotlinAndroidTarget) listOf("fonts/*") else emptyList()
val relativeResourcePlacement = provider { File("embed/self") }
val sourceSetPathProvider: (KotlinSourceSet) -> (Provider<File>) = { sourceSet ->
project.provider { project.file("src/${sourceSet.name}/multiplatformResources") }
}
publication.publishResourcesAsKotlinComponent(
target = target,
resourcePathForSourceSet = { sourceSet ->
KotlinTargetResourcesPublication.ResourceRoot(
resourcesBaseDirectory = sourceSetPathProvider(sourceSet),
includes = emptyList(),
excludes = fontsFilter,
)
},
relativeResourcePlacement = relativeResourcePlacement,
)
if (target is KotlinAndroidTarget) {
publication.publishInAndroidAssets(
target = target,
resourcePathForSourceSet = { sourceSet ->
KotlinTargetResourcesPublication.ResourceRoot(
resourcesBaseDirectory = sourceSetPathProvider(sourceSet),
includes = fontsFilter,
excludes = emptyList(),
)
},
relativeResourcePlacement = relativeResourcePlacement,
)
}
}
}
android {
namespace = "test.consumer"
compileSdk = 34
defaultConfig {
minSdk = 24
}
}
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="false"
android:supportsRtl="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,84 @@
@file:OptIn(ComposeKotlinGradlePluginApi::class)
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.ComposeKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublication
import java.io.File
group = "test"
version = "1.0"
plugins {
kotlin("multiplatform")
id("com.android.library")
`maven-publish`
}
repositories {
google()
mavenCentral()
mavenLocal()
}
kotlin {
val publication = project.ext.get(
KotlinTargetResourcesPublication.EXTENSION_NAME
) as KotlinTargetResourcesPublication
listOf(
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
},
jvm(),
).forEach { target ->
val fontsFilter = if (target is KotlinAndroidTarget) listOf("fonts/*") else emptyList()
val relativeResourcePlacement = provider { File("embed/subproject") }
val sourceSetPathProvider: (KotlinSourceSet) -> (Provider<File>) = { sourceSet ->
project.provider { project.file("src/${sourceSet.name}/multiplatformResources") }
}
publication.publishResourcesAsKotlinComponent(
target = target,
resourcePathForSourceSet = { sourceSet ->
KotlinTargetResourcesPublication.ResourceRoot(
resourcesBaseDirectory = sourceSetPathProvider(sourceSet),
includes = emptyList(),
excludes = fontsFilter,
)
},
relativeResourcePlacement = relativeResourcePlacement,
)
if (target is KotlinAndroidTarget) {
publication.publishInAndroidAssets(
target = target,
resourcePathForSourceSet = { sourceSet ->
KotlinTargetResourcesPublication.ResourceRoot(
resourcesBaseDirectory = sourceSetPathProvider(sourceSet),
includes = fontsFilter,
excludes = emptyList(),
)
},
relativeResourcePlacement = relativeResourcePlacement,
)
}
}
}
publishing {
repositories {
maven("${buildDir}/repo")
}
}
android {
namespace = "test.projectDependency"
compileSdk = 34
defaultConfig {
minSdk = 24
}
}
@@ -1,35 +1,71 @@
@file:OptIn(ComposeKotlinGradlePluginApi::class)
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.ComposeKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublication
import java.io.File
group = "test"
version = "1.0"
plugins {
kotlin("multiplatform")
id("com.android.library")
`maven-publish`
}
repositories {
google()
mavenCentral()
mavenLocal()
}
kotlin {
val publication = project.ext.get(
KotlinTargetResourcesPublication.EXTENSION_NAME
) as KotlinTargetResourcesPublication
listOf(
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
},
jvm(),
).forEach { target ->
val fontsFilter = if (target is KotlinAndroidTarget) listOf("fonts/*") else emptyList()
val relativeResourcePlacement = provider { File("embed/published") }
val sourceSetPathProvider: (KotlinSourceSet) -> (Provider<File>) = { sourceSet ->
project.provider { project.file("src/${sourceSet.name}/multiplatformResources") }
}
publication.publishResourcesAsKotlinComponent(
target,
{ sourceSet ->
target = target,
resourcePathForSourceSet = { sourceSet ->
KotlinTargetResourcesPublication.ResourceRoot(
resourcesBaseDirectory = project.provider { project.file("src/${sourceSet.name}/multiplatformResources") },
resourcesBaseDirectory = sourceSetPathProvider(sourceSet),
includes = emptyList(),
excludes = emptyList()
excludes = fontsFilter,
)
},
provider { File("embed/inside") }
relativeResourcePlacement = relativeResourcePlacement,
)
if (target is KotlinAndroidTarget) {
publication.publishInAndroidAssets(
target = target,
resourcePathForSourceSet = { sourceSet ->
KotlinTargetResourcesPublication.ResourceRoot(
resourcesBaseDirectory = sourceSetPathProvider(sourceSet),
includes = fontsFilter,
excludes = emptyList(),
)
},
relativeResourcePlacement = relativeResourcePlacement,
)
}
}
}
@@ -38,3 +74,11 @@ publishing {
maven("${buildDir}/repo")
}
}
android {
namespace = "test.publication"
compileSdk = 34
defaultConfig {
minSdk = 24
}
}