Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pack all resources to assets on the android target. #4965

Merged
merged 13 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

terrakok marked this conversation as resolved.
Show resolved Hide resolved
subprojects {
version = findProperty("deploy.version") ?: property("compose.version")!!
version = findProperty("deploy.version")!!

plugins.withId("java") {
configureIfExists<JavaPluginExtension> {
Expand Down
3 changes: 1 addition & 2 deletions components/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ android.useAndroidX=true

#Versions
kotlin.version=1.9.23
compose.version=1.6.10-beta02
agp.version=8.2.2
deploy.version=0.1.0-SNAPSHOT

#Compose
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.wasm.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
compose.desktop.verbose=true
compose.useMavenLocal=false
Expand Down
8 changes: 1 addition & 7 deletions components/resources/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ plugins {
id("org.jetbrains.kotlinx.binary-compatibility-validator")
}

val composeVersion = extra["compose.version"] as String

kotlin {
jvm("desktop")
androidTarget {
Expand Down Expand Up @@ -187,6 +185,7 @@ android {
assets.srcDir("src/androidInstrumentedTest/assets")
}
named("test") { resources.srcDir(commonTestResources) }
named("main") { manifest.srcFile("src/androidMain/AndroidManifest.xml") }
}
}

Expand All @@ -202,11 +201,6 @@ apiValidation {
nonPublicMarkers.add("org.jetbrains.compose.resources.InternalResourceApi")
}

// adding it here to make sure skiko is unpacked and available in web tests
compose.experimental {
web.application {}
}

//utility task to generate CLDRPluralRuleLists.kt file by 'CLDRPluralRules/plurals.xml'
tasks.register<GeneratePluralRuleListsTask>("generatePluralRuleLists") {
val projectDir = project.layout.projectDirectory
Expand Down
13 changes: 13 additions & 0 deletions components/resources/library/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<provider
android:authorities="${applicationId}.resources.AndroidContextProvider"
android:name="org.jetbrains.compose.resources.AndroidContextProvider"
android:exported="false"
android:enabled="true">
</provider>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.jetbrains.compose.resources

import android.annotation.SuppressLint
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri

internal val androidContext get() = AndroidContextProvider.ANDROID_CONTEXT

//https://andretietz.com/2017/09/06/autoinitialise-android-library/
internal class AndroidContextProvider : ContentProvider() {
terrakok marked this conversation as resolved.
Show resolved Hide resolved
companion object {
@SuppressLint("StaticFieldLeak")
lateinit var ANDROID_CONTEXT: Context
private set
}

override fun onCreate(): Boolean {
ANDROID_CONTEXT = context ?: error("AndroidContextProvider context is null")
return true
}

override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? = null
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int = 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ import androidx.compose.ui.text.font.*
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val environment = LocalComposeEnvironment.current.rememberEnvironment()
val path = remember(environment, resource) { resource.getResourceItemByEnvironment(environment).path }
return Font(path, LocalContext.current.assets, weight, style)
val assets = LocalContext.current.assets
return Font(path, assets, weight, style)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.jetbrains.compose.resources

import java.io.File
import android.content.res.AssetManager
import android.net.Uri
import java.io.FileNotFoundException
import java.io.InputStream

internal actual fun getPlatformResourceReader(): ResourceReader = object : ResourceReader {
private val assets: AssetManager = androidContext.assets

override suspend fun read(path: String): ByteArray {
val resource = getResourceAsStream(path)
return resource.readBytes()
Expand Down Expand Up @@ -40,32 +44,39 @@ internal actual fun getPlatformResourceReader(): ResourceReader = object : Resou
}

override fun getUri(path: String): String {
val classLoader = getClassLoader()
val resource = classLoader.getResource(path) ?: run {
//try to find a font in the android assets
if (File(path).isFontResource()) {
classLoader.getResource("assets/$path")
} else null
} ?: throw MissingResourceException(path)
return resource.toURI().toString()
val uri = if (assets.hasFile(path)) {
Uri.parse("file:///android_asset/$path")
} else {
val classLoader = getClassLoader()
val resource = classLoader.getResource(path) ?: throw MissingResourceException(path)
resource.toURI()
}
return uri.toString()
}

private fun getResourceAsStream(path: String): InputStream {
val classLoader = getClassLoader()
val resource = classLoader.getResourceAsStream(path) ?: run {
//try to find a font in the android assets
if (File(path).isFontResource()) {
classLoader.getResourceAsStream("assets/$path")
} else null
} ?: throw MissingResourceException(path)
return resource
}

private fun File.isFontResource(): Boolean {
return this.parentFile?.name.orEmpty().startsWith("font")
return try {
assets.open(path)
} catch (e: FileNotFoundException) {
val classLoader = getClassLoader()
classLoader.getResourceAsStream(path) ?: throw MissingResourceException(path)
}
}

private fun getClassLoader(): ClassLoader {
return this.javaClass.classLoader ?: error("Cannot find class loader")
}

private fun AssetManager.hasFile(path: String): Boolean {
var inputStream: InputStream? = null
val result = try {
inputStream = open(path)
true
} catch (e: FileNotFoundException) {
false
} finally {
inputStream?.close()
}
return result
}
}
4 changes: 3 additions & 1 deletion components/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ pluginManagement {
plugins {
kotlin("jvm").version(extra["kotlin.version"] as String)
kotlin("multiplatform").version(extra["kotlin.version"] as String)
id("org.jetbrains.compose").version(extra["compose.version"] as String)
id("org.jetbrains.compose") //version is not required because the plugin is included to the build
id("com.android.library").version(extra["agp.version"] as String)
id("org.jetbrains.kotlinx.binary-compatibility-validator").version("0.15.0-Beta.2")
}

includeBuild("../gradle-plugins")
}

dependencyResolutionManagement {
Expand Down
2 changes: 0 additions & 2 deletions components/ui-tooling-preview/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ plugins {
id("com.android.library")
}

val composeVersion = extra["compose.version"] as String

kotlin {
jvm("desktop")
androidTarget {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.jetbrains.compose.resources

import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
import com.android.build.gradle.internal.lint.LintModelWriterTask
import org.gradle.api.DefaultTask
Expand All @@ -10,57 +9,31 @@ import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.api.tasks.IgnoreEmptyDirectories
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.jetbrains.compose.internal.utils.registerTask
import org.jetbrains.compose.internal.utils.uppercaseFirstChar
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfoOrNull
import org.jetbrains.kotlin.gradle.utils.ObservableSet
import javax.inject.Inject

@OptIn(ExperimentalKotlinGradlePluginApi::class)
internal fun Project.configureAndroidComposeResources(
kotlinExtension: KotlinMultiplatformExtension,
androidExtension: BaseExtension
kotlinExtension: KotlinMultiplatformExtension
) {
// 1) get the Kotlin Android Target Compilation -> [A]
// 2) get default source set name for the 'A'
// 3) find the associated Android SourceSet in the AndroidExtension -> [B]
// 4) get all source sets in the 'A' and add its resources to the 'B'
kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).all { androidTarget ->
androidTarget.compilations.all { compilation: KotlinJvmAndroidCompilation ->
compilation.defaultSourceSet.androidSourceSetInfoOrNull?.let { kotlinAndroidSourceSet ->
androidExtension.sourceSets
.matching { it.name == kotlinAndroidSourceSet.androidSourceSetName }
.all { androidSourceSet ->
(compilation.allKotlinSourceSets as? ObservableSet<KotlinSourceSet>)?.forAll { kotlinSourceSet ->
val preparedComposeResources = getPreparedComposeResourcesDir(kotlinSourceSet)
androidSourceSet.resources.srcDirs(preparedComposeResources)
igordmn marked this conversation as resolved.
Show resolved Hide resolved

//fix for AGP < 8.0
//usually 'androidSourceSet.resources.srcDir(preparedCommonResources)' should be enough
compilation.androidVariant.processJavaResourcesProvider.configure {
it.dependsOn(preparedComposeResources)
}
}
}
}
}
}

//copy fonts from the compose resources dir to android assets
//copy all compose resources to android assets
val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return
androidComponents.onVariants { variant ->
val variantResources = project.files()

kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).all { androidTarget ->
androidTarget.compilations.all { compilation: KotlinJvmAndroidCompilation ->
if (compilation.androidVariant.name == variant.name) {
project.logger.info("Configure fonts for variant ${variant.name}")
project.logger.info("Configure resources for variant ${variant.name}")
(compilation.allKotlinSourceSets as? ObservableSet<KotlinSourceSet>)?.forAll { kotlinSourceSet ->
val preparedComposeResources = getPreparedComposeResourcesDir(kotlinSourceSet)
variantResources.from(preparedComposeResources)
Expand All @@ -69,22 +42,20 @@ internal fun Project.configureAndroidComposeResources(
}
}

val copyFonts = registerTask<CopyAndroidFontsToAssetsTask>(
"copy${variant.name.uppercaseFirstChar()}FontsToAndroidAssets"
val copyResources = registerTask<CopyResourcesToAndroidAssetsTask>(
"copy${variant.name.uppercaseFirstChar()}ResourcesToAndroidAssets"
) {
from.set(variantResources)
}
variant.sources?.assets?.addGeneratedSourceDirectory(
taskProvider = copyFonts,
wiredWith = CopyAndroidFontsToAssetsTask::outputDirectory
taskProvider = copyResources,
wiredWith = CopyResourcesToAndroidAssetsTask::outputDirectory
)
//exclude a duplication of fonts in apks
variant.packaging.resources.excludes.add("**/font*/*")
}
}

//Copy task doesn't work with 'variant.sources?.assets?.addGeneratedSourceDirectory' API
internal abstract class CopyAndroidFontsToAssetsTask : DefaultTask() {
internal abstract class CopyResourcesToAndroidAssetsTask : DefaultTask() {
@get:Inject
abstract val fileSystem: FileSystemOperations

Expand All @@ -100,7 +71,6 @@ internal abstract class CopyAndroidFontsToAssetsTask : DefaultTask() {
fileSystem.copy {
it.includeEmptyDirs = false
it.from(from)
it.include("**/font*/*")
it.into(outputDirectory)
}
}
Expand Down
Loading
Loading