Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ build/
.externalNativeBuild
.cxx
.kotlin
/experimental/build_from_cli/

# Ignoring the persistant lockfile until kotlin.js vulnerabilities are fixed
# yarn.lock # but absent yarn.lock leads to inconsistent builds on CI, so let's keep it
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Image(
modifier = Modifier.size(100.dp),
painter = painterResource(Res.drawable.compose),
painter = painterResource(Res.drawable.subdir.compose),
contentDescription = null
)
Text(
"""
Image(
painter = painterResource(Res.drawable.compose)
painter = painterResource(Res.drawable.subdir.compose)
)
""".trimIndent()
)
Expand Down Expand Up @@ -107,7 +107,7 @@ fun ImagesRes(contentPadding: PaddingValues) {
) {
Icon(
modifier = Modifier.size(100.dp),
painter = painterResource(Res.drawable.compose),
painter = painterResource(Res.drawable.subdir.compose),
contentDescription = null
)
Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.jetbrains.compose.internal.utils

import org.gradle.api.DomainObjectCollection
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.logging.Logger
Expand Down Expand Up @@ -72,3 +73,6 @@ internal inline fun <reified SubT> DomainObjectCollection<*>.configureEachWithTy
}
}
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun gradleError(message: String): Nothing = throw GradleException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ internal fun Project.configureComposeResourcesGeneration(
val packagingDir = config.getModuleResourcesDir(project)

kotlinExtension.sourceSets.all { sourceSet ->

//common resources must be converted (XML -> CVR)
val preparedResourcesTask = registerPrepareComposeResourcesTask(sourceSet, config)
val preparedResources = preparedResourcesTask.flatMap { it.outputDir.asFile }

if (sourceSet.name == resClassSourceSetName) {
configureResClassGeneration(
sourceSet,
Expand All @@ -58,13 +63,11 @@ internal fun Project.configureComposeResourcesGeneration(
resClassName,
makeAccessorsPublic,
packagingDir,
generateModulePath
generateModulePath,
preparedResources
)
}

//common resources must be converted (XML -> CVR)
val preparedResourcesTask = registerPrepareComposeResourcesTask(sourceSet, config)
val preparedResources = preparedResourcesTask.flatMap { it.outputDir.asFile }
configureResourceAccessorsGeneration(
sourceSet,
preparedResources,
Expand Down Expand Up @@ -100,7 +103,8 @@ private fun Project.configureResClassGeneration(
resClassName: Provider<String>,
makeAccessorsPublic: Provider<Boolean>,
packagingDir: Provider<File>,
generateModulePath: Boolean
generateModulePath: Boolean,
preparedResources: Provider<File>
) {
logger.info("Configure Res object generation for ${resClassSourceSet.name}")

Expand All @@ -112,6 +116,7 @@ private fun Project.configureResClassGeneration(
task.resClassName.set(resClassName)
task.makeAccessorsPublic.set(makeAccessorsPublic)
task.codeDir.set(layout.buildDirectory.dir("$RES_GEN_DIR/kotlin/commonResClass"))
task.preparedResources.fileProvider(preparedResources)

if (generateModulePath) {
task.packagingDir.set(packagingDir)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package org.jetbrains.compose.resources

import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileTree
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.jetbrains.compose.internal.IdeaImportTask
import java.io.File
import kotlin.io.path.name

internal abstract class GenerateResClassTask : IdeaImportTask() {
@get:Input
Expand All @@ -22,6 +26,9 @@ internal abstract class GenerateResClassTask : IdeaImportTask() {
@get:Input
abstract val makeAccessorsPublic: Property<Boolean>

@get:InputFiles
abstract val preparedResources: DirectoryProperty

@get:OutputDirectory
abstract val codeDir: DirectoryProperty

Expand All @@ -36,6 +43,41 @@ internal abstract class GenerateResClassTask : IdeaImportTask() {
val pkgName = packageName.get()
val moduleDirectory = packagingDir.getOrNull()?.let { it.invariantSeparatorsPath + "/" } ?: ""
val isPublic = makeAccessorsPublic.get()
getResFileSpec(pkgName, resClassName, moduleDirectory, isPublic).writeTo(dir)
val resourceDirectories = getAllDirectories(preparedResources.asFileTree)
val rootDir = preparedResources.get().asFile
val typeToSubdirPaths = collectSubDirPathsForResourceTypes(resourceDirectories, rootDir)
getResFileSpec(pkgName, resClassName, moduleDirectory, isPublic, typeToSubdirPaths).writeTo(dir)
}

private fun getAllDirectories(fileTree: FileTree): Set<File> {
val dirs = mutableSetOf<File>()
fileTree.visit { fileDetails ->
if (fileDetails.isDirectory) {
dirs.add(fileDetails.file)
}
}
return dirs
}

// For each top-level resource type, return all subdirectories as a split
// relative path from their resource directory.
// E.g. `/drawable/sub/sub2` is returned as `["sub", "sub2"]`.
private fun collectSubDirPathsForResourceTypes(
dirs: Set<File>,
root: File
): Map<ResourceType, Set<List<String>>> {
val result = mutableMapOf<ResourceType, MutableSet<List<String>>>()
dirs.forEach { dir ->
val relativePath = dir.relativeTo(root).toPath()
val typeName = relativePath.getName(0).name.split("-").first()
val type = ResourceType.fromString(typeName) ?: return@forEach // Ignore any resource folders we cannot create accessors for
if (relativePath.nameCount > 1) {
val subDirPath = relativePath.subpath(1, relativePath.nameCount).map { it.toString() }
result.getOrPut(type) { mutableSetOf() }.add(subDirPath)
} else {
result.getOrPut(type) { mutableSetOf() }
}
}
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SkipWhenEmpty
import org.jetbrains.compose.internal.IdeaImportTask
import org.jetbrains.compose.internal.utils.OS
import org.jetbrains.compose.internal.utils.currentOS
import java.io.File
import java.nio.file.Path
import kotlin.io.path.relativeTo

internal abstract class GenerateResourceAccessorsTask : IdeaImportTask() {
@get:Input
Expand Down Expand Up @@ -55,25 +51,7 @@ internal abstract class GenerateResourceAccessorsTask : IdeaImportTask() {

logger.info("Generate accessors for $rootResDir")

//get first level dirs
val dirs = rootResDir.listNotHiddenFiles()

dirs.forEach { f ->
if (!f.isDirectory) {
error("${f.name} is not directory! Raw files should be placed in '${rootResDir.name}/files' directory.")
}
}

//type -> id -> resource item
val resources: Map<ResourceType, Map<String, List<ResourceItem>>> = dirs
.flatMap { dir ->
dir.listNotHiddenFiles()
.mapNotNull { it.fileToResourceItems(rootResDir.toPath()) }
.flatten()
}
.groupBy { it.type }
.mapValues { (_, items) -> items.groupBy { it.name } }

val resources = ResourceHolder(rootResDir)
val pkgName = packageName.get()
val moduleDirectory = packagingDir.getOrNull()?.let { it.invariantSeparatorsPath + "/" } ?: ""
val resClassName = resClassName.get()
Expand All @@ -89,96 +67,6 @@ internal abstract class GenerateResourceAccessorsTask : IdeaImportTask() {
generateResourceContentHashAnnotation
).forEach { it.writeTo(kotlinDir) }
}

private fun File.isTextResourceFile(): Boolean =
path.endsWith(".xml", true) || path.endsWith(".svg", true)

private fun File.resourceContentHash(): Int {
if ((currentOS == OS.Windows) && isTextResourceFile()) {
// Windows has different line endings in comparison with Unixes,
// thus text resource files binary differ there, so we need to handle this.
return readText().replace("\r\n", "\n").toByteArray().contentHashCode()
} else {
return readBytes().contentHashCode()
}
}

private fun File.fileToResourceItems(
relativeTo: Path
): List<ResourceItem>? {
val file = this
val dirName = file.parentFile.name ?: return null
val typeAndQualifiers = dirName.split("-")
if (typeAndQualifiers.isEmpty()) return null

val typeString = typeAndQualifiers.first().lowercase()
val qualifiers = typeAndQualifiers.takeLast(typeAndQualifiers.size - 1)
val path = file.toPath().relativeTo(relativeTo)


if (typeString == "string") {
error("Forbidden directory name '$dirName'! String resources should be declared in 'values/strings.xml'.")
}

if (typeString == "files") {
if (qualifiers.isNotEmpty()) error("The 'files' directory doesn't support qualifiers: '$dirName'.")
return null
}

if (typeString == "values" && file.extension.equals(XmlValuesConverterTask.CONVERTED_RESOURCE_EXT, true)) {
return getValueResourceItems(file, qualifiers, path)
}

val type = ResourceType.fromString(typeString) ?: error("Unknown resource type: '$typeString'.")
return listOf(
ResourceItem(
type,
qualifiers,
file.nameWithoutExtension.asUnderscoredIdentifier(),
path,
file.resourceContentHash()
)
)
}

private fun getValueResourceItems(dataFile: File, qualifiers: List<String>, path: Path): List<ResourceItem> {
val result = mutableListOf<ResourceItem>()
dataFile.bufferedReader().use { f ->
var offset = 0L
var line: String? = f.readLine()
while (line != null) {
val size = line.encodeToByteArray().size

//first line is meta info
if (offset > 0) {
result.add(getValueResourceItem(line, offset, size.toLong(), qualifiers, path))
}

offset += size + 1 // "+1" for newline character
line = f.readLine()
}
}
return result
}

private fun getValueResourceItem(
recordString: String,
offset: Long,
size: Long,
qualifiers: List<String>,
path: Path
): ResourceItem {
val record = ValueResourceRecord.createFromString(recordString)
return ResourceItem(
record.type,
qualifiers,
record.key.asUnderscoredIdentifier(),
path,
record.content.hashCode(),
offset,
size
)
}
}

internal fun File.listNotHiddenFiles(): List<File> =
Expand Down
Loading
Loading