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

Fix Kotlin DSL accessor for android.kotlinOptions #11191

Merged
merged 1 commit into from Oct 30, 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
Expand Up @@ -48,4 +48,5 @@ dependencies {
implementation(library("slf4j_api"))

testImplementation(project(":kotlinDslTestFixtures"))
testImplementation(testLibrary("mockito_kotlin2"))
}
Expand Up @@ -33,13 +33,8 @@ import org.gradle.kotlin.dsl.accessors.ProjectSchemaEntry
import org.gradle.kotlin.dsl.accessors.ProjectSchemaProvider
import org.gradle.kotlin.dsl.accessors.SchemaType
import org.gradle.kotlin.dsl.accessors.TypedProjectSchema

import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult

import kotlin.reflect.KClass
import kotlin.reflect.KVisibility

import java.lang.reflect.Modifier
import kotlin.reflect.KVisibility


class DefaultProjectSchemaProvider : ProjectSchemaProvider {
Expand All @@ -57,7 +52,7 @@ class DefaultProjectSchemaProvider : ProjectSchemaProvider {
}


private
internal
data class TargetTypedSchema(
val extensions: List<ProjectSchemaEntry<TypeOf<*>>>,
val conventions: List<ProjectSchemaEntry<TypeOf<*>>>,
Expand All @@ -66,7 +61,7 @@ data class TargetTypedSchema(
)


private
internal
fun targetSchemaFor(target: Any, targetType: TypeOf<*>): TargetTypedSchema {

val extensions = mutableListOf<ProjectSchemaEntry<TypeOf<*>>>()
Expand Down Expand Up @@ -127,42 +122,86 @@ fun accessibleContainerSchema(collectionSchema: NamedDomainObjectCollectionSchem

private
fun NamedDomainObjectSchema.toFirstKotlinPublicOrSelf() =
publicType.concreteClass.kotlin.let { kotlinType ->
publicType.concreteClass.let { schemaType ->
// Because a public Java class might not correspond necessarily to a
// public Kotlin type due to Kotlin `internal` semantics, we check
// whether the public Java class is also the first public Kotlin type,
// otherwise we compute a new schema entry with the correct Kotlin type.
val firstPublicKotlinType = kotlinType.firstKotlinPublicOrSelf
val firstPublicKotlinType = schemaType.firstPublicKotlinAccessorTypeOrSelf
when {
firstPublicKotlinType === kotlinType -> this
firstPublicKotlinType === schemaType -> this
else -> ProjectSchemaNamedDomainObjectSchema(
name,
firstPublicKotlinType.asTypeOf()
TypeOf.typeOf(firstPublicKotlinType)
)
}
}


internal
val Class<*>.firstPublicKotlinAccessorTypeOrSelf: Class<*>
get() = firstPublicKotlinAccessorType ?: this


private
val KClass<*>.firstKotlinPublicOrSelf
get() = firstKotlinPublicOrNull ?: this
val Class<*>.firstPublicKotlinAccessorType: Class<*>?
get() = accessorTypePrecedenceSequence().find { it.isKotlinPublic }


internal
fun Class<*>.accessorTypePrecedenceSequence(): Sequence<Class<*>> = sequence {

// First, all the classes in the hierarchy, subclasses before superclasses
val classes = ancestorClassesIncludingSelf.toList()
yieldAll(classes)

// Then all supported interfaces sorted by subtyping (subtypes before supertypes)
val interfaces = mutableListOf<Class<*>>()
classes.forEach { `class` ->
`class`.interfaces.forEach { `interface` ->
when (val indexOfSupertype = interfaces.indexOfFirst { it.isAssignableFrom(`interface`) }) {
-1 -> interfaces.add(`interface`)
else -> if (interfaces[indexOfSupertype] != `interface`) {
interfaces.add(indexOfSupertype, `interface`)
}
}
}
}
yieldAll(interfaces)
}


internal
val Class<*>.ancestorClassesIncludingSelf: Sequence<Class<*>>
get() = sequence {

yield(this@ancestorClassesIncludingSelf)

var superclass: Class<*>? = superclass
while (superclass != null) {
val thisSuperclass: Class<*> = superclass
val nextSuperclass = thisSuperclass.superclass
if (nextSuperclass != null) { // skip java.lang.Object
yield(thisSuperclass)
}
superclass = nextSuperclass
}
}


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


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


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


private
Expand Down Expand Up @@ -227,8 +266,3 @@ val typeOfTaskContainer = typeOf<TaskContainer>()
internal
inline fun <reified T> typeOf(): TypeOf<T> =
object : TypeOf<T>() {}


private
fun KClass<out Any>.asTypeOf() =
TypeOf.typeOf(java)
@@ -0,0 +1,90 @@
/*
* 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.provider.plugins.precompiled

import org.gradle.api.plugins.ExtensionAware
import org.gradle.kotlin.dsl.provider.plugins.accessorTypePrecedenceSequence
import org.gradle.kotlin.dsl.provider.plugins.ancestorClassesIncludingSelf
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test
import kotlin.reflect.KClass


class AccessorTypePrecedenceTest {

@Test
fun `classes before interfaces`() {
assertAccessorTypePrecedenceOf<ChooseA>(
ChooseA::class,
Base::class,
Core::class,
A::class, // because it appears first than B when walking up the tree starting at ChooseA
B::class,
ExtensionAware::class
)
}

@Test
fun `classes before interfaces, subtypes before supertypes`() {
assertAccessorTypePrecedenceOf<ChooseB>(
ChooseB::class,
Base::class,
Core::class,
B::class, // because B extends ExtensionAware, even though ExtensionAware appears first
ExtensionAware::class
)
}

@Test
fun `ancestorClassesIncludingSelf does not include Any`() {
assertTypeSequence(
ChooseA::class.java.ancestorClassesIncludingSelf,
ChooseA::class,
Base::class,
Core::class
)
}

private
inline fun <reified T> assertAccessorTypePrecedenceOf(vararg types: KClass<*>) {
assertTypeSequence(
T::class.java.accessorTypePrecedenceSequence(),
*types
)
}

private
fun assertTypeSequence(sequence: Sequence<Class<*>>, vararg types: KClass<*>) {
assertThat(
sequence.mapTo(mutableListOf()) { it.simpleName },
equalTo(types.map { it.simpleName })
)
}

abstract class ChooseA : Base(), ExtensionAware, A

abstract class ChooseB : Base(), ExtensionAware

abstract class Base : Core(), B

open class Core

interface A : ExtensionAware

interface B : ExtensionAware
}
@@ -0,0 +1,89 @@
/*
* 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.provider.plugins.precompiled

import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import org.gradle.api.internal.plugins.ExtensionContainerInternal
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.plugins.ExtensionsSchema
import org.gradle.api.reflect.TypeOf
import org.gradle.internal.extensibility.DefaultExtensionsSchema
import org.gradle.kotlin.dsl.accessors.ProjectSchemaEntry
import org.gradle.kotlin.dsl.provider.plugins.targetSchemaFor
import org.gradle.kotlin.dsl.provider.plugins.typeOf
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test


class DefaultProjectSchemaProviderTest {

@Test
fun `chooses first public interface in type hierarchy`() {

val androidExtensionsSchema = DefaultExtensionsSchema.create(
listOf(
extensionSchema(
"kotlinOptions",
typeOf<KotlinJvmOptionsImpl>()
)
)
)

val androidExtensions = mock<ExtensionContainerInternal> {
on { extensionsSchema } doReturn androidExtensionsSchema
on { getByName("kotlinOptions") } doReturn KotlinJvmOptionsImpl()
}

val androidExtension = mock<AndroidExtension> {
on { extensions } doReturn androidExtensions
}

assertThat(
targetSchemaFor(
androidExtension,
typeOf<AndroidExtension>()
).extensions,
equalTo(
listOf(
ProjectSchemaEntry(
typeOf<AndroidExtension>(),
"kotlinOptions",
typeOf<KotlinJvmOptions>()
)
)
)
)
}

interface AndroidExtension : ExtensionAware

internal
class KotlinJvmOptionsImpl : KotlinJvmOptionsBase()

internal
open class KotlinJvmOptionsBase : KotlinJvmOptions

interface KotlinJvmOptions

private
fun <T> extensionSchema(name: String, publicType: TypeOf<T>): ExtensionsSchema.ExtensionSchema = mock {
on { getName() } doReturn name
on { getPublicType() } doReturn publicType
}
}