[Gradle] Initial 'externalTargetApi' w/ compilable Android prototype

^KT-54766 Verification Pending
diff --git a/libraries/tools/kotlin-gradle-plugin-annotations/src/main/kotlin/org/jetbrains/kotlin/gradle/ExternalKotlinTargetApi.kt b/libraries/tools/kotlin-gradle-plugin-annotations/src/main/kotlin/org/jetbrains/kotlin/gradle/ExternalKotlinTargetApi.kt
new file mode 100644
index 0000000..d2339db
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-annotations/src/main/kotlin/org/jetbrains/kotlin/gradle/ExternalKotlinTargetApi.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2010-2022 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
+
+@RequiresOptIn(
+    message = "This API is intended to be used by Google to maintain KotlinTargets outside of kotlin.git",
+    level = RequiresOptIn.Level.ERROR
+)
+annotation class ExternalKotlinTargetApi
diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts
index 7012a3d..4212e16 100644
--- a/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts
+++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/build.gradle.kts
@@ -9,9 +9,16 @@
     compileOnly(project(":kotlin-gradle-plugin"))
 }
 
+configureKotlinCompileTasksGradleCompatibility()
+
+kotlin {
+    sourceSets.all {
+        languageSettings.optIn("org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi")
+    }
+}
+
 /* This module is just for local development / prototyping and demos */
 if (!kotlinBuildProperties.isTeamcityBuild) {
-    tasks.register("install") {
-        dependsOn(tasks.named("publishToMavenLocal"))
-    }
+    publish()
+    standardPublicJars()
 }
diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTarget.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTarget.kt
new file mode 100644
index 0000000..003d47c
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTarget.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010-2022 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.android
+
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.kotlin.dsl.getByType
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.DecoratedExternalKotlinTarget
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTarget
+
+data class AndroidDsl(
+    var compileSdk: Int
+)
+
+class AndroidTarget(
+    private val target: ExternalKotlinTarget,
+    val androidDsl: AndroidDsl
+) : DecoratedExternalKotlinTarget(target) {
+    internal val kotlin = target.project.extensions.getByType<KotlinMultiplatformExtension>()
+
+    @Suppress("unchecked_cast")
+    override val compilations: NamedDomainObjectContainer<KotlinAndroidCompilation>
+        get() = target.compilations as NamedDomainObjectContainer<KotlinAndroidCompilation>
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt
new file mode 100644
index 0000000..1e3547d
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/AndroidTargetPrototype.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+@file:Suppress("DEPRECATION", "DuplicatedCode")
+
+package org.jetbrains.kotlin.gradle.android
+
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.internal.publishing.AndroidArtifacts
+import org.gradle.api.attributes.Usage
+import org.gradle.api.attributes.java.TargetJvmEnvironment
+import org.gradle.api.attributes.java.TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.named
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
+import org.jetbrains.kotlin.gradle.kpm.external.project
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.*
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetDescriptor.DecoratedExternalTargetFactory
+
+@OptIn(ExternalVariantApi::class)
+fun KotlinMultiplatformExtension.androidTargetPrototype(): AndroidTarget {
+    val project = this.project
+    val androidExtension = project.extensions.getByType<AppExtension>()
+
+    /*
+    Set a variant filter and only allow 'debug'.
+    Reason: This prototype will not deal w/ buildTypes or flavors.
+    Only 'debug' will be supported. As of agreed w/ AGP team, this is the initial goal
+    for the APIs.
+     */
+    androidExtension.variantFilter { variant ->
+        if (variant.name != "debug") {
+            variant.ignore = true
+        }
+    }
+
+    /*
+    Create our 'AndroidTarget':
+    This uses the 'KotlinPlatformType.jvm' instead of androidJvm, since from the perspective of
+    Kotlin, this is just another 'jvm' like target (using the jvm compiler)
+     */
+    val androidTarget = createExternalKotlinTarget<AndroidTarget> {
+        targetName = "android"
+        platformType = KotlinPlatformType.jvm
+        decoratedExternalTargetFactory = DecoratedExternalTargetFactory { externalTarget ->
+            AndroidTarget(externalTarget, AndroidDsl(31))
+        }
+    }
+
+    /*
+    Whilst using the .all hook, we only expect the single 'debug' variant to be available through this API.
+     */
+    androidExtension.applicationVariants.all { applicationVariant ->
+        project.logger.quiet("Setting up applicationVariant: ${applicationVariant.name}")
+
+        /*
+        Create Compilations: main, unitTest and instrumentedTest
+        (as proposed in the new Multiplatform/Android SourceSetLayout v2)
+         */
+        val mainCompilation = androidTarget.createAndroidCompilation("main")
+        val unitTestCompilation = androidTarget.createAndroidCompilation("unitTest")
+        val instrumentedTestCompilation = androidTarget.createAndroidCompilation("instrumentedTest")
+
+        /*
+        Associate unitTest/instrumentedTest compilations with main
+         */
+        unitTestCompilation.associateWith(mainCompilation)
+        instrumentedTestCompilation.associateWith(mainCompilation)
+
+        /*
+        Setup dependsOn edges as in Multiplatform/Android SourceSetLayout v2:
+        android/main dependsOn commonMain
+        android/unitTest dependsOn commonTest
+        android/instrumentedTest *does not depend on a common SourceSet*
+         */
+        mainCompilation.defaultSourceSet.dependsOn(sourceSets.getByName("commonMain"))
+        unitTestCompilation.defaultSourceSet.dependsOn(sourceSets.getByName("commonTest"))
+
+        /*
+        Wire the Kotlin Compilations output (.class files) into the Android artifacts
+        by using the 'registerPreJavacGeneratedBytecode' function
+         */
+        applicationVariant.registerPreJavacGeneratedBytecode(mainCompilation.output.classesDirs)
+        applicationVariant.unitTestVariant.registerPreJavacGeneratedBytecode(unitTestCompilation.output.classesDirs)
+        applicationVariant.testVariant.registerPreJavacGeneratedBytecode(instrumentedTestCompilation.output.classesDirs)
+
+
+        /*
+        Add dependencies coming from Kotlin to Android by adding all dependencies from Kotlin to the variants
+        compileConfiguration or runtimeConfiguration
+         */
+        applicationVariant.compileConfiguration.extendsFrom(mainCompilation.configurations.compileDependencyConfiguration)
+        applicationVariant.runtimeConfiguration.extendsFrom(mainCompilation.configurations.runtimeDependencyConfiguration)
+        applicationVariant.unitTestVariant.compileConfiguration.extendsFrom(unitTestCompilation.configurations.compileDependencyConfiguration)
+        applicationVariant.unitTestVariant.runtimeConfiguration.extendsFrom(unitTestCompilation.configurations.runtimeDependencyConfiguration)
+        applicationVariant.testVariant.compileConfiguration.extendsFrom(instrumentedTestCompilation.configurations.compileDependencyConfiguration)
+        applicationVariant.testVariant.runtimeConfiguration.extendsFrom(instrumentedTestCompilation.configurations.runtimeDependencyConfiguration)
+
+
+        /*
+        Add the 'android boot classpath' to the compilation dependencies to compile against
+         */
+        mainCompilation.configurations.compileDependencyConfiguration.dependencies.add(
+            project.dependencies.create(project.androidBootClasspath())
+        )
+
+
+        /*
+        Setup apiElements configuration:
+        Usage: JAVA_API
+        jvmEnvironment: Android
+        variants:
+            - classes (provides access to the compiled .class files)
+                artifactType: CLASSES_JAR
+         */
+        project.configurations.getByName(androidTarget.apiElementsConfigurationName).apply {
+            attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_API))
+            attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
+            outgoing.variants.create("classes").let { variant ->
+                variant.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
+                variant.artifact(mainCompilation.output.classesDirs.singleFile) {
+                    it.builtBy(mainCompilation.output.classesDirs)
+                }
+            }
+        }
+
+
+        /*
+        Setup runtimeElements configuration:
+        Usage: JAVA_RUNTIME
+        jvmEnvironment: Android
+        variants:
+            - classes (provides access to the compiled .class files)
+                artifactType: CLASSES_JAR
+         */
+        project.configurations.getByName(androidTarget.runtimeElementsConfigurationName).apply {
+            attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME))
+            attributes.attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
+            outgoing.variants.create("classes").let { variant ->
+                variant.attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
+                variant.artifact(mainCompilation.output.classesDirs.singleFile) {
+                    it.builtBy(mainCompilation.output.classesDirs)
+                }
+            }
+        }
+
+
+        /*
+        "Disable" configurations from plain Android plugin
+        This hack will not be necessary in the final implementation
+        */
+        project.configurations.findByName("${applicationVariant.name}ApiElements")?.isCanBeConsumed = false
+        project.configurations.findByName("${applicationVariant.name}RuntimeElements")?.isCanBeConsumed = false
+    }
+
+    return androidTarget
+}
diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidCompilation.kt
new file mode 100644
index 0000000..9002322
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/KotlinAndroidCompilation.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
+
+package org.jetbrains.kotlin.gradle.android
+
+import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
+import org.jetbrains.kotlin.gradle.plugin.HasCompilerOptions
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilation
+
+class KotlinAndroidCompilation(delegate: Delegate) : ExternalKotlinCompilation(delegate) {
+    override val kotlinOptions: KotlinCommonOptions
+        get() = super.kotlinOptions as KotlinJvmOptions
+
+    @Suppress("UNCHECKED_CAST")
+    override val compilerOptions: HasCompilerOptions<KotlinJvmCompilerOptions>
+        get() = super.compilerOptions as HasCompilerOptions<KotlinJvmCompilerOptions>
+
+    var androidCompilationSpecificStuff = 10
+}
+
diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidBootClasspath.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidBootClasspath.kt
new file mode 100644
index 0000000..14d4d1d
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/androidBootClasspath.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2010-2022 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.android
+
+import com.android.build.gradle.BaseExtension
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+import org.gradle.kotlin.dsl.getByType
+import java.util.concurrent.Callable
+
+internal fun Project.androidBootClasspath(): FileCollection {
+    return project.files(Callable { project.extensions.getByType<BaseExtension>().bootClasspath })
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/createAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/createAndroidCompilation.kt
new file mode 100644
index 0000000..eb631c3
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/createAndroidCompilation.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2022 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.android
+
+import com.android.build.gradle.internal.publishing.AndroidArtifacts
+import org.gradle.api.attributes.java.TargetJvmEnvironment
+import org.gradle.kotlin.dsl.named
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.DecoratedKotlinCompilationFactory
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.createCompilation
+
+internal fun AndroidTarget.createAndroidCompilation(name: String): KotlinAndroidCompilation {
+    return createCompilation {
+        compilationName = name
+        defaultSourceSet = kotlin.sourceSets.maybeCreate(camelCase("prototype", targetName, name))
+        decoratedKotlinCompilationFactory = DecoratedKotlinCompilationFactory(::KotlinAndroidCompilation)
+        compileTaskName = camelCase("prototype", "compile", targetName, name)
+
+        /*
+        Replace Kotlin's compilation association (main <-> test) with noop,
+        since Android goes through adding a dependency on the project itself
+         */
+        compilationAssociator = ExternalKotlinCompilationDescriptor.CompilationAssociator { first, second ->
+            first.configurations.compileDependencyConfiguration.extendsFrom(
+                second.configurations.apiConfiguration,
+                second.configurations.implementationConfiguration,
+                second.configurations.compileOnlyConfiguration
+            )
+        }
+
+        /* Configure the compilation before it is accessible for user code */
+        configure { compilation ->
+            /* Setup attributes for the compile dependencies */
+            compilation.configurations.compileDependencyConfiguration.apply {
+                attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
+                attributes.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
+            }
+
+            /* Setup attributes for the runtime dependencies */
+            compilation.configurations.runtimeDependencyConfiguration?.apply {
+                attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type)
+                attributes.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.objects.named(TargetJvmEnvironment.ANDROID))
+            }
+        }
+    }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/utils.kt b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/utils.kt
new file mode 100644
index 0000000..c9cb725
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-tcs-android/src/main/kotlin/org/jetbrains/kotlin/gradle/android/utils.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package org.jetbrains.kotlin.gradle.android
+
+fun camelCase(vararg parts: String): String {
+    if (parts.isEmpty()) return ""
+    return parts.joinToString("") { it.capitalize() }.decapitalize()
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt
index 4af920e..2f03dcb 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt
@@ -32,6 +32,7 @@
 import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute
 import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
 import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
+import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
 import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
 import org.jetbrains.kotlin.gradle.tasks.registerTask
 import org.jetbrains.kotlin.gradle.utils.*
@@ -143,7 +144,7 @@
                 compilation.compileKotlinTaskProvider.map { it.outputs.files }
             })
 
