Skip to content

Commit ae7fa6f

Browse files
authored
Merge pull request #108 from icerockdev/develop
Release 0.12.0
2 parents 876daec + 69d39ee commit ae7fa6f

36 files changed

Lines changed: 1049 additions & 40 deletions

.github/workflows/compilation-check.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ jobs:
2222
run: ./gradlew -PlibraryPublish detekt :resources:publishToMavenLocal
2323
- name: Check demo library
2424
run: ./gradlew detekt :sample:mpp-conditional:build :sample:mpp-hierarhical:build :sample:mpp-library:build :sample:mpp-mixed:build
25+
- name: Check tests
26+
run: ./gradlew :sample:mpp-hierarhical:check

.github/workflows/ui-tests-android-sample.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ jobs:
2020
uses: reactivecircus/android-emulator-runner@v2
2121
with:
2222
api-level: 29
23-
script: ./gradlew connectedAndroidTest
23+
script: ./gradlew :sample:android-app:connectedAndroidTest

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ This is a Kotlin MultiPlatform library that provides access to the resources on
4848
- 0.10.1
4949
- 0.11.0
5050
- 0.11.1
51+
- 0.12.0
5152

5253
## Installation
5354
root build.gradle
@@ -58,7 +59,7 @@ buildscript {
5859
}
5960
6061
dependencies {
61-
classpath "dev.icerock.moko:resources-generator:0.11.1"
62+
classpath "dev.icerock.moko:resources-generator:0.12.0"
6263
}
6364
}
6465
@@ -75,7 +76,7 @@ project build.gradle
7576
apply plugin: "dev.icerock.mobile.multiplatform-resources"
7677
7778
dependencies {
78-
commonMainApi("dev.icerock.moko:resources:0.11.1")
79+
commonMainApi("dev.icerock.moko:resources:0.12.0")
7980
}
8081
8182
multiplatformResources {
@@ -95,6 +96,26 @@ ios-app Info.plist:
9596
```
9697
in array should be added all used languages.
9798

99+
### Static kotlin frameworks support
100+
If project configured with static framework output (for example by `org.jetbrains.kotlin.native.cocoapods` plugin)
101+
in Xcode project should be added `Build Phase` (at end of list) with script:
102+
```shell script
103+
"$SRCROOT/../gradlew" -p "$SRCROOT/../" :yourframeworkproject:copyFrameworkResourcesToApp \
104+
-Pmoko.resources.PLATFORM_NAME=$PLATFORM_NAME \
105+
-Pmoko.resources.CONFIGURATION=$CONFIGURATION \
106+
-Pmoko.resources.BUILT_PRODUCTS_DIR=$BUILT_PRODUCTS_DIR \
107+
-Pmoko.resources.CONTENTS_FOLDER_PATH=$CONTENTS_FOLDER_PATH
108+
```
109+
Please replace `:yourframeworkproject` to kotlin project gradle path, and set correct relative path (`$SRCROOT/../` in example).
110+
This phase will copy resources into application, because static frameworks can't have resources.
111+
112+
To disable warnings about static framework in gradle set flag:
113+
```kotlin
114+
multiplatformResources {
115+
disableStaticFrameworkWarning = true
116+
}
117+
```
118+
98119
## Usage
99120
### Example 1 - simple localization string
100121
The first step is a create a file `strings.xml` in `commonMain/resources/MR/base` with the following content:
@@ -335,6 +356,9 @@ Then just launch task:
335356
## Samples
336357
Please see more examples in the [sample directory](sample).
337358

359+
Sample `mpp-hierarhical` contains usage of `org.jetbrains.kotlin.native.cocoapods` plugin and unit
360+
tests with resources usage.
361+
338362
## Set Up Locally
339363
- The [resources directory](resources) contains the `resources` library;
340364
- The [gradle-plugin directory](gradle-plugin) contains a gradle plugin with a `MR` class generator;

buildSrc/src/main/kotlin/Versions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ object Versions {
1212
const val kotlin = "1.3.72"
1313
const val detekt = "1.7.4"
1414

15-
private const val mokoResources = "0.11.1"
15+
private const val mokoResources = "0.12.0"
1616

1717
object Plugins {
1818
const val android = "3.6.2"

gradle-plugin/src/main/kotlin/dev/icerock/gradle/MultiplatformResourcesPlugin.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import com.android.build.gradle.api.AndroidSourceSet
1010
import dev.icerock.gradle.generator.ColorsGenerator
1111
import dev.icerock.gradle.generator.FilesGenerator
1212
import dev.icerock.gradle.generator.FontsGenerator
13-
import dev.icerock.gradle.generator.GenerateMultiplatformResourcesTask
1413
import dev.icerock.gradle.generator.ImagesGenerator
1514
import dev.icerock.gradle.generator.MRGenerator
1615
import dev.icerock.gradle.generator.PluralsGenerator
@@ -20,6 +19,7 @@ import dev.icerock.gradle.generator.StringsGenerator
2019
import dev.icerock.gradle.generator.android.AndroidMRGenerator
2120
import dev.icerock.gradle.generator.common.CommonMRGenerator
2221
import dev.icerock.gradle.generator.ios.IosMRGenerator
22+
import dev.icerock.gradle.tasks.GenerateMultiplatformResourcesTask
2323
import org.gradle.api.Plugin
2424
import org.gradle.api.Project
2525
import org.gradle.api.tasks.SourceSet
@@ -67,7 +67,8 @@ class MultiplatformResourcesPlugin : Plugin<Project> {
6767
multiplatformExtension: KotlinMultiplatformExtension,
6868
androidExtension: LibraryExtension
6969
) {
70-
val androidMainSourceSet = androidExtension.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
70+
val androidMainSourceSet =
71+
androidExtension.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
7172

7273
val commonSourceSet = multiplatformExtension.sourceSets.getByName(mrExtension.sourceSetName)
7374
val commonResources = commonSourceSet.resources
@@ -95,8 +96,15 @@ class MultiplatformResourcesPlugin : Plugin<Project> {
9596
val targets: List<KotlinTarget> = multiplatformExtension.targets.toList()
9697

9798
setupCommonGenerator(commonSourceSet, generatedDir, mrClassPackage, features, target)
98-
setupAndroidGenerator(targets, androidMainSourceSet, generatedDir, mrClassPackage, features, target)
99-
if(HostManager.hostIsMac) {
99+
setupAndroidGenerator(
100+
targets,
101+
androidMainSourceSet,
102+
generatedDir,
103+
mrClassPackage,
104+
features,
105+
target
106+
)
107+
if (HostManager.hostIsMac) {
100108
setupIosGenerator(
101109
targets,
102110
generatedDir,
@@ -146,7 +154,8 @@ class MultiplatformResourcesPlugin : Plugin<Project> {
146154
.filterNot { it.name.endsWith("Test") } // remove tests compilations
147155
.map { it.defaultSourceSet }
148156

149-
val androidSourceSet: MRGenerator.SourceSet = createSourceSet(androidMainSourceSet, kotlinSourceSets)
157+
val androidSourceSet: MRGenerator.SourceSet =
158+
createSourceSet(androidMainSourceSet, kotlinSourceSets)
150159
AndroidMRGenerator(
151160
generatedDir,
152161
androidSourceSet,

gradle-plugin/src/main/kotlin/dev/icerock/gradle/MultiplatformResourcesPluginExtension.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ open class MultiplatformResourcesPluginExtension {
1111
var multiplatformResourcesSourceSet: String? = null
1212
var iosBaseLocalizationRegion: String = "en"
1313
val sourceSetName: String get() = multiplatformResourcesSourceSet ?: KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME
14+
var disableStaticFrameworkWarning = false
1415
}

gradle-plugin/src/main/kotlin/dev/icerock/gradle/generator/ColorsGenerator.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ abstract class ColorsGenerator(
7272

7373
private fun parseColors(): List<ColorNode> {
7474
val colorNodes = mutableListOf<ColorNode>()
75+
val colorValues = mutableMapOf<String, String>()
76+
77+
fun getColor(color: String?): String? {
78+
return if (color?.startsWith(XmlColorReferencePrefix) == true) {
79+
val colorName = color.replace(XmlColorReferencePrefix, "")
80+
colorValues[colorName]
81+
} else {
82+
color
83+
}
84+
}
85+
7586
colorsFileTree.map { file ->
7687
val dbFactory = DocumentBuilderFactory.newInstance()
7788
val dBuilder = dbFactory.newDocumentBuilder()
@@ -96,10 +107,15 @@ abstract class ColorsGenerator(
96107
}
97108
else -> {
98109
singleColor = xmlNode.textContent
110+
singleColor?.let {
111+
if (!it.startsWith(XmlColorReferencePrefix)) {
112+
colorValues[colorName] = it
113+
}
114+
}
99115
}
100116
}
101117
}
102-
colorNodes.add(ColorNode(colorName, lightColor, darkColor, singleColor))
118+
colorNodes.add(ColorNode(colorName, getColor(lightColor), getColor(darkColor), getColor(singleColor)))
103119
}
104120
}
105121

@@ -139,6 +155,7 @@ abstract class ColorsGenerator(
139155
companion object {
140156
internal const val XmlColorTag = "color"
141157
internal const val XmlNodeAttrColorName = "name"
158+
internal const val XmlColorReferencePrefix = "@color/"
142159
}
143160
}
144161

gradle-plugin/src/main/kotlin/dev/icerock/gradle/generator/MRGenerator.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.squareup.kotlinpoet.FileSpec
99
import com.squareup.kotlinpoet.KModifier
1010
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
1111
import com.squareup.kotlinpoet.TypeSpec
12+
import dev.icerock.gradle.tasks.GenerateMultiplatformResourcesTask
1213
import org.gradle.api.Project
1314
import org.gradle.api.Task
1415
import java.io.File
@@ -43,7 +44,8 @@ abstract class MRGenerator(
4344
generators.forEach { generator ->
4445
val builder = TypeSpec.objectBuilder(generator.mrObjectName)
4546

46-
val fileResourceInterfaceClassName = ClassName("dev.icerock.moko.resources", "ResourceContainer")
47+
val fileResourceInterfaceClassName =
48+
ClassName("dev.icerock.moko.resources", "ResourceContainer")
4749
builder.addSuperinterface(fileResourceInterfaceClassName.parameterizedBy(generator.resourceClassName))
4850

4951
mrClassSpec.addType(generator.generate(resourcesGenerationDir, builder))
@@ -72,7 +74,10 @@ abstract class MRGenerator(
7274
val genTaskName = "generateMR$name"
7375
val genTask = runCatching {
7476
project.tasks.getByName(genTaskName)
75-
}.getOrNull() ?: project.tasks.create(genTaskName, GenerateMultiplatformResourcesTask::class.java) {
77+
}.getOrNull() ?: project.tasks.create(
78+
genTaskName,
79+
GenerateMultiplatformResourcesTask::class.java
80+
) {
7681
doLast {
7782
this@MRGenerator.generate()
7883
}

gradle-plugin/src/main/kotlin/dev/icerock/gradle/generator/android/AndroidColorsGenerator.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ class AndroidColorsGenerator(
5454
buildColorString(colorNode.name, replaceColorAlpha(colorNode.darkColor))
5555
}
5656

57-
println("defaultContent = $defaultContent")
58-
println("darkContent = $darkContent")
59-
6057
defaultStringsFile.writeText(header + "\n")
6158
defaultStringsFile.appendText(defaultContent)
6259
defaultStringsFile.appendText("\n" + footer)

gradle-plugin/src/main/kotlin/dev/icerock/gradle/generator/ios/IosMRGenerator.kt

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ import com.squareup.kotlinpoet.CodeBlock
99
import com.squareup.kotlinpoet.KModifier
1010
import com.squareup.kotlinpoet.PropertySpec
1111
import com.squareup.kotlinpoet.TypeSpec
12+
import dev.icerock.gradle.MultiplatformResourcesPluginExtension
1213
import dev.icerock.gradle.generator.MRGenerator
14+
import dev.icerock.gradle.tasks.CopyFrameworkResourcesToAppEntryPointTask
15+
import dev.icerock.gradle.tasks.CopyFrameworkResourcesToAppTask
1316
import org.gradle.api.Project
1417
import org.gradle.api.Task
1518
import org.gradle.kotlin.dsl.support.unzipTo
1619
import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation
1720
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
1821
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
22+
import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable
1923
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
24+
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
2025
import org.jetbrains.kotlin.konan.file.zipDirAs
2126
import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl
2227
import java.io.File
@@ -65,6 +70,7 @@ class IosMRGenerator(
6570
override fun apply(generationTask: Task, project: Project) {
6671
setupKLibResources(generationTask)
6772
setupFrameworkResources()
73+
setupTestsResources()
6874
}
6975

7076
override fun beforeMRGeneration() {
@@ -131,6 +137,7 @@ class IosMRGenerator(
131137

132138
private fun setupFrameworkResources() {
133139
val kotlinNativeTarget = compilation.target as KotlinNativeTarget
140+
val project = kotlinNativeTarget.project
134141

135142
kotlinNativeTarget.binaries
136143
.matching { it is Framework && it.compilation == compilation }
@@ -140,17 +147,90 @@ class IosMRGenerator(
140147
val linkTask = framework.linkTask
141148

142149
linkTask.doLast {
143-
linkTask.libraries
144-
.plus(linkTask.intermediateLibrary.get())
145-
.filter { it.extension == "klib" }
146-
.forEach {
147-
project.logger.info("copy resources from $it")
148-
val klibKonan = org.jetbrains.kotlin.konan.file.File(it.path)
149-
val klib = KotlinLibraryLayoutImpl(klibKonan)
150-
val layout = klib.extractingToTemp
151-
152-
File(layout.resourcesDir.path).copyRecursively(framework.outputFile, overwrite = true)
153-
}
150+
copyKlibsResourcesIntoFramework(linkTask)
151+
}
152+
153+
if (framework.isStatic) {
154+
val resourcesExtension =
155+
project.extensions.getByType(MultiplatformResourcesPluginExtension::class.java)
156+
if (resourcesExtension.disableStaticFrameworkWarning.not()) {
157+
project.logger.warn("$linkTask produces static framework, Xcode should have Build Phase with copyFrameworkResourcesToApp gradle task call. Please read readme on https://github.com/icerockdev/moko-resources")
158+
}
159+
createCopyFrameworkResourcesTask(linkTask)
160+
}
161+
}
162+
}
163+
164+
private fun copyKlibsResourcesIntoFramework(linkTask: KotlinNativeLink) {
165+
val project = linkTask.project
166+
val framework = linkTask.binary as Framework
167+
168+
copyResourcesFromLibraries(
169+
linkTask = linkTask,
170+
project = project,
171+
outputDir = framework.outputFile
172+
)
173+
}
174+
175+
private fun copyResourcesFromLibraries(
176+
linkTask: KotlinNativeLink,
177+
project: Project,
178+
outputDir: File
179+
) {
180+
linkTask.libraries
181+
.plus(linkTask.intermediateLibrary.get())
182+
.filter { it.extension == "klib" }
183+
.forEach {
184+
project.logger.info("copy resources from $it into $outputDir")
185+
val klibKonan = org.jetbrains.kotlin.konan.file.File(it.path)
186+
val klib = KotlinLibraryLayoutImpl(klibKonan)
187+
val layout = klib.extractingToTemp
188+
189+
File(layout.resourcesDir.path).copyRecursively(
190+
target = outputDir,
191+
overwrite = true
192+
)
193+
}
194+
}
195+
196+
private fun createCopyFrameworkResourcesTask(linkTask: KotlinNativeLink) {
197+
val framework = linkTask.binary as Framework
198+
val project = linkTask.project
199+
val taskName = linkTask.name.replace("link", "copyResources")
200+
201+
val copyTask = project.tasks.create(taskName, CopyFrameworkResourcesToAppTask::class.java) {
202+
this.framework = framework
203+
}
204+
copyTask.dependsOn(linkTask)
205+
206+
val xcodeTask = project.tasks.maybeCreate(
207+
"copyFrameworkResourcesToApp",
208+
CopyFrameworkResourcesToAppEntryPointTask::class.java
209+
)
210+
211+
if (framework.target.konanTarget == xcodeTask.konanTarget &&
212+
framework.buildType.getName() == xcodeTask.configuration
213+
) {
214+
xcodeTask.dependsOn(copyTask)
215+
}
216+
}
217+
218+
private fun setupTestsResources() {
219+
val kotlinNativeTarget = compilation.target as KotlinNativeTarget
220+
val project = kotlinNativeTarget.project
221+
222+
kotlinNativeTarget.binaries
223+
.matching { it is TestExecutable && it.compilation.associateWith.contains(compilation) }
224+
.configureEach {
225+
val executable = this as TestExecutable
226+
227+
val linkTask = executable.linkTask
228+
linkTask.doLast {
229+
copyResourcesFromLibraries(
230+
linkTask = linkTask,
231+
project = project,
232+
outputDir = executable.outputDirectory
233+
)
154234
}
155235
}
156236
}

0 commit comments

Comments
 (0)