Introduce Gradle multiplatform plugin

This commit is contained in:
Alexey Tsvetkov
2016-12-14 17:34:03 +03:00
parent d1156211a8
commit c31f91c28a
22 changed files with 308 additions and 0 deletions
@@ -291,4 +291,26 @@ class KotlinGradleIT: BaseGradleIT() {
assertSuccessful()
}
}
@Test
fun testMultiplatformCompile() {
val project = Project("multiplatformProject", GRADLE_VERSION)
project.build("build") {
assertSuccessful()
assertContains(":lib:compileKotlinCommon",
":lib:compileTestKotlinCommon",
":libJvm:compileKotlin",
":libJvm:compileTestKotlin",
":libJs:compileKotlin2Js",
":libJs:compileTestKotlin2Js")
assertFileExists("lib/build/classes/main/foo/PlatformClass.kotlin_metadata")
assertFileExists("lib/build/classes/test/foo/PlatformTest.kotlin_metadata")
assertFileExists("libJvm/build/classes/main/foo/PlatformClass.class")
assertFileExists("libJvm/build/classes/test/foo/PlatformTest.class")
assertFileExists("libJs/build/classes/main/libJs_main.js")
assertFileExists("libJs/build/classes/test/libJs_test.js")
}
}
}
@@ -0,0 +1,16 @@
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
}
}
@@ -0,0 +1,5 @@
package foo
header class PlatformClass {
val value: String
}
@@ -0,0 +1,5 @@
package foo
header class PlatformTest {
//val value: PlatformClass
}
@@ -0,0 +1,5 @@
apply plugin: 'kotlin-platform-js'
dependencies {
implement project(":lib")
}
@@ -0,0 +1,5 @@
package foo
impl class PlatformClass {
impl val value: String = "JS"
}
@@ -0,0 +1,5 @@
package foo
impl class PlatformTest {
//impl val value: PlatformClass = PlatformClass()
}
@@ -0,0 +1,6 @@
apply plugin: 'kotlin-platform-jvm'
dependencies {
implement project(":lib")
compile 'com.google.guava:guava:20.0'
}
@@ -0,0 +1,5 @@
package foo
impl class PlatformClass {
impl val value: String = "JVM"
}
@@ -0,0 +1,7 @@
package foo
import com.google.common.collect.ImmutableSet
fun main(args: Array<String>) {
val colors = ImmutableSet.of("red", "orange", "yellow")
}
@@ -0,0 +1,5 @@
package foo
impl class PlatformTest {
//impl val value: PlatformClass = PlatformClass()
}
@@ -0,0 +1,113 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
abstract class KotlinPlatformPluginBase(protected val platformName: String) : Plugin<Project>
open class KotlinPlatformCommonPlugin : KotlinPlatformPluginBase("common") {
override fun apply(project: Project) {
project.applyPlugin<KotlinCommonPluginWrapper>()
}
}
open class KotlinPlatformImplementationPluginBase(platformName: String) : KotlinPlatformPluginBase(platformName) {
private val commonProjects = arrayListOf<Project>()
private val platformSourceSets = hashMapOf<String, SourceSet>()
override fun apply(project: Project) {
project.tasks.withType(AbstractKotlinCompile::class.java).all {
(it as KotlinCompile<*>).kotlinOptions.freeCompilerArgs += listOf("-Xmulti-platform")
}
project.sourceSets.associateByTo(platformSourceSets, SourceSet::getName)
val implementConfig = project.configurations.create("implement")
implementConfig.isTransitive = false
implementConfig.dependencies.whenObjectAdded { dep ->
if (dep is ProjectDependency) {
addCommonProject(dep.dependencyProject, project)
}
else {
throw GradleException("$project `implement` dependency is not a project: $dep")
}
}
}
private fun addCommonProject(commonProject: Project, platformProject: Project) {
commonProjects.add(commonProject)
if (commonProjects.size > 1) {
throw GradleException("Platform project $platformProject implements more than one common project: ${commonProjects.joinToString()}")
}
commonProject.whenEvaluated {
if ((!commonProject.plugins.hasPlugin(KotlinPlatformCommonPlugin::class.java))) {
throw GradleException("Platform project $platformProject implements non-common project $commonProject (`apply plugin 'kotlin-platform-kotlin'`)")
}
commonProject.sourceSets.all { commonSourceSet ->
// todo: warn if not found
val platformSourceSet = platformSourceSets[commonSourceSet.name]
val platformKotlinSourceDirectorySet = platformSourceSet?.kotlin
commonSourceSet.kotlin?.srcDirs?.forEach { platformKotlinSourceDirectorySet?.srcDir(it) }
}
}
}
private val SourceSet.kotlin: SourceDirectorySet?
get() = ((getConvention("kotlin") ?: getConvention("kotlin2js")) as? KotlinSourceSet)?.kotlin
}
open class KotlinPlatformJvmPlugin : KotlinPlatformImplementationPluginBase("jvm") {
override fun apply(project: Project) {
project.applyPlugin<KotlinPluginWrapper>()
super.apply(project)
}
}
open class KotlinPlatformJsPlugin : KotlinPlatformImplementationPluginBase("js") {
override fun apply(project: Project) {
project.applyPlugin<Kotlin2JsPluginWrapper>()
super.apply(project)
}
}
private val Project.sourceSets: SourceSetContainer
get() = convention.getPlugin(JavaPluginConvention::class.java).sourceSets
private fun <T> Project.whenEvaluated(fn: Project.()->T) {
if (state.executed) {
fn()
}
else {
afterEvaluate { it.fn() }
}
}
private inline fun <reified T : Plugin<*>> Project.applyPlugin() {
pluginManager.apply(T::class.java)
}
@@ -209,6 +209,35 @@ internal class Kotlin2JsSourceSetProcessor(
}
}
internal class KotlinCommonSourceSetProcessor(
project: Project,
javaBasePlugin: JavaBasePlugin,
sourceSet: SourceSet,
tasksProvider: KotlinTasksProvider,
kotlinSourceSetProvider: KotlinSourceSetProvider
) : KotlinSourceSetProcessor<KotlinCompileCommon>(
project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider,
dslExtensionName = KOTLIN_DSL_NAME,
taskDescription = "Compiles the kotlin sources in $sourceSet to Metadata.",
compileTaskNameSuffix = "kotlinCommon"
) {
override fun doTargetSpecificProcessing() {
project.afterEvaluate { project ->
kotlinTask.source(kotlinSourceSet.kotlin)
project.tasks.findByName(sourceSet.classesTaskName).dependsOn(kotlinTask)
// can be missing (e.g. in case of tests)
project.tasks.findByName(sourceSet.jarTaskName)?.dependsOn(kotlinTask)
val javaTask = project.tasks.findByName(sourceSet.compileJavaTaskName)
project.tasks.remove(javaTask)
}
}
override val defaultKotlinDestinationDir: File
get() = sourceSet.output.classesDir
override fun doCreateTask(project: Project, taskName: String): KotlinCompileCommon =
tasksProvider.createKotlinCommonTask(project, taskName, sourceSet.name)
}
internal abstract class AbstractKotlinPlugin(
val tasksProvider: KotlinTasksProvider,
@@ -252,6 +281,14 @@ internal open class KotlinPlugin(
}
}
internal open class KotlinCommonPlugin(
tasksProvider: KotlinTasksProvider,
kotlinSourceSetProvider: KotlinSourceSetProvider,
kotlinPluginVersion: String
) : AbstractKotlinPlugin(tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion) {
override fun buildSourceSetProcessor(project: Project, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet, kotlinPluginVersion: String) =
KotlinCommonSourceSetProcessor(project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider)
}
internal open class Kotlin2JsPlugin(
tasksProvider: KotlinTasksProvider,
@@ -48,6 +48,11 @@ open class KotlinPluginWrapper @Inject constructor(fileResolver: FileResolver):
KotlinPlugin(KotlinTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion, kotlinGradleBuildServices)
}
open class KotlinCommonPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) =
KotlinCommonPlugin(KotlinCommonTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
}
open class KotlinAndroidPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) =
KotlinAndroidPlugin(AndroidTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion, kotlinGradleBuildServices)
@@ -37,6 +37,7 @@ sealed internal class RegexTaskToFriendTaskMapper(
) : TaskToFriendTaskMapper() {
class Default : RegexTaskToFriendTaskMapper("compile", "TestKotlin", "Kotlin")
class JavaScript : RegexTaskToFriendTaskMapper("compile", "TestKotlin2Js", "Kotlin2Js")
class Common : RegexTaskToFriendTaskMapper("compile", "TestKotlinCommon", "KotlinCommon")
class Android : RegexTaskToFriendTaskMapper("compile", "(Unit|Android)TestKotlin", "Kotlin")
private val regex = "$prefix(.*)$suffix".toRegex()
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.ChangedFiles
import java.io.File
internal open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArguments>() {
override val compiler = K2MetadataCompiler()
override fun populateCompilerArguments(): K2MetadataCompilerArguments =
K2MetadataCompilerArguments()
override fun getSourceRoots(): SourceRoots =
SourceRoots.KotlinOnly.create(getSource())
override fun callCompiler(args: K2MetadataCompilerArguments, sourceRoots: SourceRoots, changedFiles: ChangedFiles) {
val classpathList = classpath.files.toMutableList()
val friendTask = friendTaskName?.let { project.tasks.findByName(it) } as? AbstractCompile
friendTask?.let { classpathList.add(it.destinationDir) }
with(args) {
classpath = classpathList.joinToString(File.pathSeparator)
destination = destinationDir.canonicalPath
freeArgs = sourceRoots.kotlinSourceFiles.map { it.canonicalPath }
}
val messageCollector = GradleMessageCollector(project.logger)
val exitCode = compiler.exec(messageCollector, Services.EMPTY, args)
throwGradleExceptionIfError(exitCode)
}
}
@@ -37,10 +37,21 @@ internal open class KotlinTasksProvider {
friendTaskName = taskToFriendTaskMapper[this]
}
fun createKotlinCommonTask(project: Project, name: String, sourceSetName: String): KotlinCompileCommon =
project.tasks.create(name, KotlinCompileCommon::class.java).apply {
this.sourceSetName = sourceSetName
friendTaskName = taskToFriendTaskMapper[this]
}
protected open val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.Default()
}
internal class KotlinCommonTasksProvider : KotlinTasksProvider() {
override val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.Common()
}
internal class Kotlin2JsTasksProvider : KotlinTasksProvider() {
override val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.JavaScript()
@@ -0,0 +1 @@
implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinPlatformCommonPlugin
@@ -0,0 +1 @@
implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJsPlugin
@@ -0,0 +1 @@
implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin