All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet.kt Maven / Gradle / Ivy

There is a newer version: 2.0.20-RC
Show newest version
/*
 * Copyright 2010-2018 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.sources

import groovy.lang.Closure
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.file.DefaultSourceDirectorySet
import org.gradle.api.internal.file.FileResolver
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.build.DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.io.File
import java.lang.reflect.Constructor
import java.util.*

const val METADATA_CONFIGURATION_NAME_SUFFIX = "DependenciesMetadata"

class DefaultKotlinSourceSet(
    private val project: Project,
    val displayName: String,
    fileResolver: FileResolver
) : KotlinSourceSet {

    override val apiConfigurationName: String
        get() = disambiguateName("api")

    override val implementationConfigurationName: String
        get() = disambiguateName("implementation")

    override val compileOnlyConfigurationName: String
        get() = disambiguateName("compileOnly")

    override val runtimeOnlyConfigurationName: String
        get() = disambiguateName("runtimeOnly")

    override val apiMetadataConfigurationName: String
        get() = lowerCamelCaseName(apiConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX)

    override val implementationMetadataConfigurationName: String
        get() = lowerCamelCaseName(implementationConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX)

    override val compileOnlyMetadataConfigurationName: String
        get() = lowerCamelCaseName(compileOnlyConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX)

    override val runtimeOnlyMetadataConfigurationName: String
        get() = lowerCamelCaseName(runtimeOnlyConfigurationName, METADATA_CONFIGURATION_NAME_SUFFIX)

    override val kotlin: SourceDirectorySet = createDefaultSourceDirectorySet(project, "$name Kotlin source", fileResolver).apply {
        filter.include("**/*.java")
        filter.include("**/*.kt")
        filter.include("**/*.kts")
    }

    override val languageSettings: LanguageSettingsBuilder = DefaultLanguageSettingsBuilder()

    override val resources: SourceDirectorySet = createDefaultSourceDirectorySet(project, "$name resources", fileResolver)

    override fun kotlin(configureClosure: Closure): SourceDirectorySet =
        kotlin.apply { ConfigureUtil.configure(configureClosure, this) }

    override fun languageSettings(configureClosure: Closure): LanguageSettingsBuilder = languageSettings.apply {
        ConfigureUtil.configure(configureClosure, this)
    }

    override fun getName(): String = displayName

    override fun dependencies(configure: KotlinDependencyHandler.() -> Unit): Unit =
        DefaultKotlinDependencyHandler(this, project).run(configure)

    override fun dependencies(configureClosure: Closure) =
        dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) }

    override fun dependsOn(other: KotlinSourceSet) {
        dependsOnSourceSetsImpl.add(other)

        // Fail-fast approach: check on each new added edge and report a circular dependency at once when the edge is added.
        checkForCircularDependencies()

        project.afterEvaluate { defaultSourceSetLanguageSettingsChecker.runAllChecks(this, other) }
    }

    private val dependsOnSourceSetsImpl = mutableSetOf()

    override val dependsOn: Set
        get() = dependsOnSourceSetsImpl

    override fun toString(): String = "source set $name"

    private val explicitlyAddedCustomSourceFilesExtensions = ArrayList()

    override val customSourceFilesExtensions: Iterable
        get() = Iterable {
            val fromExplicitFilters = kotlin.filter.includes.mapNotNull { pattern ->
                pattern.substringAfterLast('.')
            }
            val merged = (fromExplicitFilters + explicitlyAddedCustomSourceFilesExtensions).filterNot { extension ->
                DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS.any { extension.equals(it, ignoreCase = true) }
                        || extension.any { it == '\\' || it == '/' }
            }.distinct()
            merged.iterator()
        }

    override fun addCustomSourceFilesExtensions(extensions: List) {
        explicitlyAddedCustomSourceFilesExtensions.addAll(extensions)
    }

    internal val dependencyTransformations: MutableMap = mutableMapOf()

    private val _requiresVisibilityOf = mutableSetOf()

    override val requiresVisibilityOf: MutableSet
        get() = Collections.unmodifiableSet(_requiresVisibilityOf)

    override fun requiresVisibilityOf(other: KotlinSourceSet) {
        _requiresVisibilityOf += other
    }

    //region IDE import for Granular source sets metadata

    data class MetadataDependencyTransformation(
        val groupId: String?,
        val moduleName: String,
        val projectPath: String?,
        val projectStructureMetadata: KotlinProjectStructureMetadata?,
        val allVisibleSourceSets: Set,
        /** If empty, then this source set does not see any 'new' source sets of the dependency, compared to its dependsOn parents, but it
         * still does see all what the dependsOn parents see. */
        val useFilesForSourceSets: Map>
    )

    @Suppress("unused") // Used in IDE import
    fun getDependenciesTransformation(configurationName: String): Iterable {
        val scope = KotlinDependencyScope.values().find {
            project.sourceSetMetadataConfigurationByScope(this, it).name == configurationName
        } ?: return emptyList()

        return getDependenciesTransformation(scope)
    }

    @Suppress("unused") // Used in IDE import
    fun getAdditionalVisibleSourceSets(): List =
        getVisibleSourceSetsFromAssociateCompilations(project, this)

    internal fun getDependenciesTransformation(scope: KotlinDependencyScope): Iterable {
        val metadataDependencyResolutionByModule =
            dependencyTransformations[scope]?.metadataDependencyResolutions
                ?.associateBy { it.dependency.moduleGroup to it.dependency.moduleName }
                ?: emptyMap()

        val baseDir = project.buildDir.resolve("tmp/kotlinMetadata/$name/${scope.scopeName}")
        if (metadataDependencyResolutionByModule.values.any { it is MetadataDependencyResolution.ChooseVisibleSourceSets }) {
            if (baseDir.isDirectory) {
                baseDir.deleteRecursively()
            }
            baseDir.mkdirs()
        }

        return metadataDependencyResolutionByModule.mapNotNull { (groupAndName, resolution) ->
            val (group, name) = groupAndName
            val projectPath = resolution.projectDependency?.dependencyProject?.path
            when (resolution) {
                is MetadataDependencyResolution.KeepOriginalDependency -> null

                is MetadataDependencyResolution.ExcludeAsUnrequested ->
                    MetadataDependencyTransformation(group, name, projectPath, null, emptySet(), emptyMap())

                is MetadataDependencyResolution.ChooseVisibleSourceSets -> {
                    val filesBySourceSet = resolution.getMetadataFilesBySourceSet(
                        baseDir,
                        doProcessFiles = true
                    )
                    MetadataDependencyTransformation(
                        group, name, projectPath,
                        resolution.projectStructureMetadata,
                        resolution.allVisibleSourceSetNames,
                        filesBySourceSet
                    )
                }
            }
        }
    }

    //endregion
}

