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

#48 nested modules resources #64

Merged
merged 12 commits into from
May 3, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.android.build.gradle.LibraryPlugin
import com.android.build.gradle.api.AndroidSourceSet
import dev.icerock.gradle.generator.AndroidMRGenerator
import dev.icerock.gradle.generator.CommonMRGenerator
import dev.icerock.gradle.generator.GenerateMultiplatformResourcesTask
import dev.icerock.gradle.generator.IosMRGenerator
import dev.icerock.gradle.generator.MRGenerator
import dev.icerock.gradle.generator.ResourceGeneratorFeature
Expand Down Expand Up @@ -79,17 +80,23 @@ class MultiplatformResourcesPlugin : Plugin<Project> {
mrExtension.multiplatformResourcesPackage!!,
androidPackage
)
val iosLocalizationRegion = mrExtension.iosBaseLocalizationRegion
val features = listOf(
StringsGeneratorFeature(sourceInfo, mrExtension.iosBaseLocalizationRegion),
PluralsGeneratorFeature(sourceInfo, mrExtension.iosBaseLocalizationRegion),
StringsGeneratorFeature(sourceInfo, iosLocalizationRegion),
PluralsGeneratorFeature(sourceInfo, iosLocalizationRegion),
ImagesGeneratorFeature(sourceInfo),
FontsGeneratorFeature(sourceInfo)
)
val targets: List<KotlinTarget> = multiplatformExtension.targets.toList()

setupCommonGenerator(commonSourceSet, generatedDir, mrClassPackage, features, target)
setupAndroidGenerator(targets, androidMainSourceSet, generatedDir, mrClassPackage, features, target)
setupIosGenerator(targets, generatedDir, mrClassPackage, features, target)
setupIosGenerator(targets, generatedDir, mrClassPackage, features, target, iosLocalizationRegion)

val generationTasks = target.tasks.filterIsInstance<GenerateMultiplatformResourcesTask>()
val commonGenerationTask = generationTasks.first { it.name == "generateMRcommonMain" }
generationTasks.filter { it != commonGenerationTask }
.forEach { it.dependsOn(commonGenerationTask) }
}

private fun setupCommonGenerator(
Expand Down Expand Up @@ -132,12 +139,14 @@ class MultiplatformResourcesPlugin : Plugin<Project> {
).apply(target)
}

