Skip to content

Commit

Permalink
Merge pull request #11014 from gradle/eskatos/release-5.6/kotlin-dsl
Browse files Browse the repository at this point in the history
Let Kotlin DSL gracefully handle lambdas registered as extensions (5.6.3)
  • Loading branch information
bamboo committed Oct 11, 2019
2 parents 24fed11 + 7baa2a9 commit cae0857
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 5 deletions.
@@ -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

0 comments on commit cae0857

Please sign in to comment.