-            if (compilation is KotlinJvmCompilation && compilation.target.withJavaEnabled) {
+            if (compilation is KotlinJvmCompilation && (compilation.target as? KotlinJvmTarget)?.withJavaEnabled == true) {
                 it.inputs.files({ compilation.compileJavaTaskProvider?.map { it.outputs.files } })
             }
 
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt
index 5b3be3f..bc148b9 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/AbstractKotlinTarget.kt
@@ -20,16 +20,9 @@
 import org.gradle.api.internal.component.UsageContext
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.publish.maven.MavenPublication
-import org.gradle.api.tasks.TaskProvider
-import org.gradle.jvm.tasks.Jar
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
 import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
 import org.jetbrains.kotlin.gradle.plugin.*
-import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetContainerDsl
-import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl
-import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
-import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
-import org.jetbrains.kotlin.gradle.tasks.dependsOn
 import org.jetbrains.kotlin.gradle.utils.dashSeparatedName
 import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
 
@@ -84,61 +77,7 @@
     }
 
     override val components: Set<SoftwareComponent> by lazy {
-        buildAdhocComponentsFromKotlinVariants(kotlinComponents)
-    }
-
-    private fun buildAdhocComponentsFromKotlinVariants(kotlinVariants: Set<KotlinTargetComponent>): Set<SoftwareComponent> {
-        val softwareComponentFactoryClass = SoftwareComponentFactory::class.java
-        // TODO replace internal API access with injection (not possible until we have this class on the compile classpath)
-        val softwareComponentFactory = (project as ProjectInternal).services.get(softwareComponentFactoryClass)
-
-        return kotlinVariants.map { kotlinVariant ->
-            val adhocVariant = softwareComponentFactory.adhoc(kotlinVariant.name)
-
-            project.whenEvaluated {
-                (kotlinVariant as SoftwareComponentInternal).usages.filterIsInstance<KotlinUsageContext>().forEach { kotlinUsageContext ->
-                    val publishedConfigurationName = publishedConfigurationName(kotlinUsageContext.name)
-                    val configuration = project.configurations.findByName(publishedConfigurationName)
-                        ?: project.configurations.create(publishedConfigurationName).also { configuration ->
-                            configuration.isCanBeConsumed = false
-                            configuration.isCanBeResolved = false
-                            configuration.extendsFrom(project.configurations.getByName(kotlinUsageContext.dependencyConfigurationName))
-                            configuration.artifacts.addAll(kotlinUsageContext.artifacts)
-
-                            val attributes = kotlinUsageContext.attributes
-                            attributes.keySet().forEach {
-                                // capture type parameter T
-                                fun <T> copyAttribute(key: Attribute<T>, from: AttributeContainer, to: AttributeContainer) {
-                                    to.attribute(key, from.getAttribute(key)!!)
-                                }
-                                copyAttribute(it, attributes, configuration.attributes)
-                            }
-                        }
-
-                    adhocVariant.addVariantsFromConfiguration(configuration) { configurationVariantDetails ->
-                        val mavenScope = when (kotlinUsageContext.usage.name) {
-                            "java-api-jars" -> "compile"
-                            "java-runtime-jars" -> "runtime"
-                            else -> error("unexpected usage value '${kotlinUsageContext.usage.name}'")
-                        }
-                        configurationVariantDetails.mapToMavenScope(mavenScope)
-                    }
-                }
-            }
-
-            adhocVariant as SoftwareComponent
-
-            object : ComponentWithVariants, ComponentWithCoordinates, SoftwareComponentInternal {
-                override fun getCoordinates() =
-                    (kotlinVariant as? ComponentWithCoordinates)?.coordinates ?: error("kotlinVariant is not ComponentWithCoordinates")
-
-                override fun getVariants(): Set<SoftwareComponent> =
-                    (kotlinVariant as? KotlinVariantWithMetadataVariant)?.variants.orEmpty()
-
-                override fun getName(): String = adhocVariant.name
-                override fun getUsages(): MutableSet<out UsageContext> = (adhocVariant as SoftwareComponentInternal).usages
-            }
-        }.toSet()
+        project.buildAdhocComponentsFromKotlinVariants(kotlinComponents)
     }
 
     protected open fun createKotlinVariant(
@@ -228,3 +167,56 @@
 
 internal fun javaApiUsageForMavenScoping() = "java-api-jars"
 
+internal fun Project.buildAdhocComponentsFromKotlinVariants(kotlinVariants: Set<KotlinTargetComponent>): Set<SoftwareComponent> {
+    val softwareComponentFactoryClass = SoftwareComponentFactory::class.java
+    // TODO replace internal API access with injection (not possible until we have this class on the compile classpath)
+    val softwareComponentFactory = (project as ProjectInternal).services.get(softwareComponentFactoryClass)
+
+    return kotlinVariants.map { kotlinVariant ->
+        val adhocVariant = softwareComponentFactory.adhoc(kotlinVariant.name)
+
+        project.whenEvaluated {
+            (kotlinVariant as SoftwareComponentInternal).usages.filterIsInstance<KotlinUsageContext>().forEach { kotlinUsageContext ->
+                val publishedConfigurationName = publishedConfigurationName(kotlinUsageContext.name)
+                val configuration = project.configurations.findByName(publishedConfigurationName)
+                    ?: project.configurations.create(publishedConfigurationName).also { configuration ->
+                        configuration.isCanBeConsumed = false
+                        configuration.isCanBeResolved = false
+                        configuration.extendsFrom(project.configurations.getByName(kotlinUsageContext.dependencyConfigurationName))
+                        configuration.artifacts.addAll(kotlinUsageContext.artifacts)
+
+                        val attributes = kotlinUsageContext.attributes
+                        attributes.keySet().forEach {
+                            // capture type parameter T
+                            fun <T> copyAttribute(key: Attribute<T>, from: AttributeContainer, to: AttributeContainer) {
+                                to.attribute(key, from.getAttribute(key)!!)
+                            }
+                            copyAttribute(it, attributes, configuration.attributes)
+                        }
+                    }
+
+                adhocVariant.addVariantsFromConfiguration(configuration) { configurationVariantDetails ->
+                    val mavenScope = when (kotlinUsageContext.usage.name) {
+                        "java-api-jars" -> "compile"
+                        "java-runtime-jars" -> "runtime"
+                        else -> error("unexpected usage value '${kotlinUsageContext.usage.name}'")
+                    }
+                    configurationVariantDetails.mapToMavenScope(mavenScope)
+                }
+            }
+        }
+
+        adhocVariant as SoftwareComponent
+
+        object : ComponentWithVariants, ComponentWithCoordinates, SoftwareComponentInternal {
+            override fun getCoordinates() =
+                (kotlinVariant as? ComponentWithCoordinates)?.coordinates ?: error("kotlinVariant is not ComponentWithCoordinates")
+
+            override fun getVariants(): Set<SoftwareComponent> =
+                (kotlinVariant as? KotlinVariantWithMetadataVariant)?.variants.orEmpty()
+
+            override fun getName(): String = adhocVariant.name
+            override fun getUsages(): MutableSet<out UsageContext> = (adhocVariant as SoftwareComponentInternal).usages
+        }
+    }.toSet()
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt
index e2ce160..8389969 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationConfigurationsContainer.kt
@@ -12,7 +12,7 @@
 import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
 import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
 
-internal interface KotlinCompilationConfigurationsContainer {
+interface KotlinCompilationConfigurationsContainer {
     val deprecatedCompileConfiguration: Configuration?
     val deprecatedRuntimeConfiguration: Configuration?
     val apiConfiguration: Configuration
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt
index dee1b27..e3c091d 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt
@@ -32,7 +32,7 @@
 
     /* Resolution of friend artifacts */
 
-    interface FriendArtifactResolver {
+    fun interface FriendArtifactResolver {
         fun resolveFriendArtifacts(compilation: InternalKotlinCompilation<*>): FileCollection
 
         companion object {
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt
index bef3662..0472e45 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationImpl.kt
@@ -25,16 +25,14 @@
 import org.jetbrains.kotlin.gradle.utils.ObservableSet
 import org.jetbrains.kotlin.tooling.core.MutableExtras
 import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
-import javax.inject.Inject
 
-
-internal class KotlinCompilationImpl @Inject constructor(
+internal class KotlinCompilationImpl internal constructor(
     private val params: Params
 ) : InternalKotlinCompilation<KotlinCommonOptions> {
 
     //region Params
 
-    data class Params(
+    internal data class Params(
         val target: KotlinTarget,
         val compilationName: String,
         val sourceSets: KotlinCompilationSourceSetsContainer,
@@ -61,7 +59,7 @@
 
     override val extras: MutableExtras = mutableExtrasOf()
 
-    val sourceSets get() = params.sourceSets
+    internal val sourceSets get() = params.sourceSets
 
     override val configurations: KotlinCompilationConfigurationsContainer
         get() = params.dependencyConfigurations
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt
new file mode 100644
index 0000000..ca58188
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/DecoratedExternalKotlinTarget.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2010-2022 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.plugin.mpp.external
+
+import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
+import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
+
+@ExternalKotlinTargetApi
+abstract class DecoratedExternalKotlinTarget(
+    internal val delegate: ExternalKotlinTarget
+) : KotlinTarget by delegate
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilation.kt
new file mode 100644
index 0000000..4d9f90a
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilation.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package org.jetbrains.kotlin.gradle.plugin.mpp.external
+
+import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
+import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
+import org.jetbrains.kotlin.gradle.plugin.mpp.DecoratedKotlinCompilation
+import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationImpl
+
+@ExternalKotlinTargetApi
+abstract class ExternalKotlinCompilation(delegate: Delegate) :
+    DecoratedKotlinCompilation<KotlinCommonOptions>(delegate.compilation) {
+    class Delegate internal constructor(internal val compilation: KotlinCompilationImpl)
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt
new file mode 100644
index 0000000..38292eb
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010-2022 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.plugin.mpp.external
+
+import org.gradle.api.file.FileCollection
+import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
+import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
+import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
+import org.jetbrains.kotlin.gradle.plugin.mpp.DecoratedKotlinCompilation
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.*
+import kotlin.properties.Delegates
+
+@ExternalKotlinTargetApi
+interface ExternalKotlinCompilationDescriptor<T : ExternalKotlinCompilation> {
+    fun interface DecoratedKotlinCompilationFactory<T : DecoratedKotlinCompilation<*>> {
+        fun create(delegate: ExternalKotlinCompilation.Delegate): T
+    }
+
+    fun interface FriendArtifactResolver<T : ExternalKotlinCompilation> {
+        fun resolveFriendPaths(compilation: T): FileCollection
+    }
+
+    fun interface CompilationAssociator<T : ExternalKotlinCompilation> {
+        fun associate(compilation: T, main: ExternalKotlinCompilation)
+    }
+
+    val compilationName: String
+    val compileTaskName: String?
+    val compileAllTaskName: String?
+    val defaultSourceSet: KotlinSourceSet
+    val decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T>
+    val friendArtifactResolver: FriendArtifactResolver<T>?
+    val compilationAssociator: CompilationAssociator<T>?
+    val configure: ((T) -> Unit)?
+}
+
+@ExternalKotlinTargetApi
+fun <T : ExternalKotlinCompilation> ExternalKotlinCompilationDescriptor(
+    configure: ExternalKotlinCompilationDescriptorBuilder<T>.() -> Unit
+): ExternalKotlinCompilationDescriptor<T> {
+    return ExternalKotlinCompilationDescriptorBuilderImpl<T>().also(configure).run {
+        ExternalKotlinCompilationDescriptorImpl(
+            compilationName = compilationName,
+            compileTaskName = compileTaskName,
+            compileAllTaskName = compileAllTaskName,
+            defaultSourceSet = defaultSourceSet,
+            decoratedKotlinCompilationFactory = decoratedKotlinCompilationFactory,
+            friendArtifactResolver = friendArtifactResolver,
+            compilationAssociator = compilationAssociator,
+            configure = this.configure
+        )
+    }
+}
+
+@ExternalKotlinTargetApi
+interface ExternalKotlinCompilationDescriptorBuilder<T : ExternalKotlinCompilation> {
+    var compilationName: String
+    var compileTaskName: String?
+    var compileAllTaskName: String?
+    var defaultSourceSet: KotlinSourceSet
+    var decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T>
+    var friendArtifactResolver: FriendArtifactResolver<T>?
+    var compilationAssociator: CompilationAssociator<T>?
+    var configure: ((T) -> Unit)?
+    fun configure(action: (T) -> Unit) = apply {
+        val configure = this.configure
+        if (configure == null) this.configure = action
+        else this.configure = { configure(it); action(it) }
+    }
+}
+
+@ExternalKotlinTargetApi
+private class ExternalKotlinCompilationDescriptorBuilderImpl<T : ExternalKotlinCompilation> :
+    ExternalKotlinCompilationDescriptorBuilder<T> {
+    override var compilationName: String by Delegates.notNull()
+    override var compileTaskName: String? = null
+    override var compileAllTaskName: String? = null
+    override var defaultSourceSet: KotlinSourceSet by Delegates.notNull()
+    override var decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T> by Delegates.notNull()
+    override var friendArtifactResolver: FriendArtifactResolver<T>? = null
+    override var compilationAssociator: CompilationAssociator<T>? = null
+    override var configure: ((T) -> Unit)? = null
+}
+
+@ExternalKotlinTargetApi
+private data class ExternalKotlinCompilationDescriptorImpl<T : ExternalKotlinCompilation>(
+    override val compilationName: String,
+    override val compileTaskName: String?,
+    override val compileAllTaskName: String?,
+    override val defaultSourceSet: KotlinSourceSet,
+    override val decoratedKotlinCompilationFactory: DecoratedKotlinCompilationFactory<T>,
+    override val friendArtifactResolver: FriendArtifactResolver<T>?,
+    override val compilationAssociator: CompilationAssociator<T>?,
+    override val configure: ((T) -> Unit)?
+) : ExternalKotlinCompilationDescriptor<T>
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTarget.kt
new file mode 100644
index 0000000..7b4d710
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTarget.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010-2022 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.plugin.mpp.external
+
+import org.gradle.api.Action
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.AttributeContainer
+import org.gradle.api.component.SoftwareComponent
+import org.gradle.api.publish.maven.MavenPublication
+import org.gradle.api.tasks.TaskProvider
+import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
+import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
+import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
+import org.jetbrains.kotlin.gradle.plugin.mpp.buildAdhocComponentsFromKotlinVariants
+
+@ExternalKotlinTargetApi
+class ExternalKotlinTarget internal constructor(
+    override val project: Project,
+    override val targetName: String,
+    override val platformType: KotlinPlatformType,
+    val defaultConfiguration: Configuration,
+    val apiElementsConfiguration: Configuration,
+    val runtimeElementsConfiguration: Configuration,
+    override val publishable: Boolean,
+    internal val kotlinComponents: Set<KotlinTargetComponent>,
+    private val artifactsTaskLocator: ArtifactsTaskLocator,
+) : KotlinTarget {
+
+    fun interface ArtifactsTaskLocator {
+        fun locate(target: ExternalKotlinTarget): TaskProvider<out Task>
+    }
+
+    val kotlin = project.multiplatformExtension
+
+    override val useDisambiguationClassifierAsSourceSetNamePrefix: Boolean = true
+
+    override val overrideDisambiguationClassifierOnIdeImport: String? = null
+
+    val artifactsTask: TaskProvider<out Task> by lazy {
+        artifactsTaskLocator.locate(this)
+    }
+
+    override val artifactsTaskName: String
+        get() = artifactsTask.name
+
+    override val defaultConfigurationName: String
+        get() = defaultConfiguration.name
+
+    override val apiElementsConfigurationName: String
+        get() = apiElementsConfiguration.name
+
+    override val runtimeElementsConfigurationName: String
+        get() = runtimeElementsConfiguration.name
+
+    override val components: Set<SoftwareComponent> by lazy {
+        project.buildAdhocComponentsFromKotlinVariants(kotlinComponents)
+    }
+
+    override val compilations: NamedDomainObjectContainer<ExternalKotlinCompilation> by lazy {
+        project.container(ExternalKotlinCompilation::class.java)
+    }
+
+    override fun mavenPublication(action: Action<MavenPublication>) {
+        TODO("Not yet implemented")
+    }
+
+    override val preset: Nothing? = null
+
+    override fun getAttributes(): AttributeContainer {
+        TODO("Not yet implemented")
+    }
+
+    internal fun onCreated() {
+        artifactsTask
+        components
+    }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor.kt
new file mode 100644
index 0000000..47707de
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010-2022 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.plugin.mpp.external
+
+import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetDescriptor.DecoratedExternalTargetFactory
+import kotlin.properties.Delegates
+
+@ExternalKotlinTargetApi
+interface ExternalKotlinTargetDescriptor<T : DecoratedExternalKotlinTarget> {
+
+    fun interface DecoratedExternalTargetFactory<T : DecoratedExternalKotlinTarget> {
+        fun create(target: ExternalKotlinTarget): T
+    }
+
+    val targetName: String
+    val platformType: KotlinPlatformType
+    val decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T>
+    val configure: ((T) -> Unit)?
+}
+
+@ExternalKotlinTargetApi
+interface ExternalKotlinTargetDescriptorBuilder<T : DecoratedExternalKotlinTarget> {
+    var targetName: String
+    var platformType: KotlinPlatformType
+    var decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T>
+    var configure: ((T) -> Unit)?
+    fun configure(action: (T) -> Unit) = apply {
+        val configure = this.configure
+        if (configure == null) this.configure = action
+        else this.configure = { configure(it); action(it) }
+    }
+}
+
+@ExternalKotlinTargetApi
+fun <T : DecoratedExternalKotlinTarget> ExternalKotlinTargetDescriptor(
+    configure: ExternalKotlinTargetDescriptorBuilder<T>.() -> Unit
+): ExternalKotlinTargetDescriptor<T> {
+    return ExternalKotlinTargetDescriptorBuilderImpl<T>().also(configure).build()
+}
+
+@ExternalKotlinTargetApi
+private class ExternalKotlinTargetDescriptorBuilderImpl<T : DecoratedExternalKotlinTarget> : ExternalKotlinTargetDescriptorBuilder<T> {
+    override var targetName: String by Delegates.notNull()
+    override var platformType: KotlinPlatformType by Delegates.notNull()
+    override var decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T> by Delegates.notNull()
+    override var configure: ((T) -> Unit)? = null
+
+    fun build(): ExternalKotlinTargetDescriptorImpl<T> = ExternalKotlinTargetDescriptorImpl(
+        targetName = targetName,
+        platformType = platformType,
+        decoratedExternalTargetFactory = decoratedExternalTargetFactory,
+        configure = configure
+    )
+}
+
+@ExternalKotlinTargetApi
+private data class ExternalKotlinTargetDescriptorImpl<T : DecoratedExternalKotlinTarget>(
+    override val targetName: String,
+    override val platformType: KotlinPlatformType,
+    override val decoratedExternalTargetFactory: DecoratedExternalTargetFactory<T>,
+    override val configure: ((T) -> Unit)?
+) : ExternalKotlinTargetDescriptor<T>
+
+
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt
new file mode 100644
index 0000000..d11f31b
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010-2022 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.plugin.mpp.external
+
+import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
+import org.jetbrains.kotlin.gradle.plugin.Kotlin2JvmSourceSetProcessor
+import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.DefaultKotlinCompilationAssociator
+import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.DefaultKotlinCompilationFriendPathsResolver
+import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationAssociator
+import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationSourceSetsContainer
+import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.*
+import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.KotlinCompilationImplFactory.KotlinCompilationTaskNamesContainerFactory
+import org.jetbrains.kotlin.gradle.plugin.mpp.decoratedInstance
+import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilation.Delegate
+import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
+import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig
+
+@ExternalKotlinTargetApi
+fun <T : ExternalKotlinCompilation> DecoratedExternalKotlinTarget.createCompilation(
+    descriptor: ExternalKotlinCompilationDescriptor<T>
+): T {
+    val compilationImplFactory = KotlinCompilationImplFactory(
+        compilerOptionsFactory = when (platformType) {
+            KotlinPlatformType.common -> KotlinMultiplatformCommonCompilerOptionsFactory
+            KotlinPlatformType.jvm -> KotlinJvmCompilerOptionsFactory
+            KotlinPlatformType.androidJvm -> KotlinJvmCompilerOptionsFactory
+            KotlinPlatformType.js -> KotlinJsCompilerOptionsFactory
+            KotlinPlatformType.native -> KotlinNativeCompilerOptionsFactory
+            KotlinPlatformType.wasm -> KotlinMultiplatformCommonCompilerOptionsFactory
+        },
+        compilationSourceSetsContainerFactory = { _, _ -> KotlinCompilationSourceSetsContainer(descriptor.defaultSourceSet) },
+        compilationTaskNamesContainerFactory = KotlinCompilationTaskNamesContainerFactory { target, compilationName ->
+            val default = DefaultKotlinCompilationTaskNamesContainerFactory.create(target, compilationName)
+            default.copy(
+                compileTaskName = descriptor.compileTaskName ?: default.compileTaskName,
+                compileAllTaskName = descriptor.compileAllTaskName ?: default.compileAllTaskName
+            )
+        },
+        compilationAssociator = descriptor.compilationAssociator?.let { declaredAssociator ->
+            @Suppress("unchecked_cast")
+            KotlinCompilationAssociator { _, first, second ->
+                declaredAssociator.associate(first.decoratedInstance as T, second.decoratedInstance as ExternalKotlinCompilation)
+            }
+        } ?: DefaultKotlinCompilationAssociator,
+        compilationFriendPathsResolver = DefaultKotlinCompilationFriendPathsResolver(
+            DefaultKotlinCompilationFriendPathsResolver.FriendArtifactResolver.composite(
+                DefaultKotlinCompilationFriendPathsResolver.DefaultFriendArtifactResolver,
+                descriptor.friendArtifactResolver?.let { declaredResolver ->
+                    DefaultKotlinCompilationFriendPathsResolver.FriendArtifactResolver { compilation ->
+                        @Suppress("unchecked_cast")
+                        declaredResolver.resolveFriendPaths(compilation as T)
+                    }
+                }
+            )
+        )
+    )
+
+    val compilationImpl = compilationImplFactory.create(this, descriptor.compilationName)
+    val decoratedCompilation = descriptor.decoratedKotlinCompilationFactory.create(Delegate(compilationImpl))
+    descriptor.configure?.invoke(decoratedCompilation)
+    this.delegate.compilations.add(decoratedCompilation)
+
+
+    val tasksProvider = KotlinTasksProvider()
+    val compilationInfo = KotlinCompilationInfo(decoratedCompilation)
+
+    val config = KotlinCompileConfig(compilationInfo)
+    config.configureTask { task ->
+        task.useModuleDetection.value(true).disallowChanges()
+        task.destinationDirectory.set(project.layout.buildDirectory.dir("tmp/kotlin-classes/debug"))
+    }
+
+    Kotlin2JvmSourceSetProcessor(tasksProvider, compilationInfo).run()
+    project.logger.quiet("Registered: ${compilationInfo.compileKotlinTaskName}")
+
+    return decoratedCompilation
+}
+
+@ExternalKotlinTargetApi
+fun <T : ExternalKotlinCompilation> DecoratedExternalKotlinTarget.createCompilation(
+    descriptor: ExternalKotlinCompilationDescriptorBuilder<T>.() -> Unit
+): T {
+    return createCompilation(ExternalKotlinCompilationDescriptor(descriptor))
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt
new file mode 100644
index 0000000..0c55646
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinTarget.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+@file:OptIn(ExternalKotlinTargetApi::class)
+
+package org.jetbrains.kotlin.gradle.plugin.mpp.external
+
+import org.gradle.jvm.tasks.Jar
+import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
+import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
+
+@ExternalKotlinTargetApi
+fun <T : DecoratedExternalKotlinTarget> KotlinMultiplatformExtension.createExternalKotlinTarget(
+    descriptor: ExternalKotlinTargetDescriptor<T>
+): T {
+    val defaultConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "default"))
+    val apiElementsConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "apiElements"))
+    val runtimeElementsConfiguration = project.configurations.maybeCreate(lowerCamelCaseName(descriptor.targetName, "runtimeElements"))
+    val artifactsTaskLocator = ExternalKotlinTarget.ArtifactsTaskLocator { target ->
+        target.project.locateOrRegisterTask<Jar>(lowerCamelCaseName(descriptor.targetName, "jar"))
+    }
+
+    val target = ExternalKotlinTarget(
+        project = project,
+        targetName = descriptor.targetName,
+        platformType = descriptor.platformType,
+        defaultConfiguration = defaultConfiguration,
+        apiElementsConfiguration = apiElementsConfiguration,
+        runtimeElementsConfiguration = runtimeElementsConfiguration,
+        publishable = true,
+        kotlinComponents = emptySet(),
+        artifactsTaskLocator = artifactsTaskLocator
+    )
+
+    val decorated = descriptor.decoratedExternalTargetFactory.create(target)
+    target.onCreated()
+    descriptor.configure?.invoke(decorated)
+    targets.add(decorated)
+    return decorated
+}
+
+@ExternalKotlinTargetApi
+fun <T : DecoratedExternalKotlinTarget> KotlinMultiplatformExtension.createExternalKotlinTarget(
+    descriptor: ExternalKotlinTargetDescriptorBuilder<T>.() -> Unit
+): T {
+    return createExternalKotlinTarget(ExternalKotlinTargetDescriptor(descriptor))
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt
index dac5e8f..a6a1189 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/buildProject.kt
@@ -5,6 +5,7 @@
 
 package org.jetbrains.kotlin.gradle
 
+import com.android.build.api.dsl.ApplicationExtension
 import com.android.build.gradle.LibraryExtension
 import org.gradle.api.Project
 import org.gradle.api.internal.project.ProjectInternal
@@ -13,7 +14,6 @@
 import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
 import org.jetbrains.kotlin.gradle.kpm.applyKpmPlugin
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin
-import org.jetbrains.kotlin.gradle.plugin.extraProperties
 import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
 
 fun buildProject(
@@ -51,8 +51,13 @@
 
 fun Project.androidLibrary(code: LibraryExtension.() -> Unit) {
     plugins.findPlugin("com.android.library") ?: plugins.apply("com.android.library")
-    val androidExtension = project.extensions.findByName("android") as? LibraryExtension
-        ?: throw IllegalStateException("Android library extension is missing in project")
+    val androidExtension = project.extensions.getByName("android") as LibraryExtension
+    androidExtension.code()
+}
+
+fun Project.androidApplication(code: ApplicationExtension.() -> Unit) {
+    plugins.findPlugin("com.android.application") ?: plugins.apply("com.android.application")
+    val androidExtension = project.extensions.getByName("android") as ApplicationExtension
     androidExtension.code()
 }
 
diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt
new file mode 100644
index 0000000..92c9f61
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/externalTargetApi/ExternalAndroidTargetPrototypeSmokeTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-2022 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.
+ */
+
+@file:Suppress("FunctionName")
+
+package org.jetbrains.kotlin.gradle.externalTargetApi
+
+import org.jetbrains.kotlin.gradle.android.androidTargetPrototype
+import org.jetbrains.kotlin.gradle.androidApplication
+import org.jetbrains.kotlin.gradle.buildProjectWithMPP
+import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class ExternalAndroidTargetPrototypeSmokeTest {
+
+    @Test
+    fun `apply prototype - evaluate - compilations exist`() {
+        val project = buildProjectWithMPP()
+        project.androidApplication { compileSdk = 31 }
+        val androidTargetPrototype = project.multiplatformExtension.androidTargetPrototype()
+        project.evaluate()
+
+        assertEquals(
+            setOf("main", "unitTest", "instrumentedTest"),
+            androidTargetPrototype.compilations.map { it.name }.toSet()
+        )
+    }
+
+    @Test
+    fun `apply prototype - evaluate - configurations can be resolved`() {
+        val project = buildProjectWithMPP()
+        project.androidApplication { compileSdk = 31 }
+
+        val androidTargetPrototype = project.multiplatformExtension.androidTargetPrototype()
+        project.evaluate()
+
+        androidTargetPrototype.compilations.all { compilation ->
+            compilation.compileDependencyFiles.files
+            compilation.runtimeDependencyFiles?.files
+        }
+    }
+}
\ No newline at end of file