@Suppress("LongParameterList")
private fun setupIosGenerator(
targets: List<KotlinTarget>,
generatedDir: File,
mrClassPackage: String,
features: List<ResourceGeneratorFeature>,
target: Project
target: Project,
iosLocalizationRegion: String
) {
val compilations = targets
.filterIsInstance<KotlinNativeTarget>()
Expand All @@ -158,7 +167,8 @@ class MultiplatformResourcesPlugin : Plugin<Project> {
sourceSet,
mrClassPackage,
generators = features.map { it.createIosGenerator() },
compilation = compilation
compilation = compilation,
baseLocalizationRegion = iosLocalizationRegion
).apply(target)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package dev.icerock.gradle.generator
import com.squareup.kotlinpoet.KModifier
import org.gradle.api.Project
import org.gradle.api.Task
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import java.io.File

class CommonMRGenerator(
Expand All @@ -23,11 +22,6 @@ class CommonMRGenerator(
) {
override fun getMRClassModifiers(): Array<KModifier> = arrayOf(KModifier.EXPECT)

override fun apply(generationTask: Task, project: Project) {
project.tasks.getByName("preBuild").dependsOn(generationTask)

project.tasks
.mapNotNull { it as? KotlinNativeLink }
.forEach { it.compilation.compileKotlinTask.dependsOn(generationTask) }
}
@Suppress("EmptyFunctionBlock")
override fun apply(generationTask: Task, project: Project) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.icerock.gradle.generator

import org.gradle.api.DefaultTask

open class GenerateMultiplatformResourcesTask : DefaultTask() {
init {
group = "multiplatform"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,23 @@ import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.kotlin.dsl.support.unzipTo
import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.w3c.dom.Document
import org.w3c.dom.Node
import org.jetbrains.kotlin.konan.file.zipDirAs
import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl
import java.io.File
import java.io.FileWriter
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import java.util.Properties

class IosMRGenerator(
generatedDir: File,
sourceSet: SourceSet,
mrClassPackage: String,
private val generators: List<Generator>,
private val compilation: AbstractKotlinNativeCompilation
generators: List<Generator>,
private val compilation: AbstractKotlinNativeCompilation,
private val baseLocalizationRegion: String
) : MRGenerator(
generatedDir = generatedDir,
sourceSet = sourceSet,
Expand All @@ -39,6 +36,7 @@ class IosMRGenerator(
) {
private val bundleClassName =
ClassName("platform.Foundation", "NSBundle")
private val bundleIdentifier = "$mrClassPackage.MR"

override fun getMRClassModifiers(): Array<KModifier> = arrayOf(KModifier.ACTUAL)

Expand All @@ -51,20 +49,99 @@ class IosMRGenerator(
bundleClassName,
KModifier.PRIVATE
)
.initializer(CodeBlock.of("NSBundle.bundleForClass(object_getClass(this)!!)"))
.delegate(
CodeBlock.of(
"""
lazy<NSBundle> {
val result = NSBundle.bundleWithIdentifier("$bundleIdentifier")
if (result != null) return@lazy result

val mainPath = NSBundle.mainBundle.bundlePath
val appFrameworks = NSBundle.allFrameworks.filterIsInstance<NSBundle>()
.filter { it.bundlePath.startsWith(mainPath) }
appFrameworks.flatMap { frameworkBundle ->
@Suppress("UNCHECKED_CAST")
frameworkBundle.pathsForResourcesOfType(ext = "bundle", inDirectory = null) as List<String>
}.forEach { bundlePath ->
NSBundle.bundleWithPath(bundlePath)?.bundleIdentifier
}

return@lazy NSBundle.bundleWithIdentifier("$bundleIdentifier")!!
}
Alex009 marked this conversation as resolved.
Show resolved Hide resolved
""".trimIndent()
)
)
.build()
)
}

override fun getImports(): List<ClassName> = listOf(
bundleClassName,
ClassName("platform.objc", "object_getClass")
bundleClassName
)

override fun apply(generationTask: Task, project: Project) {
setupKLibResources(generationTask)
setupFrameworkResources()
}

private fun setupKLibResources(generationTask: Task) {
val compileTask: KotlinNativeCompile = compilation.compileKotlinTask
compileTask.dependsOn(generationTask)

compileTask.doLast {
this as KotlinNativeCompile

val klibFile = this.outputFile.get()
val repackDir = File(klibFile.parent, klibFile.nameWithoutExtension)
val resRepackDir = File(repackDir, "resources")

unzipTo(zipFile = klibFile, outputDirectory = repackDir)

val manifestFile = File(repackDir, "manifest")
val manifest = Properties()
manifest.load(manifestFile.inputStream())

val uniqueName = manifest["unique_name"] as String

val bundleDir = File(resRepackDir, "$uniqueName.bundle")
bundleDir.mkdir()

val bundleContentsDir = File(bundleDir, "Contents")
bundleContentsDir.mkdir()

val bundlePList = File(bundleContentsDir, "Info.plist")
bundlePList.writeText(
"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$baseLocalizationRegion</string>
<key>CFBundleIdentifier</key>
<string>$bundleIdentifier</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
</dict>
</plist>"""
)

val bundleResourcesDir = File(bundleContentsDir, "Resources")
bundleResourcesDir.mkdir()
Alex009 marked this conversation as resolved.
Show resolved Hide resolved

resourcesGenerationDir.copyRecursively(bundleResourcesDir, overwrite = true)

val repackKonan = org.jetbrains.kotlin.konan.file.File(repackDir.path)
val klibKonan = org.jetbrains.kotlin.konan.file.File(klibFile.path)

repackKonan.zipDirAs(klibKonan)

repackDir.deleteRecursively()
}
}

private fun setupFrameworkResources() {
val kotlinNativeTarget = compilation.target as KotlinNativeTarget

val frameworkBinaries: List<Framework> = kotlinNativeTarget.binaries
Expand All @@ -75,42 +152,22 @@ class IosMRGenerator(
val linkTask = framework.linkTask

linkTask.doLast {
resourcesGenerationDir.copyRecursively(framework.outputFile, overwrite = true)

processInfoPlist(framework)
linkTask.libraries
.plus(linkTask.intermediateLibrary.get())
.filter { it.extension == "klib" }
.forEach {
project.logger.info("copy resources from $it")
val klibKonan = org.jetbrains.kotlin.konan.file.File(it.path)
val klib = KotlinLibraryLayoutImpl(klibKonan)
val layout = klib.extractingToTemp

File(layout.resourcesDir.path).copyRecursively(framework.outputFile, overwrite = true)
}
}
}
}

private fun processInfoPlist(framework: Framework) {
val infoPList = File(framework.outputFile, "Info.plist")

val dbFactory = DocumentBuilderFactory.newInstance()
dbFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
val dBuilder = dbFactory.newDocumentBuilder()
val doc = dBuilder.parse(infoPList)

val rootDict = doc.getElementsByTagName("dict").item(0)

generators
.mapNotNull { it as? ExtendsPlistDictionary }
.forEach { it.appendPlistInfo(doc, rootDict) }

val transformerFactory = TransformerFactory.newInstance()
val transformer = transformerFactory.newTransformer()
transformer.setOutputProperty(OutputKeys.INDENT, "yes")

val writer = FileWriter(infoPList)
val result = StreamResult(writer)

transformer.transform(DOMSource(doc), result)
}

companion object {
const val BUNDLE_PROPERTY_NAME = "bundle"
}
}

interface ExtendsPlistDictionary {
fun appendPlistInfo(doc: Document, rootDict: Node)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import java.io.File
abstract class MRGenerator(
generatedDir: File,
protected val sourceSet: SourceSet,
private val mrClassPackage: String,
protected val mrClassPackage: String,
private val generators: List<Generator>
) {
private val sourcesGenerationDir = File(generatedDir, "${sourceSet.name}/src")
Expand Down Expand Up @@ -60,9 +60,7 @@ abstract class MRGenerator(
val genTaskName = "generateMR$name"
val genTask = runCatching {
project.tasks.getByName(genTaskName)
}.getOrNull() ?: project.task(genTaskName) {
group = "multiplatform"

}.getOrNull() ?: project.tasks.create(genTaskName, GenerateMultiplatformResourcesTask::class.java) {
doLast {
this@MRGenerator.generate()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package dev.icerock.gradle.generator.fonts

import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.KModifier
import dev.icerock.gradle.generator.IosMRGenerator
import org.gradle.api.file.FileTree
import java.io.File

Expand All @@ -19,7 +20,10 @@ class IosFontsGenerator(
override fun getPropertyModifiers(): Array<KModifier> = arrayOf(KModifier.ACTUAL)

override fun getPropertyInitializer(fontFileName: String): CodeBlock? {
return CodeBlock.of("FontResource(fontName = %S)", fontFileName)
return CodeBlock.of(
"FontResource(fontName = %S, bundle = ${IosMRGenerator.BUNDLE_PROPERTY_NAME})",
fontFileName
)
}

override fun generateResources(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@ package dev.icerock.gradle.generator.strings

import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.KModifier
import dev.icerock.gradle.generator.ExtendsPlistDictionary
import dev.icerock.gradle.generator.IosMRGenerator.Companion.BUNDLE_PROPERTY_NAME
import org.gradle.api.file.FileTree
import org.w3c.dom.Document
import org.w3c.dom.Node
import java.io.File

class IosStringsGenerator(
stringsFileTree: FileTree,
private val baseLocalizationRegion: String
) : StringsGenerator(
stringsFileTree = stringsFileTree
), ExtendsPlistDictionary {
) {
override fun getClassModifiers(): Array<KModifier> = arrayOf(KModifier.ACTUAL)

override fun getPropertyModifiers(): Array<KModifier> = arrayOf(KModifier.ACTUAL)
Expand Down Expand Up @@ -53,13 +50,4 @@ class IosStringsGenerator(
regionFile.writeText(content)
}
}

override fun appendPlistInfo(doc: Document, rootDict: Node) {
rootDict.appendChild(doc.createElement("key").apply {
textContent = "CFBundleDevelopmentRegion"
})
rootDict.appendChild(doc.createElement("string").apply {
textContent = baseLocalizationRegion
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ actual sealed class StringDesc {
abstract fun toString(context: Context): String

actual sealed class LocaleType {
actual class System actual constructor() :
LocaleType() {
actual object System : LocaleType() {
override val systemLocale: Locale? = null
}

Expand Down Expand Up @@ -131,6 +130,6 @@ actual sealed class StringDesc {
return localizedContext(context).resources
}

actual var localeType: LocaleType = LocaleType.System()
actual var localeType: LocaleType = LocaleType.System
}
}
Loading