Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let Kotlin DSL gracefully handle lambdas registered as extensions (5.6.3) #11014

Merged
merged 2 commits into from Oct 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,292 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.gradle.kotlin.dsl.integration

import org.gradle.test.fixtures.file.LeaksFileHandles
import org.gradle.test.fixtures.plugin.PluginBuilder

import org.hamcrest.CoreMatchers.containsString

import org.junit.ComparisonFailure
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException

import spock.lang.Issue


@LeaksFileHandles("Kotlin Compiler Daemon working directory")
class ProjectSchemaLambdaAccessorsIntegrationTest : AbstractPluginIntegrationTest() {

@get:Rule
val exceptionRule: ExpectedException = ExpectedException.none()

@Test
fun `accessors to **untyped** groovy closures extensions are typed Any`() {

withDefaultSettings()
PluginBuilder(file("buildSrc")).apply {
addPlugin(
"""
project.extensions.add("closureExtension", { String name ->
name.toUpperCase()
})
""".trimIndent(),
"my"
)
generateForBuildSrc()
}

withBuildScript("""
plugins {
my
}

inline fun <reified T> typeOf(value: T) = typeOf<T>()

println("closureExtension: " + typeOf(closureExtension))

val casted = closureExtension as groovy.lang.Closure<*>
println(casted.call("some"))
""")

build("help").apply {
assertOutputContains("closureExtension: java.lang.Object")
assertOutputContains("SOME")
}
}

@Test
fun `accessors to **untyped** kotlin lambda extensions are typed Any`() {

requireGradleDistributionOnEmbeddedExecuter()

withDefaultSettings()
withKotlinBuildSrc()
withFile("buildSrc/src/main/kotlin/my.gradle.kts", """
extensions.add("lambdaExtension", { name: String ->
name.toUpperCase()
})
""")

withBuildScript("""
plugins {
my
}

inline fun <reified T> typeOf(value: T) = typeOf<T>()

println("lambdaExtension: " + typeOf(lambdaExtension))

val casted = lambdaExtension as (String) -> String
println(casted.invoke("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: java.lang.Object")
assertOutputContains("SOME")
}
}

@Test
fun `accessors to **untyped** java lambda extensions are typed Any`() {

withDefaultSettings()
withFile("buildSrc/build.gradle", """
plugins {
id("java")
id("java-gradle-plugin")
}
gradlePlugin {
plugins {
my {
id = "my"
implementationClass = "my.MyPlugin"
}
}
}
""")
withFile("buildSrc/src/main/java/my/MyPlugin.java", """
package my;

import org.gradle.api.*;
import java.util.function.Function;

public class MyPlugin implements Plugin<Project> {
public void apply(Project project) {
Function<String, String> lambda = s -> s.toUpperCase();
project.getExtensions().add("lambdaExtension", lambda);
}
}
""")

withBuildScript("""
import java.util.function.Function

plugins {
my
}

inline fun <reified T> typeOf(value: T) = typeOf<T>()

println("lambdaExtension: " + typeOf(lambdaExtension))

val casted = lambdaExtension as Function<String, String>
println(casted.apply("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: java.lang.Object")
assertOutputContains("SOME")
}
}

@Test
fun `accessors to **typed** groovy closures extensions are typed`() {

withDefaultSettings()
PluginBuilder(file("buildSrc")).apply {
addPlugin(
"""
def typeToken = new org.gradle.api.reflect.TypeOf<Closure<String>>() {}
project.extensions.add(typeToken, "closureExtension", { String name ->
name.toUpperCase()
})
""".trimIndent(),
"my"
)
generateForBuildSrc()
}

withBuildScript("""
plugins {
my
}

inline fun <reified T> typeOf(value: T) = typeOf<T>()

println("closureExtension: " + typeOf(closureExtension))

println(closureExtension.call("some"))
""")

build("help").apply {
assertOutputContains("closureExtension: groovy.lang.Closure<java.lang.String>")
assertOutputContains("SOME")
}
}

@Test
@Issue("https://github.com/gradle/gradle/issues/10772")
fun `accessors to **typed** kotlin lambda extensions are typed`() {

// TODO:kotlin-dsl Remove once above issue is fixed
exceptionRule.apply {
expect(ComparisonFailure::class.java)
expectMessage(containsString("lambdaExtension: kotlin.jvm.functions.Function1<? super java.lang.Object, ? extends java.lang.String>"))
}

requireGradleDistributionOnEmbeddedExecuter()

withDefaultSettings()
withKotlinBuildSrc()
withFile("buildSrc/src/main/kotlin/my.gradle.kts", """
val typeToken = typeOf<(String) -> String>()
val lambda = { name: String -> name.toUpperCase() }
extensions.add(typeToken, "lambdaExtension", lambda)
""")

withBuildScript("""
plugins {
my
}

inline fun <reified T> typeOf(value: T) = typeOf<T>()

println("lambdaExtension: " + typeOf(lambdaExtension))

println(lambdaExtension("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: kotlin.jvm.functions.Function1<? super java.lang.String, ? extends java.lang.String>")
assertOutputContains("SOME")
}
}

@Test
@Issue("https://github.com/gradle/gradle/issues/10771")
fun `accessors to **typed** java lambda extensions are typed`() {

// TODO:kotlin-dsl Remove once above issue is fixed
exceptionRule.apply {
expect(ComparisonFailure::class.java)
expectMessage(containsString("lambdaExtension: java.lang.Object"))
}

withDefaultSettings()
withFile("buildSrc/build.gradle", """
plugins {
id("java")
id("java-gradle-plugin")
}
gradlePlugin {
plugins {
my {
id = "my"
implementationClass = "my.MyPlugin"
}
}
}
""")
withFile("buildSrc/src/main/java/my/MyPlugin.java", """
package my;

import org.gradle.api.*;
import org.gradle.api.reflect.*;
import java.util.function.Function;

public class MyPlugin implements Plugin<Project> {
public void apply(Project project) {
TypeOf<Function<String, String>> typeToken = new TypeOf<Function<String, String>>() {};
Function<String, String> lambda = s -> s.toUpperCase();
project.getExtensions().add(typeToken, "lambdaExtension", lambda);
}
}
""")

withBuildScript("""
import java.util.function.Function

plugins {
my
}

inline fun <reified T> typeOf(value: T) = typeOf<T>()

println("lambdaExtension: " + typeOf(lambdaExtension))

val casted = lambdaExtension as Function<String, String>
println(casted.apply("some"))
""")

build("help").apply {
assertOutputContains("lambdaExtension: java.util.function.Function<java.lang.String, java.lang.String>")
assertOutputContains("SOME")
}
}
}
Expand Up @@ -38,7 +38,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult

import kotlin.reflect.KClass
import kotlin.reflect.KVisibility
import kotlin.reflect.full.superclasses

import java.lang.reflect.Modifier


class DefaultProjectSchemaProvider : ProjectSchemaProvider {
Expand Down Expand Up @@ -149,8 +150,19 @@ val KClass<*>.firstKotlinPublicOrSelf

private
val KClass<*>.firstKotlinPublicOrNull: KClass<*>?
get() = takeIf { visibility == KVisibility.PUBLIC }
?: superclasses.firstNotNullResult { it.firstKotlinPublicOrNull }
get() = takeIf { isJavaPublic && isKotlinVisible && visibility == KVisibility.PUBLIC }
?: (java.superclass as Class<*>?)?.kotlin?.firstKotlinPublicOrNull
?: java.interfaces.firstNotNullResult { it.kotlin.firstKotlinPublicOrNull }


private
val KClass<*>.isJavaPublic
get() = Modifier.isPublic(java.modifiers)


private
val KClass<*>.isKotlinVisible: Boolean
get() = !java.isLocalClass && !java.isAnonymousClass && !java.isSynthetic


private
Expand Down
Expand Up @@ -23,7 +23,7 @@ import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.CompileClasspath
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
Expand All @@ -46,7 +46,7 @@ abstract class CompilePrecompiledScriptPluginPlugins : DefaultTask(), SharedAcce
internal
lateinit var hashedClassPath: HashedClassPath

@get:CompileClasspath
@get:Classpath
val classPathFiles: FileCollection
get() = hashedClassPath.classPathFiles

Expand Down