diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index c22cf69a0fe..f9571cadee2 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -584,6 +584,25 @@ class NewMultiplatformIT : BaseGradleIT() { } } + @Test + fun testConsumeMppLibraryFromNonKotlinProject() { + val libRepo = with(Project("sample-lib", gradleVersion, "new-mpp-lib-and-app")) { + build("publish") { assertSuccessful() } + projectDir.resolve("repo") + } + + with(Project("sample-app-without-kotlin", gradleVersion, "new-mpp-lib-and-app")) { + setupWorkingDir() + gradleBuildScript().appendText("\nrepositories { maven { url '${libRepo.toURI()}' } }") + + build("assemble") { + assertSuccessful() + assertTasksExecuted(":compileJava") + assertFileExists("build/classes/java/main/A.class") + } + } + } + @Test fun testNativeTests() = with(Project("new-mpp-native-tests", gradleVersion)) { val testTasks = listOf("macos64Test", "linux64Test", "mingw64Test") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/build.gradle new file mode 100644 index 00000000000..1fba99ae41c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'java' +} + +dependencies { + implementation 'com.example:sample-lib:1.0' +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/settings.gradle new file mode 100644 index 00000000000..9dddddd9eb7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/settings.gradle @@ -0,0 +1 @@ +enableFeaturePreview('GRADLE_METADATA') \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/src/main/java/A.java b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/src/main/java/A.java new file mode 100644 index 00000000000..bfc074b5232 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-lib-and-app/sample-app-without-kotlin/src/main/java/A.java @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +import com.example.lib.CommonKt; + +public class A { + public void useKotlinClass() { + CommonKt.id(123); + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 501fbdb9030..4136b59d3c8 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -6,6 +6,7 @@ import com.android.builder.model.SourceProvider import groovy.lang.Closure import org.gradle.api.* import org.gradle.api.artifacts.ExternalDependency +import org.gradle.api.attributes.Usage import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileCollection import org.gradle.api.file.SourceDirectorySet @@ -436,8 +437,15 @@ internal abstract class AbstractKotlinPlugin( // Don't set the attributes for common module; otherwise their 'common' platform won't be compatible with the one in // platform-specific modules if (kotlinTarget.platformType != KotlinPlatformType.common) { - project.configurations.getByName(kotlinTarget.apiElementsConfigurationName).usesPlatformOf(kotlinTarget) - project.configurations.getByName(kotlinTarget.runtimeElementsConfigurationName).usesPlatformOf(kotlinTarget) + project.configurations.getByName(kotlinTarget.apiElementsConfigurationName).run { + attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(kotlinTarget)) + usesPlatformOf(kotlinTarget) + } + + project.configurations.getByName(kotlinTarget.runtimeElementsConfigurationName).run { + attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerRuntimeUsage(kotlinTarget)) + usesPlatformOf(kotlinTarget) + } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index ad215daff08..9cd13b38a70 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -26,11 +26,11 @@ import org.gradle.internal.reflect.Instantiator import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSetFactory import org.jetbrains.kotlin.gradle.plugin.sources.KotlinSourceSetFactory import org.jetbrains.kotlin.gradle.tasks.KOTLIN_COMPILER_EMBEDDABLE import org.jetbrains.kotlin.gradle.tasks.KOTLIN_MODULE_GROUP -import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast import java.io.FileNotFoundException import java.util.* import javax.inject.Inject @@ -79,6 +79,7 @@ abstract class KotlinBasePluginWrapper( private fun setupAttributeMatchingStrategy(project: Project) = with(project.dependencies.attributesSchema) { KotlinPlatformType.setupAttributesMatchingStrategy(this) + KotlinUsages.setupAttributesMatchingStrategy(this) } internal abstract fun getPlugin( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt index 80bef6f6210..08f7c289ae5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt @@ -172,7 +172,7 @@ abstract class AbstractKotlinTargetConfigurator isVisible = false isCanBeResolved = false isCanBeConsumed = true - attributes.attribute(USAGE_ATTRIBUTE, project.usageByName(Usage.JAVA_API)) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(target)) extendsFrom(configurations.maybeCreate(mainCompilation.apiConfigurationName)) if (mainCompilation is KotlinCompilationToRunnableFiles) { val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) @@ -187,7 +187,7 @@ abstract class AbstractKotlinTargetConfigurator isVisible = false isCanBeConsumed = true isCanBeResolved = false - attributes.attribute(USAGE_ATTRIBUTE, project.usageByName(Usage.JAVA_RUNTIME_JARS)) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.producerRuntimeUsage(target)) val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName) extendsFrom(implementationConfiguration, runtimeOnlyConfiguration, runtimeConfiguration) usesPlatformOf(target) @@ -277,7 +277,7 @@ abstract class AbstractKotlinTargetConfigurator usesPlatformOf(target) isVisible = false isCanBeConsumed = false - attributes.attribute(USAGE_ATTRIBUTE, compilation.target.project.usageByName(Usage.JAVA_API)) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(compilation.target)) description = "Compile classpath for $compilation." } @@ -303,7 +303,7 @@ abstract class AbstractKotlinTargetConfigurator isVisible = false isCanBeConsumed = false isCanBeResolved = true - attributes.attribute(USAGE_ATTRIBUTE, compilation.target.project.usageByName(Usage.JAVA_RUNTIME)) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerRuntimeUsage(compilation.target)) description = "Runtime classpath of $compilation." } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt index 7212f7d691d..eb69ada5210 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -31,11 +31,6 @@ class KotlinSoftwareComponent( kotlinTargets.map { it.component }.toSet() override fun getName(): String = name - - companion object { - fun kotlinApiUsage(project: Project) = project.usageByName(Usage.JAVA_API) - fun kotlinRuntimeUsage(project: Project) = project.usageByName(Usage.JAVA_RUNTIME) - } } open class KotlinVariant( @@ -93,10 +88,10 @@ internal class KotlinPlatformUsageContext( ) : UsageContext { override fun getUsage(): Usage = usage - override fun getName(): String = kotlinTarget.targetName + when (usage.name) { - Usage.JAVA_API -> "-api" - Usage.JAVA_RUNTIME -> "-runtime" - else -> error("unexpected usage") + override fun getName(): String = kotlinTarget.targetName + when (dependencyConfigurationName) { + kotlinTarget.apiElementsConfigurationName -> "-api" + kotlinTarget.runtimeElementsConfigurationName -> "-runtime" + else -> error("unexpected configuration") } private val configuration: Configuration diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt new file mode 100644 index 00000000000..a1da8c11de8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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 + +import org.gradle.api.attributes.* +import org.gradle.api.attributes.Usage.* +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.androidJvm +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.usageByName +import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast + +object KotlinUsages { + const val KOTLIN_API = "kotlin-api" + const val KOTLIN_RUNTIME = "kotlin-runtime" + val values = setOf(KOTLIN_API, KOTLIN_RUNTIME) + + private val jvmPlatformTypes: Set = setOf(jvm, androidJvm) + + internal fun consumerApiUsage(target: KotlinTarget) = target.project.usageByName( + when (target.platformType) { + in jvmPlatformTypes -> JAVA_API + else -> KOTLIN_API + } + ) + + internal fun consumerRuntimeUsage(target: KotlinTarget) = target.project.usageByName( + when (target.platformType) { + in jvmPlatformTypes -> JAVA_RUNTIME + else -> KOTLIN_RUNTIME + } + ) + + internal fun producerApiUsage(target: KotlinTarget) = target.project.usageByName( + when (target.platformType) { + in jvmPlatformTypes -> JAVA_API + else -> KOTLIN_API + } + ) + + internal fun producerRuntimeUsage(target: KotlinTarget) = target.project.usageByName( + when (target.platformType) { + in jvmPlatformTypes -> JAVA_RUNTIME_JARS + else -> KOTLIN_RUNTIME + } + ) + + private class KotlinJavaRuntimeJarsCompatibility : AttributeCompatibilityRule { + // When Gradle resolves a plain old JAR dependency with no metadata attached, the Usage attribute of that dependency + // is 'java-runtime-jars'. This rule tells Gradle that Kotlin consumers can consume plain old JARs: + override fun execute(details: CompatibilityCheckDetails) = with(details) { + when { + consumerValue?.name == KOTLIN_API && producerValue?.name == JAVA_API -> compatible() + consumerValue?.name in values && producerValue?.name == JAVA_RUNTIME_JARS -> compatible() + } + } + } + + private class KotlinUsagesDisambiguation : AttributeDisambiguationRule { + override fun execute(details: MultipleCandidatesDetails) = with(details) { + val candidateNames = candidateValues.map { it?.name }.toSet() + + // if both API and runtime artifacts are chosen according to the compatibility rules, then + // the consumer requested nothing specific, so provide them with the runtime variant, which is more complete: + if (candidateNames == setOf(KOTLIN_RUNTIME, KOTLIN_API)) { + details.closestMatch(candidateValues.single { it?.name == KOTLIN_RUNTIME }!!) + } + if (JAVA_API in candidateNames && JAVA_RUNTIME_JARS in candidateNames && values.none { it in candidateNames }) { + details.closestMatch(candidateValues.single { it?.name == JAVA_API }!!) + } + if (JAVA_RUNTIME_CLASSES in candidateNames && JAVA_RUNTIME_RESOURCES in candidateNames && KOTLIN_RUNTIME in candidateNames) { + closestMatch(candidateValues.single { it?.name == KOTLIN_RUNTIME }!!) + } + } + } + + internal fun setupAttributesMatchingStrategy(attributesSchema: AttributesSchema) { + if (isGradleVersionAtLeast(4, 0)) { + attributesSchema.attribute(Usage.USAGE_ATTRIBUTE) { strategy -> + strategy.compatibilityRules.add(KotlinJavaRuntimeJarsCompatibility::class.java) + strategy.disambiguationRules.add(KotlinUsagesDisambiguation::class.java) + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt index da8664a3665..244aeda1cf7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinTargets.kt @@ -12,6 +12,8 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Dependency import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Usage.JAVA_API +import org.gradle.api.attributes.Usage.JAVA_RUNTIME_JARS import org.gradle.api.internal.component.UsageContext import org.gradle.api.plugins.JavaPlugin import org.gradle.api.publish.maven.MavenPublication @@ -53,20 +55,24 @@ abstract class AbstractKotlinTarget ( KotlinVariant(this) } - override fun createUsageContexts(): Set = - setOf( + override fun createUsageContexts(): Set { + // Here, `JAVA_API` and `JAVA_RUNTIME_JARS` are used intentionally as Gradle needs this for + // ordering of the usage contexts (prioritizing the dependencies); + // These Java usages should not be replaced with the custom Kotlin usages. + + return listOfNotNull( KotlinPlatformUsageContext( - project, this, KotlinSoftwareComponent.kotlinApiUsage(project), apiElementsConfigurationName - ) - ) + if (compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) is KotlinCompilationToRunnableFiles) - setOf( + project, this, project.usageByName(JAVA_API), + apiElementsConfigurationName + ), + if (compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME) is KotlinCompilationToRunnableFiles) KotlinPlatformUsageContext( - project, - this, - KotlinSoftwareComponent.kotlinRuntimeUsage(project), + project, this, project.usageByName(JAVA_RUNTIME_JARS), runtimeElementsConfigurationName ) - ) else emptyList() + else null + ).toSet() + } @Suppress("UNCHECKED_CAST") internal val publicationConfigureActions = diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt index 0ecae740812..b2e94327666 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/KotlinSourceSetFactory.kt @@ -11,6 +11,8 @@ import org.gradle.api.attributes.Usage import org.gradle.api.internal.file.FileResolver import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages +import org.jetbrains.kotlin.gradle.plugin.usageByName import java.io.File internal abstract class KotlinSourceSetFactory internal constructor( @@ -67,6 +69,7 @@ internal class DefaultKotlinSourceSetFactory( dependencyConfigurationWithMetadata.forEach { (configurationName, metadataName) -> project.configurations.maybeCreate(metadataName).apply { attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common) + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(KotlinUsages.KOTLIN_API)) isVisible = false isCanBeConsumed = false extendsFrom(project.configurations.maybeCreate(configurationName))