Migration to new multiplatorm plugin (#947)

Migration to a new multiplatform plugin
  
  * kotlinx-coroutines-core-[common|js|native] are merged into one core module with multiple source sets
  * Folder structure and readme are restructured
  * Publication process is patched to preserve backward compatibility with artifact names
diff --git a/build.gradle b/build.gradle
index a6e81b7..01b2ec1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,26 @@
 /*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
  */
+import org.jetbrains.kotlin.konan.target.HostManager
+
+apply from: rootProject.file("gradle/experimental.gradle")
+
+def rootModule = "kotlinx.coroutines"
+def coreModule = "kotlinx-coroutines-core"
+// Not applicable for Kotlin plugin
+def sourceless = ['kotlinx.coroutines', 'site']
+def internal = sourceless + ['benchmarks', 'knit', 'js-stub', 'stdlib-stubs', 'binary-compatibility-validator']
+// Not published
+def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js', 'android-unit-tests']
+
+static def platformOf(project) {
+    def name = project.name
+    if (name.endsWith("-js")) return "js"
+    if (name.endsWith("-common") || name.endsWith("-native")) {
+        throw IllegalStateException("$name platform is not supported")
+    }
+    return "jvm"
+}
 
 buildscript {
     ext.useKotlinSnapshot = rootProject.properties['kotlinSnapshot'] != null
@@ -19,22 +39,10 @@
         maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" }
         maven { url "https://plugins.gradle.org/m2/" }
     }
-    configurations.classpath {
-        resolutionStrategy {
-            eachDependency { DependencyResolveDetails details ->
-                if (details.requested.group == 'org.jetbrains.kotlin' && details.requested.name != 'kotlin-native-gradle-plugin') {
-                    // fix version of all dependencies from org.jetbrains.kotlin group
-                    // even when other dependencies require other versions indirectly,
-                    // except kotlin-native, which has its own pre-release versioning
-                    details.useVersion kotlin_version
-                }
-            }
-        }
-    }
+
     dependencies {
         classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-        classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version"
         classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
         classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicFU_version"
         classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$bintray_version"
@@ -48,19 +56,26 @@
 }
 
 allprojects {
+    // the only place where HostManager could be instantiated
+    project.ext.hostManager = new HostManager()
+}
+
+allprojects {
+    apply plugin: 'kotlinx-atomicfu'
+
     def deployVersion = properties['DeployVersion']
     if (deployVersion != null) version = deployVersion
     if (useKotlinSnapshot) {
         kotlin_version = '1.2-SNAPSHOT'
     }
 
-    def name = it.name
+    def projectName = it.name
     repositories {
         /*
          * google should be first in the repository list because some of the play services
          * transitive dependencies was removed from jcenter, thus breaking gradle dependency resolution
          */
-        if (name == "kotlinx-coroutines-play-services") {
+        if (projectName == "kotlinx-coroutines-play-services") {
             google()
         }
         jcenter()
@@ -69,98 +84,63 @@
         maven { url "https://kotlin.bintray.com/kotlin-eap" }
         maven { url "https://kotlin.bintray.com/kotlinx" }
     }
-}
 
+    if (projectName == rootModule || projectName == coreModule) return
 
-// Report Kotlin compiler version when building project
-println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION")
+    // Add dependency to core source sets. Core is configured in kx-core/build.gradle
+    evaluationDependsOn(":$coreModule")
+    if (sourceless.contains(projectName)) return
 
-// --------------- Configure sub-projects with Kotlin sources ---------------
-
-def sourceless = ['site']
-
-static def platformOf(project) {
-    if (project.name.endsWith("-common")) return "common"
-    if (project.name.endsWith("-js")) return "js"
-    if (project.name.endsWith("-native")) return "native"
-    return "jvm"
-}
-
-static def platformLib(base, platform) {
-    if (platform == "jvm") return base
-    return "$base-$platform"
-}
-
-subprojects {
-    if (useKotlinSnapshot) {
-        repositories {
-            maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
-        }
-    }
-    configurations.all {
-        resolutionStrategy {
-            eachDependency { DependencyResolveDetails details ->
-                if (details.requested.group == 'org.jetbrains.kotlin') {
-                    details.useVersion kotlin_version
-                }
-            }
-        }
-    }
-}
-
-configure(subprojects.findAll { !sourceless.contains(it.name)  }) {
     def platform = platformOf(it)
     apply from: rootProject.file("gradle/compile-${platform}.gradle")
+
+
+    dependencies {
+        // See comment below for rationale, it will be replaced with "project" dependency
+        compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
+        compileOnly "org.jetbrains.kotlinx:atomicfu:$atomicFU_version"
+
+        // the only way IDEA can resolve test classes
+        testCompile project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+    }
+
+    tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
+        kotlinOptions.freeCompilerArgs += experimentalAnnotations.collect { "-Xuse-experimental=" + it }
+        kotlinOptions.freeCompilerArgs += "-progressive"
+        // Binary compatibility support
+        kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"]
+    }
 }
 
-// --------------- Configure sub-projects that are part of the library ---------------
+/*
+ * Hack to trick nmpp plugin: we are renaming artifacts in order to provide backward compatibility for dependencies,
+ * but publishing plugin does not re-read artifact names for kotlin-jvm projects, so renaming is not applied in pom files
+ * for JVM-only projects.
+ *
+ * We artificially replace "project" dependency with "module" one to have proper names in pom files, but then substitute it 
+ * to have out "project" dependency back.
+ */
+configure(subprojects.findAll { it.name != coreModule && it.name != rootModule }) {
+    configurations.all {
+        resolutionStrategy.dependencySubstitution {
+            substitute module("org.jetbrains.kotlinx:kotlinx-coroutines-core:$version") with project(':kotlinx-coroutines-core')
+        }
+    }
+}
 
-def internal = sourceless + ['benchmarks', 'knit', 'js-stub', 'stdlib-stubs', 'binary-compatibility-validator']
-
-// Reconfigure source sets to avoid long "src/main/kotlin/fqn"
-configure(subprojects.findAll { !it.name.contains(sourceless) && it.name != "benchmarks" }) {
-    def projectName = it.name
+// Redefine source sets because we are not using 'kotlin/main/fqn' folder convention
+configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != "benchmarks" && it.name != coreModule }) {
     sourceSets {
         main.kotlin.srcDirs = ['src']
         test.kotlin.srcDirs = ['test']
-        // todo: do we still need this workaround?
-        if (!projectName.endsWith("-native")) {
-            main.resources.srcDirs = ['resources']
-            test.resources.srcDirs = ['test-resources']
-        }
+        main.resources.srcDirs = ['resources']
+        test.resources.srcDirs = ['test-resources']
+
     }
 }
 