private fun KotlinSourceSet.checkForCircularDependencies() {
    // If adding an edge creates a cycle, than the source node of the edge belongs to the cycle, so run DFS from that node
    // to check whether it became reachable from itself
    val visited = hashSetOf()
    val stack = LinkedHashSet() // Store the stack explicitly to pretty-print the cycle

    fun checkReachableRecursively(from: KotlinSourceSet) {
        stack += from
        visited += from

        for (to in from.dependsOn) {
            if (to == this@checkForCircularDependencies)
                throw InvalidUserCodeException(
                    "Circular dependsOn hierarchy found in the Kotlin source sets: " +
                            (stack.toList() + to).joinToString(" -> ") { it.name }
                )

            if (to !in visited) {
                checkReachableRecursively(to)
            }
        }
        stack -= from
    }

    checkReachableRecursively(this@checkForCircularDependencies)
}

internal fun KotlinSourceSet.disambiguateName(simpleName: String): String {
    val nameParts = listOfNotNull(this.name.takeIf { it != "main" }, simpleName)
    return lowerCamelCaseName(*nameParts.toTypedArray())
}

private fun createDefaultSourceDirectorySet(project: Project, name: String?, resolver: FileResolver?): SourceDirectorySet {
    if (isGradleVersionAtLeast(5, 0)) {
        val objects = project.objects
        val sourceDirectorySetMethod = objects.javaClass.methods.single { it.name == "sourceDirectorySet" && it.parameterCount == 2 }
        return sourceDirectorySetMethod(objects, name, name) as SourceDirectorySet
    }

    val klass = DefaultSourceDirectorySet::class.java
    val defaultConstructor = klass.constructorOrNull(String::class.java, FileResolver::class.java)

    return if (defaultConstructor != null && defaultConstructor.getAnnotation(java.lang.Deprecated::class.java) == null) {
        // TODO: drop when gradle < 2.12 are obsolete
        defaultConstructor.newInstance(name, resolver)
    } else {
        val directoryFileTreeFactoryClass = Class.forName("org.gradle.api.internal.file.collections.DirectoryFileTreeFactory")
        val alternativeConstructor = klass.getConstructor(String::class.java, FileResolver::class.java, directoryFileTreeFactoryClass)

        val defaultFileTreeFactoryClass = Class.forName("org.gradle.api.internal.file.collections.DefaultDirectoryFileTreeFactory")
        val defaultFileTreeFactory = defaultFileTreeFactoryClass.getConstructor().newInstance()
        alternativeConstructor.newInstance(name, resolver, defaultFileTreeFactory)
    }
}

internal fun KotlinSourceSet.getSourceSetHierarchy(): Set {
    val result = mutableSetOf()

    fun processSourceSet(sourceSet: KotlinSourceSet) {
        if (result.add(sourceSet)) {
            sourceSet.dependsOn.forEach { processSourceSet(it) }
        }
    }

    processSourceSet(this)
    return result
}


private fun  Class.constructorOrNull(vararg parameterTypes: Class<*>): Constructor? =
    try {
        getConstructor(*parameterTypes)
    } catch (e: NoSuchMethodException) {
        null
    }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy