Gradle, native: Add a DSL shortcut for iOS, watchsOS and tvOS

This patch adds a group of shortcut DSL methods allowing a user to
create simulator and device targets for Apple platforms along with
corresponding common source set(s) in one command. iOS, tvOS and
watchOS targets are supported.

Issue #KT-33856 fixed
This commit is contained in:
Ilya Matveev
2019-10-04 20:02:37 +07:00
parent 912690517f
commit 9ab2b928bc
39 changed files with 506 additions and 4 deletions
@@ -13,7 +13,8 @@ import org.jetbrains.kotlin.konan.util.DependencyDirectories
class Distribution(
private val onlyDefaultProfiles: Boolean = false,
private val konanHomeOverride: String? = null,
private val runtimeFileOverride: String? = null) {
private val runtimeFileOverride: String? = null
) {
val localKonanDir = DependencyDirectories.localKonanDir
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.NamedDomainObjectCollection
import org.gradle.api.NamedDomainObjectContainer
interface KotlinTargetsContainer {
val targets: NamedDomainObjectCollection<KotlinTarget>
@@ -13,4 +14,8 @@ interface KotlinTargetsContainer {
interface KotlinTargetsContainerWithPresets : KotlinTargetsContainer {
val presets: NamedDomainObjectCollection<KotlinTargetPreset<*>>
}
interface KotlinSourceSetContainer {
val sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2019 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
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.Assume
import org.junit.Test
class CommonNativeIT : BaseGradleIT() {
private fun doCommonNativeTest(
projectName: String,
libTargets: List<String>,
appTargets: List<String>
) = with(transformProjectWithPluginsDsl(projectName, directoryPrefix = "new-mpp-common-native")) {
val libCompileTasks = libTargets.map { ":lib:compileKotlin${it.capitalize()}" }
val appCompileTasks = appTargets.map { ":app:compileKotlin${it.capitalize()}" }
val appLinkFrameworkTasks = appTargets.map { ":app:linkDebugFramework${it.capitalize()}" }
val appLinkTestTasks = appTargets.map { ":app:linkDebugTest${it.capitalize()}" }
build(":lib:publish") {
assertSuccessful()
assertTasksExecuted(libCompileTasks)
libTargets.forEach {
assertContains("Configuring $it")
assertFileExists("lib/build/classes/kotlin/$it/main/lib.klib")
}
}
build(":app:build", *appLinkTestTasks.toTypedArray()) {
assertSuccessful()
assertTasksExecuted(appCompileTasks)
assertTasksExecuted(appLinkFrameworkTasks)
assertTasksExecuted(appLinkTestTasks)
appTargets.forEach {
assertFileExists("app/build/classes/kotlin/$it/main/app.klib")
assertFileExists("app/build/bin/$it/debugFramework")
assertFileExists("app/build/bin/$it/debugTest")
}
}
}
@Test
fun testCommonIos() {
Assume.assumeTrue(HostManager.hostIsMac)
doCommonNativeTest(
"common-ios",
libTargets = listOf("iosLibArm64", "iosLibX64"),
appTargets = listOf("iosArm64", "iosX64")
)
}
@Test
fun testCommonWatchos() {
Assume.assumeTrue(HostManager.hostIsMac)
doCommonNativeTest(
"common-watchos",
libTargets = listOf("watchosLibArm32", "watchosLibArm64", "watchosLibX86"),
appTargets = listOf("watchosArm32", "watchosArm64", "watchosX86")
)
}
@Test
fun testCommonTvos() {
Assume.assumeTrue(HostManager.hostIsMac)
doCommonNativeTest(
"common-tvos",
libTargets = listOf("tvosLibArm64", "tvosLibX64"),
appTargets = listOf("tvosArm64", "tvosX64")
)
}
}
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.Assert
import org.junit.Assume
import org.junit.Ignore
import org.junit.Test
import java.util.jar.JarFile
@@ -0,0 +1,22 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
}
repositories {
mavenLocal()
jcenter()
maven { setUrl(rootProject.projectDir.resolve("repo")) }
}
kotlin {
ios()
// Check that we can reenter the configuration method.
ios {
binaries.framework(listOf(DEBUG))
}
sourceSets["iosMain"].dependencies {
implementation("common.ios:lib:1.0")
}
}
@@ -0,0 +1,3 @@
package common.ios.app
actual fun platform(): String = "Device"
@@ -0,0 +1,10 @@
package common.ios.app
import common.ios.lib.*
expect fun platform(): String
fun appFunction() {
libFunction()
println(platform())
}
@@ -0,0 +1,7 @@
import common.ios.app.*
import kotlin.test.*
@Test
fun test() {
appFunction()
}
@@ -0,0 +1,3 @@
package common.ios.app
actual fun platform(): String = "Simulator"
@@ -0,0 +1,25 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
id("maven-publish")
}
group = "common.ios"
version = "1.0"
repositories {
mavenLocal()
jcenter()
}
kotlin {
ios("iosLib")
ios("iosLib") {
println("Configuring ${this.name}")
}
}
publishing {
repositories {
maven { setUrl(rootProject.projectDir.resolve("repo")) }
}
}
@@ -0,0 +1,3 @@
package common.ios.lib
actual fun platform(): String = "Device"
@@ -0,0 +1,7 @@
package common.ios.lib
expect fun platform(): String
fun libFunction() {
println(platform())
}
@@ -0,0 +1,3 @@
package common.ios.lib
actual fun platform(): String = "Simulator"
@@ -0,0 +1,12 @@
pluginManagement {
repositories {
mavenLocal()
jcenter()
gradlePluginPortal()
}
}
enableFeaturePreview("GRADLE_METADATA")
include(":app")
include(":lib")
@@ -0,0 +1,22 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
}
repositories {
mavenLocal()
jcenter()
maven { setUrl(rootProject.projectDir.resolve("repo")) }
}
kotlin {
tvos()
// Check that we can reenter the configuration method.
tvos {
binaries.framework(listOf(DEBUG))
}
sourceSets["tvosMain"].dependencies {
implementation("common.tvos:lib:1.0")
}
}
@@ -0,0 +1,3 @@
package common.tvos.app
actual fun platform(): String = "Device"
@@ -0,0 +1,12 @@
package common.tvos.app
import common.tvos.lib.*
expect fun platform(): String
fun appFunction() {
libFunction()
println(platform())
}
@@ -0,0 +1,7 @@
import common.tvos.app.*
import kotlin.test.*
@Test
fun test() {
appFunction()
}
@@ -0,0 +1,3 @@
package common.tvos.app
actual fun platform(): String = "Simulator"
@@ -0,0 +1,25 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
id("maven-publish")
}
group = "common.tvos"
version = "1.0"
repositories {
mavenLocal()
jcenter()
}
kotlin {
tvos("tvosLib")
tvos("tvosLib") {
println("Configuring ${this.name}")
}
}
publishing {
repositories {
maven { setUrl(rootProject.projectDir.resolve("repo")) }
}
}
@@ -0,0 +1,3 @@
package common.tvos.lib
actual fun platform(): String = "Device"
@@ -0,0 +1,7 @@
package common.tvos.lib
expect fun platform(): String
fun libFunction() {
println(platform())
}
@@ -0,0 +1,3 @@
package common.tvos.lib
actual fun platform(): String = "Simulator"
@@ -0,0 +1,12 @@
pluginManagement {
repositories {
mavenLocal()
jcenter()
gradlePluginPortal()
}
}
enableFeaturePreview("GRADLE_METADATA")
include(":app")
include(":lib")
@@ -0,0 +1,22 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
}
repositories {
mavenLocal()
jcenter()
maven { setUrl(rootProject.projectDir.resolve("repo")) }
}
kotlin {
watchos()
// Check that we can reenter the configuration method.
watchos {
binaries.framework(listOf(DEBUG))
}
sourceSets["watchosMain"].dependencies {
implementation("common.watchos:lib:1.0")
}
}
@@ -0,0 +1,5 @@
package common.watchos.app
expect val bitness: Int
actual fun platform(): String = "Device$bitness"
@@ -0,0 +1,10 @@
package common.watchos.app
import common.watchos.lib.*
expect fun platform(): String
fun appFunction() {
libFunction()
println(platform())
}
@@ -0,0 +1,7 @@
import common.watchos.app.*
import kotlin.test.*
@Test
fun test() {
appFunction()
}
@@ -0,0 +1,3 @@
package common.watchos.app
actual fun platform(): String = "Simulator"
@@ -0,0 +1,25 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
id("maven-publish")
}
group = "common.watchos"
version = "1.0"
repositories {
mavenLocal()
jcenter()
}
kotlin {
watchos("watchosLib")
watchos("watchosLib") {
println("Configuring ${this.name}")
}
}
publishing {
repositories {
maven { setUrl(rootProject.projectDir.resolve("repo")) }
}
}
@@ -0,0 +1,7 @@
package common.watchos.lib
expect fun platform(): String
fun libFunction() {
println(platform())
}
@@ -0,0 +1,3 @@
package common.watchos.lib
actual fun platform(): String = "Simulator"
@@ -0,0 +1,12 @@
pluginManagement {
repositories {
mavenLocal()
jcenter()
gradlePluginPortal()
}
}
enableFeaturePreview("GRADLE_METADATA")
include(":app")
include(":lib")
@@ -13,7 +13,11 @@ import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
open class KotlinMultiplatformExtension : KotlinProjectExtension(), KotlinTargetContainerWithPresetFunctions {
open class KotlinMultiplatformExtension :
KotlinProjectExtension(),
KotlinTargetContainerWithPresetFunctions,
KotlinTargetContainerWithNativeShortcuts
{
override lateinit var presets: NamedDomainObjectCollection<KotlinTargetPreset<*>>
internal set
@@ -22,6 +22,7 @@ import org.gradle.api.Project
import org.gradle.api.internal.plugins.DslObject
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
@@ -50,11 +51,11 @@ internal val Project.multiplatformExtensionOrNull: KotlinMultiplatformExtension?
internal val Project.multiplatformExtension: KotlinMultiplatformExtension
get() = extensions.getByName(KOTLIN_PROJECT_EXTENSION_NAME) as KotlinMultiplatformExtension
open class KotlinProjectExtension {
open class KotlinProjectExtension: KotlinSourceSetContainer {
val experimental: ExperimentalExtension
get() = DslObject(this).extensions.getByType(ExperimentalExtension::class.java)
var sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
override var sourceSets: NamedDomainObjectContainer<KotlinSourceSet>
@Suppress("UNCHECKED_CAST")
get() = DslObject(this).extensions.getByName("sourceSets") as NamedDomainObjectContainer<KotlinSourceSet>
internal set(value) {
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2019 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.dsl
import groovy.lang.Closure
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_TEST_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
interface KotlinTargetContainerWithNativeShortcuts : KotlinTargetContainerWithPresetFunctions, KotlinSourceSetContainer {
private data class DefaultSourceSets(val main: KotlinSourceSet, val test: KotlinSourceSet)
private fun KotlinNativeTarget.defaultSourceSets(): DefaultSourceSets =
DefaultSourceSets(
compilations.getByName(MAIN_COMPILATION_NAME).defaultSourceSet,
compilations.getByName(TEST_COMPILATION_NAME).defaultSourceSet
)
private fun mostCommonSourceSets() = DefaultSourceSets(
sourceSets.getByName(COMMON_MAIN_SOURCE_SET_NAME),
sourceSets.getByName(COMMON_TEST_SOURCE_SET_NAME)
)
private fun List<KotlinNativeTarget>.defaultSourceSets(): List<DefaultSourceSets> = map { it.defaultSourceSets() }
private fun createIntermediateSourceSet(
name: String,
children: List<KotlinSourceSet>,
parent: KotlinSourceSet? = null
) : KotlinSourceSet =
sourceSets.maybeCreate(name).apply {
parent?.let { dependsOn(parent) }
children.forEach {
it.dependsOn(this)
}
}
private fun createIntermediateSourceSets(
namePrefix: String,
children: List<DefaultSourceSets>,
parent: DefaultSourceSets? = null
): DefaultSourceSets {
val main = createIntermediateSourceSet("${namePrefix}Main", children.map { it.main }, parent?.main)
val test = createIntermediateSourceSet("${namePrefix}Test", children.map { it.test }, parent?.test)
return DefaultSourceSets(main, test)
}
fun ios(
namePrefix: String = "ios",
configure: KotlinNativeTarget.() -> Unit = {}
) {
val targets = listOf(
iosArm64("${namePrefix}Arm64"),
iosX64("${namePrefix}X64")
)
createIntermediateSourceSets(namePrefix, targets.defaultSourceSets(), mostCommonSourceSets())
targets.forEach { it.configure() }
}
fun ios() = ios("ios") { }
fun ios(namePrefix: String) = ios(namePrefix) { }
fun ios(namePrefix: String, configure: Closure<*>) = ios(namePrefix) { ConfigureUtil.configure(configure, this) }
fun ios(configure: Closure<*>) = ios { ConfigureUtil.configure(configure, this) }
fun tvos(
namePrefix: String = "tvos",
configure: KotlinNativeTarget.() -> Unit
) {
val targets = listOf(
tvosArm64("${namePrefix}Arm64"),
tvosX64("${namePrefix}X64")
)
createIntermediateSourceSets(namePrefix, targets.defaultSourceSets(), mostCommonSourceSets())
targets.forEach { it.configure() }
}
fun tvos() = tvos("tvos") { }
fun tvos(namePrefix: String) = tvos(namePrefix) { }
fun tvos(namePrefix: String, configure: Closure<*>) = tvos(namePrefix) { ConfigureUtil.configure(configure, this) }
fun tvos(configure: Closure<*>) = tvos { ConfigureUtil.configure(configure, this) }
fun watchos(
namePrefix: String = "watchos",
configure: KotlinNativeTarget.() -> Unit = {}
) {
val device32 = watchosArm32("${namePrefix}Arm32")
val device64 = watchosArm64("${namePrefix}Arm64")
val simulator = watchosX86("${namePrefix}X86")
val deviceTargets = listOf(device32, device64)
val deviceSourceSets = createIntermediateSourceSets(
"${namePrefix}Device",
deviceTargets.defaultSourceSets()
)
createIntermediateSourceSets(
namePrefix,
listOf(deviceSourceSets, simulator.defaultSourceSets()),
mostCommonSourceSets()
)
listOf(device32, device64, simulator).forEach { it.configure() }
}
fun watchos() = watchos("watchos") { }
fun watchos(namePrefix: String) = watchos(namePrefix) { }
fun watchos(namePrefix: String, configure: Closure<*>) = watchos(namePrefix) { ConfigureUtil.configure(configure, this) }
fun watchos(configure: Closure<*>) = watchos { ConfigureUtil.configure(configure, this) }
}