-// configure atomicfu
-configure(subprojects.findAll { !internal.contains(it.name) }) {
-    def platform = platformOf(it)
-    apply from: rootProject.file("gradle/atomicfu-${platform}.gradle")
-}
-
-// configure dependencies on core
-configure(subprojects.findAll { !internal.contains(it.name) && it.name != 'kotlinx-coroutines-core-common'}) {
-    def platform = platformOf(it)
-    def coroutines_core = platformLib("kotlinx-coroutines-core", platform)
-
-    if (it.name == coroutines_core) {
-        dependencies {
-            expectedBy project(':kotlinx-coroutines-core-common')
-        }
-    } else {
-        dependencies {
-            compile project(":$coroutines_core")
-            //the only way IDEA can resolve test classes
-            testCompile project(":$coroutines_core").sourceSets.test.output
-        }
-    }
-}
-
-// --------------- Configure sub-projects that are published ---------------
-
-def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js', 'android-unit-tests']
-
-def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/"
-def core_docs_file = "$projectDir/core/kotlinx-coroutines-core/build/dokka/kotlinx-coroutines-core/package-list"
+def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/$coreModule/"
+def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/kotlinx-coroutines-core/package-list"
 
 configure(subprojects.findAll { !unpublished.contains(it.name) }) {
     apply from: rootProject.file('gradle/dokka.gradle')
@@ -168,12 +148,8 @@
 }
 
 configure(subprojects.findAll { !unpublished.contains(it.name) }) {
-    def platform = platformOf(it)
-    def coroutines_core = platformLib("kotlinx-coroutines-core", platform)
-
-    if (it.name != coroutines_core) {
-        dokka.dependsOn project(":$coroutines_core").dokka
-
+    if (it.name != coreModule) {
+        dokka.dependsOn project(":$coreModule").dokka
         tasks.withType(dokka.getClass()) {
             externalDocumentationLink {
                 url = new URL(core_docs_url)
@@ -182,27 +158,16 @@
         }
     }
 
-    if (platform == "jvm") {
-        dokkaJavadoc.dependsOn project(":$coroutines_core").dokka
-        // dump declarations from main JVM module for binary-compatibility-validator
-        compileKotlin {
-            kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"]
-        }
-    }
-
-
-    tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
-        kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental",
-                                           "-Xuse-experimental=kotlin.experimental.ExperimentalTypeInference",
-                                           "-Xuse-experimental=kotlin.ExperimentalMultiplatform",
-                                           "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
-                                           "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
-                                           "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
-                                           "-progressive"]
+    if (platformOf(it) == "jvm") {
+        dokkaJavadoc.dependsOn project(":$coreModule").dokka
     }
 }
 
-// main deployment task
+
+// Report Kotlin compiler version when building project
+println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION")
+
+// --------------- Configure sub-projects that are published ---------------
 task deploy(dependsOn: getTasksByName("bintrayUpload", true) + getTasksByName("publishNpm", true))
 
 apply plugin: 'base'